2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
111 #include "password.h"
112 #include "ldapserver.h"
126 #define N_ATTACH_COLS (N_COL_COLUMNS)
130 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
145 } ComposeCallAdvancedAction;
149 PRIORITY_HIGHEST = 1,
158 COMPOSE_INSERT_SUCCESS,
159 COMPOSE_INSERT_READ_ERROR,
160 COMPOSE_INSERT_INVALID_CHARACTER,
161 COMPOSE_INSERT_NO_FILE
162 } ComposeInsertResult;
166 COMPOSE_WRITE_FOR_SEND,
167 COMPOSE_WRITE_FOR_STORE
172 COMPOSE_QUOTE_FORCED,
179 SUBJECT_FIELD_PRESENT,
184 #define B64_LINE_SIZE 57
185 #define B64_BUFFSIZE 77
187 #define MAX_REFERENCES_LEN 999
189 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
190 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
192 static GdkColor default_header_bgcolor = {
199 static GdkColor default_header_color = {
206 static GList *compose_list = NULL;
207 static GSList *extra_headers = NULL;
209 static Compose *compose_generic_new (PrefsAccount *account,
213 GList *listAddress );
215 static Compose *compose_create (PrefsAccount *account,
220 static void compose_entry_indicate (Compose *compose,
221 const gchar *address);
222 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
223 ComposeQuoteMode quote_mode,
227 static Compose *compose_forward_multiple (PrefsAccount *account,
228 GSList *msginfo_list);
229 static Compose *compose_reply (MsgInfo *msginfo,
230 ComposeQuoteMode quote_mode,
235 static Compose *compose_reply_mode (ComposeMode mode,
236 GSList *msginfo_list,
238 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
239 static void compose_update_privacy_systems_menu(Compose *compose);
241 static GtkWidget *compose_account_option_menu_create
243 static void compose_set_out_encoding (Compose *compose);
244 static void compose_set_template_menu (Compose *compose);
245 static void compose_destroy (Compose *compose);
247 static MailField compose_entries_set (Compose *compose,
249 ComposeEntryType to_type);
250 static gint compose_parse_header (Compose *compose,
252 static gint compose_parse_manual_headers (Compose *compose,
254 HeaderEntry *entries);
255 static gchar *compose_parse_references (const gchar *ref,
258 static gchar *compose_quote_fmt (Compose *compose,
264 gboolean need_unescape,
265 const gchar *err_msg);
267 static void compose_reply_set_entry (Compose *compose,
273 followup_and_reply_to);
274 static void compose_reedit_set_entry (Compose *compose,
277 static void compose_insert_sig (Compose *compose,
279 static ComposeInsertResult compose_insert_file (Compose *compose,
282 static gboolean compose_attach_append (Compose *compose,
285 const gchar *content_type,
286 const gchar *charset);
287 static void compose_attach_parts (Compose *compose,
290 static gboolean compose_beautify_paragraph (Compose *compose,
291 GtkTextIter *par_iter,
293 static void compose_wrap_all (Compose *compose);
294 static void compose_wrap_all_full (Compose *compose,
297 static void compose_set_title (Compose *compose);
298 static void compose_select_account (Compose *compose,
299 PrefsAccount *account,
302 static PrefsAccount *compose_current_mail_account(void);
303 /* static gint compose_send (Compose *compose); */
304 static gboolean compose_check_for_valid_recipient
306 static gboolean compose_check_entries (Compose *compose,
307 gboolean check_everything);
308 static gint compose_write_to_file (Compose *compose,
311 gboolean attach_parts);
312 static gint compose_write_body_to_file (Compose *compose,
314 static gint compose_remove_reedit_target (Compose *compose,
316 static void compose_remove_draft (Compose *compose);
317 static gint compose_queue_sub (Compose *compose,
321 gboolean perform_checks,
322 gboolean remove_reedit_target);
323 static int compose_add_attachments (Compose *compose,
325 static gchar *compose_get_header (Compose *compose);
326 static gchar *compose_get_manual_headers_info (Compose *compose);
328 static void compose_convert_header (Compose *compose,
333 gboolean addr_field);
335 static void compose_attach_info_free (AttachInfo *ainfo);
336 static void compose_attach_remove_selected (GtkAction *action,
339 static void compose_template_apply (Compose *compose,
342 static void compose_attach_property (GtkAction *action,
344 static void compose_attach_property_create (gboolean *cancelled);
345 static void attach_property_ok (GtkWidget *widget,
346 gboolean *cancelled);
347 static void attach_property_cancel (GtkWidget *widget,
348 gboolean *cancelled);
349 static gint attach_property_delete_event (GtkWidget *widget,
351 gboolean *cancelled);
352 static gboolean attach_property_key_pressed (GtkWidget *widget,
354 gboolean *cancelled);
356 static void compose_exec_ext_editor (Compose *compose);
358 static gint compose_exec_ext_editor_real (const gchar *file,
359 GdkNativeWindow socket_wid);
360 static gboolean compose_ext_editor_kill (Compose *compose);
361 static gboolean compose_input_cb (GIOChannel *source,
362 GIOCondition condition,
364 static void compose_set_ext_editor_sensitive (Compose *compose,
366 static gboolean compose_get_ext_editor_cmd_valid();
367 static gboolean compose_get_ext_editor_uses_socket();
368 static gboolean compose_ext_editor_plug_removed_cb
371 #endif /* G_OS_UNIX */
373 static void compose_undo_state_changed (UndoMain *undostruct,
378 static void compose_create_header_entry (Compose *compose);
379 static void compose_add_header_entry (Compose *compose, const gchar *header,
380 gchar *text, ComposePrefType pref_type);
381 static void compose_remove_header_entries(Compose *compose);
383 static void compose_update_priority_menu_item(Compose * compose);
385 static void compose_spell_menu_changed (void *data);
386 static void compose_dict_changed (void *data);
388 static void compose_add_field_list ( Compose *compose,
389 GList *listAddress );
391 /* callback functions */
393 static void compose_notebook_size_alloc (GtkNotebook *notebook,
394 GtkAllocation *allocation,
396 static gboolean compose_edit_size_alloc (GtkEditable *widget,
397 GtkAllocation *allocation,
398 GtkSHRuler *shruler);
399 static void account_activated (GtkComboBox *optmenu,
401 static void attach_selected (GtkTreeView *tree_view,
402 GtkTreePath *tree_path,
403 GtkTreeViewColumn *column,
405 static gboolean attach_button_pressed (GtkWidget *widget,
406 GdkEventButton *event,
408 static gboolean attach_key_pressed (GtkWidget *widget,
411 static void compose_send_cb (GtkAction *action, gpointer data);
412 static void compose_send_later_cb (GtkAction *action, gpointer data);
414 static void compose_save_cb (GtkAction *action,
417 static void compose_attach_cb (GtkAction *action,
419 static void compose_insert_file_cb (GtkAction *action,
421 static void compose_insert_sig_cb (GtkAction *action,
423 static void compose_replace_sig_cb (GtkAction *action,
426 static void compose_close_cb (GtkAction *action,
428 static void compose_print_cb (GtkAction *action,
431 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
433 static void compose_address_cb (GtkAction *action,
435 static void about_show_cb (GtkAction *action,
437 static void compose_template_activate_cb(GtkWidget *widget,
440 static void compose_ext_editor_cb (GtkAction *action,
443 static gint compose_delete_cb (GtkWidget *widget,
447 static void compose_undo_cb (GtkAction *action,
449 static void compose_redo_cb (GtkAction *action,
451 static void compose_cut_cb (GtkAction *action,
453 static void compose_copy_cb (GtkAction *action,
455 static void compose_paste_cb (GtkAction *action,
457 static void compose_paste_as_quote_cb (GtkAction *action,
459 static void compose_paste_no_wrap_cb (GtkAction *action,
461 static void compose_paste_wrap_cb (GtkAction *action,
463 static void compose_allsel_cb (GtkAction *action,
466 static void compose_advanced_action_cb (GtkAction *action,
469 static void compose_grab_focus_cb (GtkWidget *widget,
472 static void compose_changed_cb (GtkTextBuffer *textbuf,
475 static void compose_wrap_cb (GtkAction *action,
477 static void compose_wrap_all_cb (GtkAction *action,
479 static void compose_find_cb (GtkAction *action,
481 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
483 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
486 static void compose_toggle_ruler_cb (GtkToggleAction *action,
488 static void compose_toggle_sign_cb (GtkToggleAction *action,
490 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
492 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
493 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
494 static void activate_privacy_system (Compose *compose,
495 PrefsAccount *account,
497 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
499 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
501 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
502 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
503 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_attach_drag_received_cb (GtkWidget *widget,
506 GdkDragContext *drag_context,
509 GtkSelectionData *data,
513 static void compose_insert_drag_received_cb (GtkWidget *widget,
514 GdkDragContext *drag_context,
517 GtkSelectionData *data,
521 static void compose_header_drag_received_cb (GtkWidget *widget,
522 GdkDragContext *drag_context,
525 GtkSelectionData *data,
530 static gboolean compose_drag_drop (GtkWidget *widget,
531 GdkDragContext *drag_context,
533 guint time, gpointer user_data);
534 static gboolean completion_set_focus_to_subject
539 static void text_inserted (GtkTextBuffer *buffer,
544 static Compose *compose_generic_reply(MsgInfo *msginfo,
545 ComposeQuoteMode quote_mode,
549 gboolean followup_and_reply_to,
552 static void compose_headerentry_changed_cb (GtkWidget *entry,
553 ComposeHeaderEntry *headerentry);
554 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
558 ComposeHeaderEntry *headerentry);
560 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
562 static void compose_allow_user_actions (Compose *compose, gboolean allow);
564 static void compose_nothing_cb (GtkAction *action, gpointer data)
570 static void compose_check_all (GtkAction *action, gpointer data);
571 static void compose_highlight_all (GtkAction *action, gpointer data);
572 static void compose_check_backwards (GtkAction *action, gpointer data);
573 static void compose_check_forwards_go (GtkAction *action, gpointer data);
576 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
578 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
581 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
582 FolderItem *folder_item);
584 static void compose_attach_update_label(Compose *compose);
585 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
586 gboolean respect_default_to);
587 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
588 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
590 static GtkActionEntry compose_popup_entries[] =
592 {"Compose", NULL, "Compose", NULL, NULL, NULL },
593 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
594 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
595 {"Compose/---", NULL, "---", NULL, NULL, NULL },
596 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
599 static GtkActionEntry compose_entries[] =
601 {"Menu", NULL, "Menu", NULL, NULL, NULL },
603 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
604 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
606 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
608 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
609 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
610 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
612 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
613 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
614 {"Message/---", NULL, "---", NULL, NULL, NULL },
616 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
617 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
618 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
619 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
620 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
621 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
622 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
623 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
624 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
625 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
628 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
629 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
630 {"Edit/---", NULL, "---", NULL, NULL, NULL },
632 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
633 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
634 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
636 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
637 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
638 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
639 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
641 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
643 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
644 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
645 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
646 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
647 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
648 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
649 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
650 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
651 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
652 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
653 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
654 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
655 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
656 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
657 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
659 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
660 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
664 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
669 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
670 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
671 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
672 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
674 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
675 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
679 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
680 {"Options/---", NULL, "---", NULL, NULL, NULL },
681 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
682 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
684 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
685 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
687 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
688 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
689 #define ENC_ACTION(cs_char,c_char,string) \
690 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
692 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
693 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
694 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
695 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
696 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
697 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
698 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
699 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
700 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
703 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
705 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
706 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
707 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
708 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
711 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
714 static GtkToggleActionEntry compose_toggle_entries[] =
716 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
717 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
718 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
719 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
720 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
721 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
722 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
725 static GtkRadioActionEntry compose_radio_rm_entries[] =
727 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
728 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
729 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
730 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
733 static GtkRadioActionEntry compose_radio_prio_entries[] =
735 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
738 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
742 static GtkRadioActionEntry compose_radio_enc_entries[] =
744 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
779 static GtkTargetEntry compose_mime_types[] =
781 {"text/uri-list", 0, 0},
782 {"UTF8_STRING", 0, 0},
786 static gboolean compose_put_existing_to_front(MsgInfo *info)
788 const GList *compose_list = compose_get_compose_list();
789 const GList *elem = NULL;
792 for (elem = compose_list; elem != NULL && elem->data != NULL;
794 Compose *c = (Compose*)elem->data;
796 if (!c->targetinfo || !c->targetinfo->msgid ||
800 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
801 gtkut_window_popup(c->window);
809 static GdkColor quote_color1 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
811 static GdkColor quote_color2 =
812 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 static GdkColor quote_color3 =
814 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor1 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 static GdkColor quote_bgcolor2 =
819 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
820 static GdkColor quote_bgcolor3 =
821 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor signature_color = {
830 static GdkColor uri_color = {
837 static void compose_create_tags(GtkTextView *text, Compose *compose)
839 GtkTextBuffer *buffer;
840 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
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 gint compose_send(Compose *compose)
5210 FolderItem *folder = NULL;
5212 gchar *msgpath = NULL;
5213 gboolean discard_window = FALSE;
5214 gchar *errstr = NULL;
5215 gchar *tmsgid = NULL;
5216 MainWindow *mainwin = mainwindow_get_mainwindow();
5217 gboolean queued_removed = FALSE;
5219 if (prefs_common.send_dialog_invisible
5220 || compose->batch == TRUE)
5221 discard_window = TRUE;
5223 compose_allow_user_actions (compose, FALSE);
5224 compose->sending = TRUE;
5226 if (compose_check_entries(compose, TRUE) == FALSE) {
5227 if (compose->batch) {
5228 gtk_widget_show_all(compose->window);
5234 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5237 if (compose->batch) {
5238 gtk_widget_show_all(compose->window);
5241 alertpanel_error(_("Could not queue message for sending:\n\n"
5242 "Charset conversion failed."));
5243 } else if (val == -5) {
5244 alertpanel_error(_("Could not queue message for sending:\n\n"
5245 "Couldn't get recipient encryption key."));
5246 } else if (val == -6) {
5248 } else if (val == -3) {
5249 if (privacy_peek_error())
5250 alertpanel_error(_("Could not queue message for sending:\n\n"
5251 "Signature failed: %s"), privacy_get_error());
5252 } else if (val == -2 && errno != 0) {
5253 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5255 alertpanel_error(_("Could not queue message for sending."));
5260 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5261 if (discard_window) {
5262 compose->sending = FALSE;
5263 compose_close(compose);
5264 /* No more compose access in the normal codepath
5265 * after this point! */
5270 alertpanel_error(_("The message was queued but could not be "
5271 "sent.\nUse \"Send queued messages\" from "
5272 "the main window to retry."));
5273 if (!discard_window) {
5280 if (msgpath == NULL) {
5281 msgpath = folder_item_fetch_msg(folder, msgnum);
5282 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5285 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5286 claws_unlink(msgpath);
5289 if (!discard_window) {
5291 if (!queued_removed)
5292 folder_item_remove_msg(folder, msgnum);
5293 folder_item_scan(folder);
5295 /* make sure we delete that */
5296 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5298 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5299 folder_item_remove_msg(folder, tmp->msgnum);
5300 procmsg_msginfo_free(&tmp);
5307 if (!queued_removed)
5308 folder_item_remove_msg(folder, msgnum);
5309 folder_item_scan(folder);
5311 /* make sure we delete that */
5312 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5314 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5315 folder_item_remove_msg(folder, tmp->msgnum);
5316 procmsg_msginfo_free(&tmp);
5319 if (!discard_window) {
5320 compose->sending = FALSE;
5321 compose_allow_user_actions (compose, TRUE);
5322 compose_close(compose);
5326 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5327 "the main window to retry."), errstr);
5330 alertpanel_error_log(_("The message was queued but could not be "
5331 "sent.\nUse \"Send queued messages\" from "
5332 "the main window to retry."));
5334 if (!discard_window) {
5343 toolbar_main_set_sensitive(mainwin);
5344 main_window_set_menu_sensitive(mainwin);
5350 compose_allow_user_actions (compose, TRUE);
5351 compose->sending = FALSE;
5352 compose->modified = TRUE;
5353 toolbar_main_set_sensitive(mainwin);
5354 main_window_set_menu_sensitive(mainwin);
5359 static gboolean compose_use_attach(Compose *compose)
5361 GtkTreeModel *model = gtk_tree_view_get_model
5362 (GTK_TREE_VIEW(compose->attach_clist));
5363 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5366 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5369 gchar buf[BUFFSIZE];
5371 gboolean first_to_address;
5372 gboolean first_cc_address;
5374 ComposeHeaderEntry *headerentry;
5375 const gchar *headerentryname;
5376 const gchar *cc_hdr;
5377 const gchar *to_hdr;
5378 gboolean err = FALSE;
5380 debug_print("Writing redirect header\n");
5382 cc_hdr = prefs_common_translated_header_name("Cc:");
5383 to_hdr = prefs_common_translated_header_name("To:");
5385 first_to_address = TRUE;
5386 for (list = compose->header_list; list; list = list->next) {
5387 headerentry = ((ComposeHeaderEntry *)list->data);
5388 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5390 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5391 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5392 Xstrdup_a(str, entstr, return -1);
5394 if (str[0] != '\0') {
5395 compose_convert_header
5396 (compose, buf, sizeof(buf), str,
5397 strlen("Resent-To") + 2, TRUE);
5399 if (first_to_address) {
5400 err |= (fprintf(fp, "Resent-To: ") < 0);
5401 first_to_address = FALSE;
5403 err |= (fprintf(fp, ",") < 0);
5405 err |= (fprintf(fp, "%s", buf) < 0);
5409 if (!first_to_address) {
5410 err |= (fprintf(fp, "\n") < 0);
5413 first_cc_address = TRUE;
5414 for (list = compose->header_list; list; list = list->next) {
5415 headerentry = ((ComposeHeaderEntry *)list->data);
5416 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5418 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5419 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5420 Xstrdup_a(str, strg, return -1);
5422 if (str[0] != '\0') {
5423 compose_convert_header
5424 (compose, buf, sizeof(buf), str,
5425 strlen("Resent-Cc") + 2, TRUE);
5427 if (first_cc_address) {
5428 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5429 first_cc_address = FALSE;
5431 err |= (fprintf(fp, ",") < 0);
5433 err |= (fprintf(fp, "%s", buf) < 0);
5437 if (!first_cc_address) {
5438 err |= (fprintf(fp, "\n") < 0);
5441 return (err ? -1:0);
5444 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5446 gchar date[RFC822_DATE_BUFFSIZE];
5447 gchar buf[BUFFSIZE];
5449 const gchar *entstr;
5450 /* struct utsname utsbuf; */
5451 gboolean err = FALSE;
5453 cm_return_val_if_fail(fp != NULL, -1);
5454 cm_return_val_if_fail(compose->account != NULL, -1);
5455 cm_return_val_if_fail(compose->account->address != NULL, -1);
5458 if (prefs_common.hide_timezone)
5459 get_rfc822_date_hide_tz(date, sizeof(date));
5461 get_rfc822_date(date, sizeof(date));
5462 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5465 if (compose->account->name && *compose->account->name) {
5466 compose_convert_header
5467 (compose, buf, sizeof(buf), compose->account->name,
5468 strlen("From: "), TRUE);
5469 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5470 buf, compose->account->address) < 0);
5472 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5475 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5476 if (*entstr != '\0') {
5477 Xstrdup_a(str, entstr, return -1);
5480 compose_convert_header(compose, buf, sizeof(buf), str,
5481 strlen("Subject: "), FALSE);
5482 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5486 /* Resent-Message-ID */
5487 if (compose->account->gen_msgid) {
5488 gchar *addr = prefs_account_generate_msgid(compose->account);
5489 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5491 g_free(compose->msgid);
5492 compose->msgid = addr;
5494 compose->msgid = NULL;
5497 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5500 /* separator between header and body */
5501 err |= (fputs("\n", fp) == EOF);
5503 return (err ? -1:0);
5506 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5511 gchar rewrite_buf[BUFFSIZE];
5513 gboolean skip = FALSE;
5514 gboolean err = FALSE;
5515 gchar *not_included[]={
5516 "Return-Path:", "Delivered-To:", "Received:",
5517 "Subject:", "X-UIDL:", "AF:",
5518 "NF:", "PS:", "SRH:",
5519 "SFN:", "DSR:", "MID:",
5520 "CFG:", "PT:", "S:",
5521 "RQ:", "SSV:", "NSV:",
5522 "SSH:", "R:", "MAID:",
5523 "NAID:", "RMID:", "FMID:",
5524 "SCF:", "RRCPT:", "NG:",
5525 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5526 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5527 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5528 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5529 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5534 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5535 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5539 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5541 for (i = 0; not_included[i] != NULL; i++) {
5542 if (g_ascii_strncasecmp(buf, not_included[i],
5543 strlen(not_included[i])) == 0) {
5553 if (fputs(buf, fdest) == -1) {
5559 if (!prefs_common.redirect_keep_from) {
5560 if (g_ascii_strncasecmp(buf, "From:",
5561 strlen("From:")) == 0) {
5562 err |= (fputs(" (by way of ", fdest) == EOF);
5563 if (compose->account->name
5564 && *compose->account->name) {
5565 gchar buffer[BUFFSIZE];
5567 compose_convert_header
5568 (compose, buffer, sizeof(buffer),
5569 compose->account->name,
5572 err |= (fprintf(fdest, "%s <%s>",
5574 compose->account->address) < 0);
5576 err |= (fprintf(fdest, "%s",
5577 compose->account->address) < 0);
5578 err |= (fputs(")", fdest) == EOF);
5584 if (fputs("\n", fdest) == -1)
5591 if (compose_redirect_write_headers(compose, fdest))
5594 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5595 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5609 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5611 GtkTextBuffer *buffer;
5612 GtkTextIter start, end, tmp;
5613 gchar *chars, *tmp_enc_file, *content;
5615 const gchar *out_codeset;
5616 EncodingType encoding = ENC_UNKNOWN;
5617 MimeInfo *mimemsg, *mimetext;
5619 const gchar *src_codeset = CS_INTERNAL;
5620 gchar *from_addr = NULL;
5621 gchar *from_name = NULL;
5624 if (action == COMPOSE_WRITE_FOR_SEND) {
5625 attach_parts = TRUE;
5627 /* We're sending the message, generate a Message-ID
5629 if (compose->msgid == NULL &&
5630 compose->account->gen_msgid) {
5631 compose->msgid = prefs_account_generate_msgid(compose->account);
5635 /* create message MimeInfo */
5636 mimemsg = procmime_mimeinfo_new();
5637 mimemsg->type = MIMETYPE_MESSAGE;
5638 mimemsg->subtype = g_strdup("rfc822");
5639 mimemsg->content = MIMECONTENT_MEM;
5640 mimemsg->tmp = TRUE; /* must free content later */
5641 mimemsg->data.mem = compose_get_header(compose);
5643 /* Create text part MimeInfo */
5644 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5645 gtk_text_buffer_get_end_iter(buffer, &end);
5648 /* We make sure that there is a newline at the end. */
5649 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5650 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5651 if (*chars != '\n') {
5652 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5657 /* get all composed text */
5658 gtk_text_buffer_get_start_iter(buffer, &start);
5659 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5661 out_codeset = conv_get_charset_str(compose->out_encoding);
5663 if (!out_codeset && is_ascii_str(chars)) {
5664 out_codeset = CS_US_ASCII;
5665 } else if (prefs_common.outgoing_fallback_to_ascii &&
5666 is_ascii_str(chars)) {
5667 out_codeset = CS_US_ASCII;
5668 encoding = ENC_7BIT;
5672 gchar *test_conv_global_out = NULL;
5673 gchar *test_conv_reply = NULL;
5675 /* automatic mode. be automatic. */
5676 codeconv_set_strict(TRUE);
5678 out_codeset = conv_get_outgoing_charset_str();
5680 debug_print("trying to convert to %s\n", out_codeset);
5681 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5684 if (!test_conv_global_out && compose->orig_charset
5685 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5686 out_codeset = compose->orig_charset;
5687 debug_print("failure; trying to convert to %s\n", out_codeset);
5688 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5691 if (!test_conv_global_out && !test_conv_reply) {
5693 out_codeset = CS_INTERNAL;
5694 debug_print("failure; finally using %s\n", out_codeset);
5696 g_free(test_conv_global_out);
5697 g_free(test_conv_reply);
5698 codeconv_set_strict(FALSE);
5701 if (encoding == ENC_UNKNOWN) {
5702 if (prefs_common.encoding_method == CTE_BASE64)
5703 encoding = ENC_BASE64;
5704 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5705 encoding = ENC_QUOTED_PRINTABLE;
5706 else if (prefs_common.encoding_method == CTE_8BIT)
5707 encoding = ENC_8BIT;
5709 encoding = procmime_get_encoding_for_charset(out_codeset);
5712 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5713 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5715 if (action == COMPOSE_WRITE_FOR_SEND) {
5716 codeconv_set_strict(TRUE);
5717 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5718 codeconv_set_strict(FALSE);
5723 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5724 "to the specified %s charset.\n"
5725 "Send it as %s?"), out_codeset, src_codeset);
5726 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5727 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5731 if (aval != G_ALERTALTERNATE) {
5736 out_codeset = src_codeset;
5742 out_codeset = src_codeset;
5747 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5748 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5749 strstr(buf, "\nFrom ") != NULL) {
5750 encoding = ENC_QUOTED_PRINTABLE;
5754 mimetext = procmime_mimeinfo_new();
5755 mimetext->content = MIMECONTENT_MEM;
5756 mimetext->tmp = TRUE; /* must free content later */
5757 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5758 * and free the data, which we need later. */
5759 mimetext->data.mem = g_strdup(buf);
5760 mimetext->type = MIMETYPE_TEXT;
5761 mimetext->subtype = g_strdup("plain");
5762 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5763 g_strdup(out_codeset));
5765 /* protect trailing spaces when signing message */
5766 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5767 privacy_system_can_sign(compose->privacy_system)) {
5768 encoding = ENC_QUOTED_PRINTABLE;
5772 debug_print("main text: %Id bytes encoded as %s in %d\n",
5774 debug_print("main text: %zd bytes encoded as %s in %d\n",
5776 strlen(buf), out_codeset, encoding);
5778 /* check for line length limit */
5779 if (action == COMPOSE_WRITE_FOR_SEND &&
5780 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5781 check_line_length(buf, 1000, &line) < 0) {
5784 msg = g_strdup_printf
5785 (_("Line %d exceeds the line length limit (998 bytes).\n"
5786 "The contents of the message might be broken on the way to the delivery.\n"
5788 "Send it anyway?"), line + 1);
5789 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5792 if (aval != G_ALERTALTERNATE) {
5798 if (encoding != ENC_UNKNOWN)
5799 procmime_encode_content(mimetext, encoding);
5801 /* append attachment parts */
5802 if (compose_use_attach(compose) && attach_parts) {
5803 MimeInfo *mimempart;
5804 gchar *boundary = NULL;
5805 mimempart = procmime_mimeinfo_new();
5806 mimempart->content = MIMECONTENT_EMPTY;
5807 mimempart->type = MIMETYPE_MULTIPART;
5808 mimempart->subtype = g_strdup("mixed");
5812 boundary = generate_mime_boundary(NULL);
5813 } while (strstr(buf, boundary) != NULL);
5815 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5818 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5820 g_node_append(mimempart->node, mimetext->node);
5821 g_node_append(mimemsg->node, mimempart->node);
5823 if (compose_add_attachments(compose, mimempart) < 0)
5826 g_node_append(mimemsg->node, mimetext->node);
5830 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5831 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5832 /* extract name and address */
5833 if (strstr(spec, " <") && strstr(spec, ">")) {
5834 from_addr = g_strdup(strrchr(spec, '<')+1);
5835 *(strrchr(from_addr, '>')) = '\0';
5836 from_name = g_strdup(spec);
5837 *(strrchr(from_name, '<')) = '\0';
5844 /* sign message if sending */
5845 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5846 privacy_system_can_sign(compose->privacy_system))
5847 if (!privacy_sign(compose->privacy_system, mimemsg,
5848 compose->account, from_addr)) {
5856 if (compose->use_encryption) {
5857 if (compose->encdata != NULL &&
5858 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5860 /* First, write an unencrypted copy and save it to outbox, if
5861 * user wants that. */
5862 if (compose->account->save_encrypted_as_clear_text) {
5863 debug_print("saving sent message unencrypted...\n");
5864 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5868 /* fp now points to a file with headers written,
5869 * let's make a copy. */
5871 content = file_read_stream_to_str(fp);
5873 str_write_to_file(content, tmp_enc_file);
5876 /* Now write the unencrypted body. */
5877 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5878 procmime_write_mimeinfo(mimemsg, tmpfp);
5881 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5883 outbox = folder_get_default_outbox();
5885 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5886 claws_unlink(tmp_enc_file);
5888 g_warning("Can't open file '%s'", tmp_enc_file);
5891 g_warning("couldn't get tempfile");
5894 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5895 debug_print("Couldn't encrypt mime structure: %s.\n",
5896 privacy_get_error());
5897 alertpanel_error(_("Couldn't encrypt the email: %s"),
5898 privacy_get_error());
5903 procmime_write_mimeinfo(mimemsg, fp);
5905 procmime_mimeinfo_free_all(&mimemsg);
5910 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5912 GtkTextBuffer *buffer;
5913 GtkTextIter start, end;
5918 if ((fp = g_fopen(file, "wb")) == NULL) {
5919 FILE_OP_ERROR(file, "fopen");
5923 /* chmod for security */
5924 if (change_file_mode_rw(fp, file) < 0) {
5925 FILE_OP_ERROR(file, "chmod");
5926 g_warning("can't change file mode");
5929 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5930 gtk_text_buffer_get_start_iter(buffer, &start);
5931 gtk_text_buffer_get_end_iter(buffer, &end);
5932 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5934 chars = conv_codeset_strdup
5935 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5944 len = strlen(chars);
5945 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5946 FILE_OP_ERROR(file, "fwrite");
5955 if (fclose(fp) == EOF) {
5956 FILE_OP_ERROR(file, "fclose");
5963 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5966 MsgInfo *msginfo = compose->targetinfo;
5968 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5969 if (!msginfo) return -1;
5971 if (!force && MSG_IS_LOCKED(msginfo->flags))
5974 item = msginfo->folder;
5975 cm_return_val_if_fail(item != NULL, -1);
5977 if (procmsg_msg_exist(msginfo) &&
5978 (folder_has_parent_of_type(item, F_QUEUE) ||
5979 folder_has_parent_of_type(item, F_DRAFT)
5980 || msginfo == compose->autosaved_draft)) {
5981 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5982 g_warning("can't remove the old message");
5985 debug_print("removed reedit target %d\n", msginfo->msgnum);
5992 static void compose_remove_draft(Compose *compose)
5995 MsgInfo *msginfo = compose->targetinfo;
5996 drafts = account_get_special_folder(compose->account, F_DRAFT);
5998 if (procmsg_msg_exist(msginfo)) {
5999 folder_item_remove_msg(drafts, msginfo->msgnum);
6004 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6005 gboolean remove_reedit_target)
6007 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6010 static gboolean compose_warn_encryption(Compose *compose)
6012 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6013 AlertValue val = G_ALERTALTERNATE;
6015 if (warning == NULL)
6018 val = alertpanel_full(_("Encryption warning"), warning,
6019 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6020 TRUE, NULL, ALERT_WARNING);
6021 if (val & G_ALERTDISABLE) {
6022 val &= ~G_ALERTDISABLE;
6023 if (val == G_ALERTALTERNATE)
6024 privacy_inhibit_encrypt_warning(compose->privacy_system,
6028 if (val == G_ALERTALTERNATE) {
6035 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6036 gchar **msgpath, gboolean perform_checks,
6037 gboolean remove_reedit_target)
6044 PrefsAccount *mailac = NULL, *newsac = NULL;
6045 gboolean err = FALSE;
6047 debug_print("queueing message...\n");
6048 cm_return_val_if_fail(compose->account != NULL, -1);
6050 if (compose_check_entries(compose, perform_checks) == FALSE) {
6051 if (compose->batch) {
6052 gtk_widget_show_all(compose->window);
6057 if (!compose->to_list && !compose->newsgroup_list) {
6058 g_warning("can't get recipient list.");
6062 if (compose->to_list) {
6063 if (compose->account->protocol != A_NNTP)
6064 mailac = compose->account;
6065 else if (cur_account && cur_account->protocol != A_NNTP)
6066 mailac = cur_account;
6067 else if (!(mailac = compose_current_mail_account())) {
6068 alertpanel_error(_("No account for sending mails available!"));
6073 if (compose->newsgroup_list) {
6074 if (compose->account->protocol == A_NNTP)
6075 newsac = compose->account;
6077 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6082 /* write queue header */
6083 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6084 G_DIR_SEPARATOR, compose, (guint) rand());
6085 debug_print("queuing to %s\n", tmp);
6086 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6087 FILE_OP_ERROR(tmp, "fopen");
6092 if (change_file_mode_rw(fp, tmp) < 0) {
6093 FILE_OP_ERROR(tmp, "chmod");
6094 g_warning("can't change file mode");
6097 /* queueing variables */
6098 err |= (fprintf(fp, "AF:\n") < 0);
6099 err |= (fprintf(fp, "NF:0\n") < 0);
6100 err |= (fprintf(fp, "PS:10\n") < 0);
6101 err |= (fprintf(fp, "SRH:1\n") < 0);
6102 err |= (fprintf(fp, "SFN:\n") < 0);
6103 err |= (fprintf(fp, "DSR:\n") < 0);
6105 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6107 err |= (fprintf(fp, "MID:\n") < 0);
6108 err |= (fprintf(fp, "CFG:\n") < 0);
6109 err |= (fprintf(fp, "PT:0\n") < 0);
6110 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6111 err |= (fprintf(fp, "RQ:\n") < 0);
6113 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6115 err |= (fprintf(fp, "SSV:\n") < 0);
6117 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6119 err |= (fprintf(fp, "NSV:\n") < 0);
6120 err |= (fprintf(fp, "SSH:\n") < 0);
6121 /* write recepient list */
6122 if (compose->to_list) {
6123 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6124 for (cur = compose->to_list->next; cur != NULL;
6126 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6127 err |= (fprintf(fp, "\n") < 0);
6129 /* write newsgroup list */
6130 if (compose->newsgroup_list) {
6131 err |= (fprintf(fp, "NG:") < 0);
6132 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6133 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6134 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6135 err |= (fprintf(fp, "\n") < 0);
6137 /* Sylpheed account IDs */
6139 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6141 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6144 if (compose->privacy_system != NULL) {
6145 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6146 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6147 if (compose->use_encryption) {
6148 if (!compose_warn_encryption(compose)) {
6154 if (mailac && mailac->encrypt_to_self) {
6155 GSList *tmp_list = g_slist_copy(compose->to_list);
6156 tmp_list = g_slist_append(tmp_list, compose->account->address);
6157 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6158 g_slist_free(tmp_list);
6160 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6162 if (compose->encdata != NULL) {
6163 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6164 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6165 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6166 compose->encdata) < 0);
6167 } /* else we finally dont want to encrypt */
6169 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6170 /* and if encdata was null, it means there's been a problem in
6173 g_warning("failed to write queue message");
6182 /* Save copy folder */
6183 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6184 gchar *savefolderid;
6186 savefolderid = compose_get_save_to(compose);
6187 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6188 g_free(savefolderid);
6190 /* Save copy folder */
6191 if (compose->return_receipt) {
6192 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6194 /* Message-ID of message replying to */
6195 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6196 gchar *folderid = NULL;
6198 if (compose->replyinfo->folder)
6199 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6200 if (folderid == NULL)
6201 folderid = g_strdup("NULL");
6203 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6206 /* Message-ID of message forwarding to */
6207 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6208 gchar *folderid = NULL;
6210 if (compose->fwdinfo->folder)
6211 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6212 if (folderid == NULL)
6213 folderid = g_strdup("NULL");
6215 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6219 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6220 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6222 /* end of headers */
6223 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6225 if (compose->redirect_filename != NULL) {
6226 if (compose_redirect_write_to_file(compose, fp) < 0) {
6234 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6238 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6242 g_warning("failed to write queue message");
6248 if (fclose(fp) == EOF) {
6249 FILE_OP_ERROR(tmp, "fclose");
6255 if (item && *item) {
6258 queue = account_get_special_folder(compose->account, F_QUEUE);
6261 g_warning("can't find queue folder");
6266 folder_item_scan(queue);
6267 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6268 g_warning("can't queue the message");
6274 if (msgpath == NULL) {
6280 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6281 compose_remove_reedit_target(compose, FALSE);
6284 if ((msgnum != NULL) && (item != NULL)) {
6292 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6295 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6300 GError *error = NULL;
6305 gchar *type, *subtype;
6306 GtkTreeModel *model;
6309 model = gtk_tree_view_get_model(tree_view);
6311 if (!gtk_tree_model_get_iter_first(model, &iter))
6314 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6316 if (!is_file_exist(ainfo->file)) {
6317 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6318 AlertValue val = alertpanel_full(_("Warning"), msg,
6319 _("Cancel sending"), _("Ignore attachment"), NULL,
6320 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6322 if (val == G_ALERTDEFAULT) {
6328 f = g_file_new_for_path(ainfo->file);
6329 fi = g_file_query_info(f, "standard::size",
6330 G_FILE_QUERY_INFO_NONE, NULL, &error);
6331 if (error != NULL) {
6332 g_warning(error->message);
6333 g_error_free(error);
6337 size = g_file_info_get_size(fi);
6341 if (g_stat(ainfo->file, &statbuf) < 0)
6343 size = statbuf.st_size;
6346 mimepart = procmime_mimeinfo_new();
6347 mimepart->content = MIMECONTENT_FILE;
6348 mimepart->data.filename = g_strdup(ainfo->file);
6349 mimepart->tmp = FALSE; /* or we destroy our attachment */
6350 mimepart->offset = 0;
6351 mimepart->length = size;
6353 type = g_strdup(ainfo->content_type);
6355 if (!strchr(type, '/')) {
6357 type = g_strdup("application/octet-stream");
6360 subtype = strchr(type, '/') + 1;
6361 *(subtype - 1) = '\0';
6362 mimepart->type = procmime_get_media_type(type);
6363 mimepart->subtype = g_strdup(subtype);
6366 if (mimepart->type == MIMETYPE_MESSAGE &&
6367 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6368 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6369 } else if (mimepart->type == MIMETYPE_TEXT) {
6370 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6371 /* Text parts with no name come from multipart/alternative
6372 * forwards. Make sure the recipient won't look at the
6373 * original HTML part by mistake. */
6374 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6375 ainfo->name = g_strdup_printf(_("Original %s part"),
6379 g_hash_table_insert(mimepart->typeparameters,
6380 g_strdup("charset"), g_strdup(ainfo->charset));
6382 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6383 if (mimepart->type == MIMETYPE_APPLICATION &&
6384 !strcmp2(mimepart->subtype, "octet-stream"))
6385 g_hash_table_insert(mimepart->typeparameters,
6386 g_strdup("name"), g_strdup(ainfo->name));
6387 g_hash_table_insert(mimepart->dispositionparameters,
6388 g_strdup("filename"), g_strdup(ainfo->name));
6389 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6392 if (mimepart->type == MIMETYPE_MESSAGE
6393 || mimepart->type == MIMETYPE_MULTIPART)
6394 ainfo->encoding = ENC_BINARY;
6395 else if (compose->use_signing) {
6396 if (ainfo->encoding == ENC_7BIT)
6397 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6398 else if (ainfo->encoding == ENC_8BIT)
6399 ainfo->encoding = ENC_BASE64;
6402 procmime_encode_content(mimepart, ainfo->encoding);
6404 g_node_append(parent->node, mimepart->node);
6405 } while (gtk_tree_model_iter_next(model, &iter));
6410 static gchar *compose_quote_list_of_addresses(gchar *str)
6412 GSList *list = NULL, *item = NULL;
6413 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6415 list = address_list_append_with_comments(list, str);
6416 for (item = list; item != NULL; item = item->next) {
6417 gchar *spec = item->data;
6418 gchar *endofname = strstr(spec, " <");
6419 if (endofname != NULL) {
6422 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6423 qqname = escape_internal_quotes(qname, '"');
6425 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6426 gchar *addr = g_strdup(endofname);
6427 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6428 faddr = g_strconcat(name, addr, NULL);
6431 debug_print("new auto-quoted address: '%s'\n", faddr);
6435 result = g_strdup((faddr != NULL)? faddr: spec);
6437 result = g_strconcat(result,
6439 (faddr != NULL)? faddr: spec,
6442 if (faddr != NULL) {
6447 slist_free_strings_full(list);
6452 #define IS_IN_CUSTOM_HEADER(header) \
6453 (compose->account->add_customhdr && \
6454 custom_header_find(compose->account->customhdr_list, header) != NULL)
6456 static const gchar *compose_untranslated_header_name(gchar *header_name)
6458 /* return the untranslated header name, if header_name is a known
6459 header name, in either its translated or untranslated form, with
6460 or without trailing colon. otherwise, returns header_name. */
6461 gchar *translated_header_name;
6462 gchar *translated_header_name_wcolon;
6463 const gchar *untranslated_header_name;
6464 const gchar *untranslated_header_name_wcolon;
6467 cm_return_val_if_fail(header_name != NULL, NULL);
6469 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6470 untranslated_header_name = HEADERS[i].header_name;
6471 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6473 translated_header_name = gettext(untranslated_header_name);
6474 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6476 if (!strcmp(header_name, untranslated_header_name) ||
6477 !strcmp(header_name, translated_header_name)) {
6478 return untranslated_header_name;
6480 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6481 !strcmp(header_name, translated_header_name_wcolon)) {
6482 return untranslated_header_name_wcolon;
6486 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6490 static void compose_add_headerfield_from_headerlist(Compose *compose,
6492 const gchar *fieldname,
6493 const gchar *seperator)
6495 gchar *str, *fieldname_w_colon;
6496 gboolean add_field = FALSE;
6498 ComposeHeaderEntry *headerentry;
6499 const gchar *headerentryname;
6500 const gchar *trans_fieldname;
6503 if (IS_IN_CUSTOM_HEADER(fieldname))
6506 debug_print("Adding %s-fields\n", fieldname);
6508 fieldstr = g_string_sized_new(64);
6510 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6511 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6513 for (list = compose->header_list; list; list = list->next) {
6514 headerentry = ((ComposeHeaderEntry *)list->data);
6515 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6517 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6518 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6520 str = compose_quote_list_of_addresses(ustr);
6522 if (str != NULL && str[0] != '\0') {
6524 g_string_append(fieldstr, seperator);
6525 g_string_append(fieldstr, str);
6534 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6535 compose_convert_header
6536 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6537 strlen(fieldname) + 2, TRUE);
6538 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6542 g_free(fieldname_w_colon);
6543 g_string_free(fieldstr, TRUE);
6548 static gchar *compose_get_manual_headers_info(Compose *compose)
6550 GString *sh_header = g_string_new(" ");
6552 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6554 for (list = compose->header_list; list; list = list->next) {
6555 ComposeHeaderEntry *headerentry;
6558 gchar *headername_wcolon;
6559 const gchar *headername_trans;
6561 gboolean standard_header = FALSE;
6563 headerentry = ((ComposeHeaderEntry *)list->data);
6565 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6567 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6572 if (!strstr(tmp, ":")) {
6573 headername_wcolon = g_strconcat(tmp, ":", NULL);
6574 headername = g_strdup(tmp);
6576 headername_wcolon = g_strdup(tmp);
6577 headername = g_strdup(strtok(tmp, ":"));
6581 string = std_headers;
6582 while (*string != NULL) {
6583 headername_trans = prefs_common_translated_header_name(*string);
6584 if (!strcmp(headername_trans, headername_wcolon))
6585 standard_header = TRUE;
6588 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6589 g_string_append_printf(sh_header, "%s ", headername);
6591 g_free(headername_wcolon);
6593 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6594 return g_string_free(sh_header, FALSE);
6597 static gchar *compose_get_header(Compose *compose)
6599 gchar date[RFC822_DATE_BUFFSIZE];
6600 gchar buf[BUFFSIZE];
6601 const gchar *entry_str;
6605 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6607 gchar *from_name = NULL, *from_address = NULL;
6610 cm_return_val_if_fail(compose->account != NULL, NULL);
6611 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6613 header = g_string_sized_new(64);
6616 if (prefs_common.hide_timezone)
6617 get_rfc822_date_hide_tz(date, sizeof(date));
6619 get_rfc822_date(date, sizeof(date));
6620 g_string_append_printf(header, "Date: %s\n", date);
6624 if (compose->account->name && *compose->account->name) {
6626 QUOTE_IF_REQUIRED(buf, compose->account->name);
6627 tmp = g_strdup_printf("%s <%s>",
6628 buf, compose->account->address);
6630 tmp = g_strdup_printf("%s",
6631 compose->account->address);
6633 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6634 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6636 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6637 from_address = g_strdup(compose->account->address);
6639 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6640 /* extract name and address */
6641 if (strstr(spec, " <") && strstr(spec, ">")) {
6642 from_address = g_strdup(strrchr(spec, '<')+1);
6643 *(strrchr(from_address, '>')) = '\0';
6644 from_name = g_strdup(spec);
6645 *(strrchr(from_name, '<')) = '\0';
6648 from_address = g_strdup(spec);
6655 if (from_name && *from_name) {
6657 compose_convert_header
6658 (compose, buf, sizeof(buf), from_name,
6659 strlen("From: "), TRUE);
6660 QUOTE_IF_REQUIRED(name, buf);
6661 qname = escape_internal_quotes(name, '"');
6663 g_string_append_printf(header, "From: %s <%s>\n",
6664 qname, from_address);
6665 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6666 compose->return_receipt) {
6667 compose_convert_header(compose, buf, sizeof(buf), from_name,
6668 strlen("Disposition-Notification-To: "),
6670 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6675 g_string_append_printf(header, "From: %s\n", from_address);
6676 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6677 compose->return_receipt)
6678 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6682 g_free(from_address);
6685 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6688 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6691 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6695 * If this account is a NNTP account remove Bcc header from
6696 * message body since it otherwise will be publicly shown
6698 if (compose->account->protocol != A_NNTP)
6699 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6702 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6704 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6707 compose_convert_header(compose, buf, sizeof(buf), str,
6708 strlen("Subject: "), FALSE);
6709 g_string_append_printf(header, "Subject: %s\n", buf);
6715 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6716 g_string_append_printf(header, "Message-ID: <%s>\n",
6720 if (compose->remove_references == FALSE) {
6722 if (compose->inreplyto && compose->to_list)
6723 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6726 if (compose->references)
6727 g_string_append_printf(header, "References: %s\n", compose->references);
6731 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6734 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6737 if (compose->account->organization &&
6738 strlen(compose->account->organization) &&
6739 !IS_IN_CUSTOM_HEADER("Organization")) {
6740 compose_convert_header(compose, buf, sizeof(buf),
6741 compose->account->organization,
6742 strlen("Organization: "), FALSE);
6743 g_string_append_printf(header, "Organization: %s\n", buf);
6746 /* Program version and system info */
6747 if (compose->account->gen_xmailer &&
6748 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6749 !compose->newsgroup_list) {
6750 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6752 gtk_major_version, gtk_minor_version, gtk_micro_version,
6755 if (compose->account->gen_xmailer &&
6756 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6757 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6759 gtk_major_version, gtk_minor_version, gtk_micro_version,
6763 /* custom headers */
6764 if (compose->account->add_customhdr) {
6767 for (cur = compose->account->customhdr_list; cur != NULL;
6769 CustomHeader *chdr = (CustomHeader *)cur->data;
6771 if (custom_header_is_allowed(chdr->name)
6772 && chdr->value != NULL
6773 && *(chdr->value) != '\0') {
6774 compose_convert_header
6775 (compose, buf, sizeof(buf),
6777 strlen(chdr->name) + 2, FALSE);
6778 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6783 /* Automatic Faces and X-Faces */
6784 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6785 g_string_append_printf(header, "X-Face: %s\n", buf);
6787 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6788 g_string_append_printf(header, "X-Face: %s\n", buf);
6790 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6791 g_string_append_printf(header, "Face: %s\n", buf);
6793 else if (get_default_face (buf, sizeof(buf)) == 0) {
6794 g_string_append_printf(header, "Face: %s\n", buf);
6798 switch (compose->priority) {
6799 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6800 "X-Priority: 1 (Highest)\n");
6802 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6803 "X-Priority: 2 (High)\n");
6805 case PRIORITY_NORMAL: break;
6806 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6807 "X-Priority: 4 (Low)\n");
6809 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6810 "X-Priority: 5 (Lowest)\n");
6812 default: debug_print("compose: priority unknown : %d\n",
6816 /* get special headers */
6817 for (list = compose->header_list; list; list = list->next) {
6818 ComposeHeaderEntry *headerentry;
6821 gchar *headername_wcolon;
6822 const gchar *headername_trans;
6825 gboolean standard_header = FALSE;
6827 headerentry = ((ComposeHeaderEntry *)list->data);
6829 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6831 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6836 if (!strstr(tmp, ":")) {
6837 headername_wcolon = g_strconcat(tmp, ":", NULL);
6838 headername = g_strdup(tmp);
6840 headername_wcolon = g_strdup(tmp);
6841 headername = g_strdup(strtok(tmp, ":"));
6845 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6846 Xstrdup_a(headervalue, entry_str, return NULL);
6847 subst_char(headervalue, '\r', ' ');
6848 subst_char(headervalue, '\n', ' ');
6849 g_strstrip(headervalue);
6850 if (*headervalue != '\0') {
6851 string = std_headers;
6852 while (*string != NULL && !standard_header) {
6853 headername_trans = prefs_common_translated_header_name(*string);
6854 /* support mixed translated and untranslated headers */
6855 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6856 standard_header = TRUE;
6859 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6860 /* store untranslated header name */
6861 g_string_append_printf(header, "%s %s\n",
6862 compose_untranslated_header_name(headername_wcolon), headervalue);
6866 g_free(headername_wcolon);
6870 g_string_free(header, FALSE);
6875 #undef IS_IN_CUSTOM_HEADER
6877 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6878 gint header_len, gboolean addr_field)
6880 gchar *tmpstr = NULL;
6881 const gchar *out_codeset = NULL;
6883 cm_return_if_fail(src != NULL);
6884 cm_return_if_fail(dest != NULL);
6886 if (len < 1) return;
6888 tmpstr = g_strdup(src);
6890 subst_char(tmpstr, '\n', ' ');
6891 subst_char(tmpstr, '\r', ' ');
6894 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6895 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6896 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6901 codeconv_set_strict(TRUE);
6902 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6903 conv_get_charset_str(compose->out_encoding));
6904 codeconv_set_strict(FALSE);
6906 if (!dest || *dest == '\0') {
6907 gchar *test_conv_global_out = NULL;
6908 gchar *test_conv_reply = NULL;
6910 /* automatic mode. be automatic. */
6911 codeconv_set_strict(TRUE);
6913 out_codeset = conv_get_outgoing_charset_str();
6915 debug_print("trying to convert to %s\n", out_codeset);
6916 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6919 if (!test_conv_global_out && compose->orig_charset
6920 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6921 out_codeset = compose->orig_charset;
6922 debug_print("failure; trying to convert to %s\n", out_codeset);
6923 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6926 if (!test_conv_global_out && !test_conv_reply) {
6928 out_codeset = CS_INTERNAL;
6929 debug_print("finally using %s\n", out_codeset);
6931 g_free(test_conv_global_out);
6932 g_free(test_conv_reply);
6933 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6935 codeconv_set_strict(FALSE);
6940 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6944 cm_return_if_fail(user_data != NULL);
6946 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6947 g_strstrip(address);
6948 if (*address != '\0') {
6949 gchar *name = procheader_get_fromname(address);
6950 extract_address(address);
6951 #ifndef USE_ALT_ADDRBOOK
6952 addressbook_add_contact(name, address, NULL, NULL);
6954 debug_print("%s: %s\n", name, address);
6955 if (addressadd_selection(name, address, NULL, NULL)) {
6956 debug_print( "addressbook_add_contact - added\n" );
6963 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6965 GtkWidget *menuitem;
6968 cm_return_if_fail(menu != NULL);
6969 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6971 menuitem = gtk_separator_menu_item_new();
6972 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6973 gtk_widget_show(menuitem);
6975 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6976 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6978 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6979 g_strstrip(address);
6980 if (*address == '\0') {
6981 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6984 g_signal_connect(G_OBJECT(menuitem), "activate",
6985 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6986 gtk_widget_show(menuitem);
6989 void compose_add_extra_header(gchar *header, GtkListStore *model)
6992 if (strcmp(header, "")) {
6993 COMBOBOX_ADD(model, header, COMPOSE_TO);
6997 void compose_add_extra_header_entries(GtkListStore *model)
7001 gchar buf[BUFFSIZE];
7004 if (extra_headers == NULL) {
7005 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7006 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7007 debug_print("extra headers file not found\n");
7008 goto extra_headers_done;
7010 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7011 lastc = strlen(buf) - 1; /* remove trailing control chars */
7012 while (lastc >= 0 && buf[lastc] != ':')
7013 buf[lastc--] = '\0';
7014 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7015 buf[lastc] = '\0'; /* remove trailing : for comparison */
7016 if (custom_header_is_allowed(buf)) {
7018 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7021 g_message("disallowed extra header line: %s\n", buf);
7025 g_message("invalid extra header line: %s\n", buf);
7031 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7032 extra_headers = g_slist_reverse(extra_headers);
7034 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7038 static void _ldap_srv_func(gpointer data, gpointer user_data)
7040 LdapServer *server = (LdapServer *)data;
7041 gboolean *enable = (gboolean *)user_data;
7043 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7044 server->searchFlag = *enable;
7048 static void compose_create_header_entry(Compose *compose)
7050 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7057 const gchar *header = NULL;
7058 ComposeHeaderEntry *headerentry;
7059 gboolean standard_header = FALSE;
7060 GtkListStore *model;
7063 headerentry = g_new0(ComposeHeaderEntry, 1);
7065 /* Combo box model */
7066 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7067 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7069 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7071 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7073 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7074 COMPOSE_NEWSGROUPS);
7075 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7077 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7078 COMPOSE_FOLLOWUPTO);
7079 compose_add_extra_header_entries(model);
7082 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7083 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7084 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7085 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7086 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7087 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7088 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7089 G_CALLBACK(compose_grab_focus_cb), compose);
7090 gtk_widget_show(combo);
7092 /* Putting only the combobox child into focus chain of its parent causes
7093 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7094 * This eliminates need to pres Tab twice in order to really get from the
7095 * combobox to next widget. */
7097 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7098 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7101 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7102 compose->header_nextrow, compose->header_nextrow+1,
7103 GTK_SHRINK, GTK_FILL, 0, 0);
7104 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7105 const gchar *last_header_entry = gtk_entry_get_text(
7106 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7108 while (*string != NULL) {
7109 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7110 standard_header = TRUE;
7113 if (standard_header)
7114 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7116 if (!compose->header_last || !standard_header) {
7117 switch(compose->account->protocol) {
7119 header = prefs_common_translated_header_name("Newsgroups:");
7122 header = prefs_common_translated_header_name("To:");
7127 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7129 gtk_editable_set_editable(
7130 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7131 prefs_common.type_any_header);
7133 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7134 G_CALLBACK(compose_grab_focus_cb), compose);
7136 /* Entry field with cleanup button */
7137 button = gtk_button_new();
7138 gtk_button_set_image(GTK_BUTTON(button),
7139 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7140 gtk_widget_show(button);
7141 CLAWS_SET_TIP(button,
7142 _("Delete entry contents"));
7143 entry = gtk_entry_new();
7144 gtk_widget_show(entry);
7145 CLAWS_SET_TIP(entry,
7146 _("Use <tab> to autocomplete from addressbook"));
7147 hbox = gtk_hbox_new (FALSE, 0);
7148 gtk_widget_show(hbox);
7149 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7150 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7151 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7152 compose->header_nextrow, compose->header_nextrow+1,
7153 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7155 g_signal_connect(G_OBJECT(entry), "key-press-event",
7156 G_CALLBACK(compose_headerentry_key_press_event_cb),
7158 g_signal_connect(G_OBJECT(entry), "changed",
7159 G_CALLBACK(compose_headerentry_changed_cb),
7161 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7162 G_CALLBACK(compose_grab_focus_cb), compose);
7164 g_signal_connect(G_OBJECT(button), "clicked",
7165 G_CALLBACK(compose_headerentry_button_clicked_cb),
7169 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7170 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7171 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7172 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7173 G_CALLBACK(compose_header_drag_received_cb),
7175 g_signal_connect(G_OBJECT(entry), "drag-drop",
7176 G_CALLBACK(compose_drag_drop),
7178 g_signal_connect(G_OBJECT(entry), "populate-popup",
7179 G_CALLBACK(compose_entry_popup_extend),
7183 #ifndef PASSWORD_CRYPTO_OLD
7184 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7185 if (pwd_servers != NULL && master_passphrase() == NULL) {
7186 gboolean enable = FALSE;
7187 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7188 /* Temporarily disable password-protected LDAP servers,
7189 * because user did not provide a master passphrase.
7190 * We can safely enable searchFlag on all servers in this list
7191 * later, since addrindex_get_password_protected_ldap_servers()
7192 * includes servers which have it enabled initially. */
7193 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7194 compose->passworded_ldap_servers = pwd_servers;
7196 #endif /* PASSWORD_CRYPTO_OLD */
7197 #endif /* USE_LDAP */
7199 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7201 headerentry->compose = compose;
7202 headerentry->combo = combo;
7203 headerentry->entry = entry;
7204 headerentry->button = button;
7205 headerentry->hbox = hbox;
7206 headerentry->headernum = compose->header_nextrow;
7207 headerentry->type = PREF_NONE;
7209 compose->header_nextrow++;
7210 compose->header_last = headerentry;
7211 compose->header_list =
7212 g_slist_append(compose->header_list,
7216 static void compose_add_header_entry(Compose *compose, const gchar *header,
7217 gchar *text, ComposePrefType pref_type)
7219 ComposeHeaderEntry *last_header = compose->header_last;
7220 gchar *tmp = g_strdup(text), *email;
7221 gboolean replyto_hdr;
7223 replyto_hdr = (!strcasecmp(header,
7224 prefs_common_translated_header_name("Reply-To:")) ||
7226 prefs_common_translated_header_name("Followup-To:")) ||
7228 prefs_common_translated_header_name("In-Reply-To:")));
7230 extract_address(tmp);
7231 email = g_utf8_strdown(tmp, -1);
7233 if (replyto_hdr == FALSE &&
7234 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7236 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7237 header, text, (gint) pref_type);
7243 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7244 gtk_entry_set_text(GTK_ENTRY(
7245 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7247 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7248 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7249 last_header->type = pref_type;
7251 if (replyto_hdr == FALSE)
7252 g_hash_table_insert(compose->email_hashtable, email,
7253 GUINT_TO_POINTER(1));
7260 static void compose_destroy_headerentry(Compose *compose,
7261 ComposeHeaderEntry *headerentry)
7263 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7266 extract_address(text);
7267 email = g_utf8_strdown(text, -1);
7268 g_hash_table_remove(compose->email_hashtable, email);
7272 gtk_widget_destroy(headerentry->combo);
7273 gtk_widget_destroy(headerentry->entry);
7274 gtk_widget_destroy(headerentry->button);
7275 gtk_widget_destroy(headerentry->hbox);
7276 g_free(headerentry);
7279 static void compose_remove_header_entries(Compose *compose)
7282 for (list = compose->header_list; list; list = list->next)
7283 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7285 compose->header_last = NULL;
7286 g_slist_free(compose->header_list);
7287 compose->header_list = NULL;
7288 compose->header_nextrow = 1;
7289 compose_create_header_entry(compose);
7292 static GtkWidget *compose_create_header(Compose *compose)
7294 GtkWidget *from_optmenu_hbox;
7295 GtkWidget *header_table_main;
7296 GtkWidget *header_scrolledwin;
7297 GtkWidget *header_table;
7299 /* parent with account selection and from header */
7300 header_table_main = gtk_table_new(2, 2, FALSE);
7301 gtk_widget_show(header_table_main);
7302 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7304 from_optmenu_hbox = compose_account_option_menu_create(compose);
7305 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7306 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7308 /* child with header labels and entries */
7309 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7310 gtk_widget_show(header_scrolledwin);
7311 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7313 header_table = gtk_table_new(2, 2, FALSE);
7314 gtk_widget_show(header_table);
7315 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7316 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7317 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7318 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7319 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7321 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7322 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7324 compose->header_table = header_table;
7325 compose->header_list = NULL;
7326 compose->header_nextrow = 0;
7328 compose_create_header_entry(compose);
7330 compose->table = NULL;
7332 return header_table_main;
7335 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7337 Compose *compose = (Compose *)data;
7338 GdkEventButton event;
7341 event.time = gtk_get_current_event_time();
7343 return attach_button_pressed(compose->attach_clist, &event, compose);
7346 static GtkWidget *compose_create_attach(Compose *compose)
7348 GtkWidget *attach_scrwin;
7349 GtkWidget *attach_clist;
7351 GtkListStore *store;
7352 GtkCellRenderer *renderer;
7353 GtkTreeViewColumn *column;
7354 GtkTreeSelection *selection;
7356 /* attachment list */
7357 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7358 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7359 GTK_POLICY_AUTOMATIC,
7360 GTK_POLICY_AUTOMATIC);
7361 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7363 store = gtk_list_store_new(N_ATTACH_COLS,
7369 G_TYPE_AUTO_POINTER,
7371 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7372 (GTK_TREE_MODEL(store)));
7373 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7374 g_object_unref(store);
7376 renderer = gtk_cell_renderer_text_new();
7377 column = gtk_tree_view_column_new_with_attributes
7378 (_("Mime type"), renderer, "text",
7379 COL_MIMETYPE, NULL);
7380 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7382 renderer = gtk_cell_renderer_text_new();
7383 column = gtk_tree_view_column_new_with_attributes
7384 (_("Size"), renderer, "text",
7386 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7388 renderer = gtk_cell_renderer_text_new();
7389 column = gtk_tree_view_column_new_with_attributes
7390 (_("Name"), renderer, "text",
7392 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7394 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7395 prefs_common.use_stripes_everywhere);
7396 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7397 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7399 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7400 G_CALLBACK(attach_selected), compose);
7401 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7402 G_CALLBACK(attach_button_pressed), compose);
7403 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7404 G_CALLBACK(popup_attach_button_pressed), compose);
7405 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7406 G_CALLBACK(attach_key_pressed), compose);
7409 gtk_drag_dest_set(attach_clist,
7410 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7411 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7412 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7413 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7414 G_CALLBACK(compose_attach_drag_received_cb),
7416 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7417 G_CALLBACK(compose_drag_drop),
7420 compose->attach_scrwin = attach_scrwin;
7421 compose->attach_clist = attach_clist;
7423 return attach_scrwin;
7426 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7427 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7429 static GtkWidget *compose_create_others(Compose *compose)
7432 GtkWidget *savemsg_checkbtn;
7433 GtkWidget *savemsg_combo;
7434 GtkWidget *savemsg_select;
7437 gchar *folderidentifier;
7439 /* Table for settings */
7440 table = gtk_table_new(3, 1, FALSE);
7441 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7442 gtk_widget_show(table);
7443 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7446 /* Save Message to folder */
7447 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7448 gtk_widget_show(savemsg_checkbtn);
7449 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7450 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7451 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7453 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7454 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7456 savemsg_combo = gtk_combo_box_text_new_with_entry();
7457 compose->savemsg_checkbtn = savemsg_checkbtn;
7458 compose->savemsg_combo = savemsg_combo;
7459 gtk_widget_show(savemsg_combo);
7461 if (prefs_common.compose_save_to_history)
7462 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7463 prefs_common.compose_save_to_history);
7464 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7465 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7466 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7467 G_CALLBACK(compose_grab_focus_cb), compose);
7468 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7469 folderidentifier = folder_item_get_identifier(account_get_special_folder
7470 (compose->account, F_OUTBOX));
7471 compose_set_save_to(compose, folderidentifier);
7472 g_free(folderidentifier);
7475 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7476 gtk_widget_show(savemsg_select);
7477 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7478 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7479 G_CALLBACK(compose_savemsg_select_cb),
7485 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7487 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7488 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7491 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7496 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7497 _("Select folder to save message to"));
7500 path = folder_item_get_identifier(dest);
7502 compose_set_save_to(compose, path);
7506 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7507 GdkAtom clip, GtkTextIter *insert_place);
7510 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7514 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7516 if (event->button == 3) {
7518 GtkTextIter sel_start, sel_end;
7519 gboolean stuff_selected;
7521 /* move the cursor to allow GtkAspell to check the word
7522 * under the mouse */
7523 if (event->x && event->y) {
7524 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7525 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7527 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7530 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7531 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7534 stuff_selected = gtk_text_buffer_get_selection_bounds(
7536 &sel_start, &sel_end);
7538 gtk_text_buffer_place_cursor (buffer, &iter);
7539 /* reselect stuff */
7541 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7542 gtk_text_buffer_select_range(buffer,
7543 &sel_start, &sel_end);
7545 return FALSE; /* pass the event so that the right-click goes through */
7548 if (event->button == 2) {
7553 /* get the middle-click position to paste at the correct place */
7554 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7555 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7557 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7560 entry_paste_clipboard(compose, text,
7561 prefs_common.linewrap_pastes,
7562 GDK_SELECTION_PRIMARY, &iter);
7570 static void compose_spell_menu_changed(void *data)
7572 Compose *compose = (Compose *)data;
7574 GtkWidget *menuitem;
7575 GtkWidget *parent_item;
7576 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7579 if (compose->gtkaspell == NULL)
7582 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7583 "/Menu/Spelling/Options");
7585 /* setting the submenu removes /Spelling/Options from the factory
7586 * so we need to save it */
7588 if (parent_item == NULL) {
7589 parent_item = compose->aspell_options_menu;
7590 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7592 compose->aspell_options_menu = parent_item;
7594 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7596 spell_menu = g_slist_reverse(spell_menu);
7597 for (items = spell_menu;
7598 items; items = items->next) {
7599 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7600 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7601 gtk_widget_show(GTK_WIDGET(menuitem));
7603 g_slist_free(spell_menu);
7605 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7606 gtk_widget_show(parent_item);
7609 static void compose_dict_changed(void *data)
7611 Compose *compose = (Compose *) data;
7613 if(!compose->gtkaspell)
7615 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7618 gtkaspell_highlight_all(compose->gtkaspell);
7619 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7623 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7625 Compose *compose = (Compose *)data;
7626 GdkEventButton event;
7629 event.time = gtk_get_current_event_time();
7633 return text_clicked(compose->text, &event, compose);
7636 static gboolean compose_force_window_origin = TRUE;
7637 static Compose *compose_create(PrefsAccount *account,
7646 GtkWidget *handlebox;
7648 GtkWidget *notebook;
7650 GtkWidget *attach_hbox;
7651 GtkWidget *attach_lab1;
7652 GtkWidget *attach_lab2;
7657 GtkWidget *subject_hbox;
7658 GtkWidget *subject_frame;
7659 GtkWidget *subject_entry;
7663 GtkWidget *edit_vbox;
7664 GtkWidget *ruler_hbox;
7666 GtkWidget *scrolledwin;
7668 GtkTextBuffer *buffer;
7669 GtkClipboard *clipboard;
7671 UndoMain *undostruct;
7673 GtkWidget *popupmenu;
7674 GtkWidget *tmpl_menu;
7675 GtkActionGroup *action_group = NULL;
7678 GtkAspell * gtkaspell = NULL;
7681 static GdkGeometry geometry;
7683 cm_return_val_if_fail(account != NULL, NULL);
7685 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7686 &default_header_bgcolor);
7687 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7688 &default_header_color);
7690 debug_print("Creating compose window...\n");
7691 compose = g_new0(Compose, 1);
7693 compose->batch = batch;
7694 compose->account = account;
7695 compose->folder = folder;
7697 compose->mutex = cm_mutex_new();
7698 compose->set_cursor_pos = -1;
7700 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7702 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7703 gtk_widget_set_size_request(window, prefs_common.compose_width,
7704 prefs_common.compose_height);
7706 if (!geometry.max_width) {
7707 geometry.max_width = gdk_screen_width();
7708 geometry.max_height = gdk_screen_height();
7711 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7712 &geometry, GDK_HINT_MAX_SIZE);
7713 if (!geometry.min_width) {
7714 geometry.min_width = 600;
7715 geometry.min_height = 440;
7717 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7718 &geometry, GDK_HINT_MIN_SIZE);
7720 #ifndef GENERIC_UMPC
7721 if (compose_force_window_origin)
7722 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7723 prefs_common.compose_y);
7725 g_signal_connect(G_OBJECT(window), "delete_event",
7726 G_CALLBACK(compose_delete_cb), compose);
7727 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7728 gtk_widget_realize(window);
7730 gtkut_widget_set_composer_icon(window);
7732 vbox = gtk_vbox_new(FALSE, 0);
7733 gtk_container_add(GTK_CONTAINER(window), vbox);
7735 compose->ui_manager = gtk_ui_manager_new();
7736 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7737 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7738 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7739 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7740 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7741 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7742 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7743 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7744 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7745 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7762 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7766 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7768 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7864 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)
7865 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)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7871 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)
7872 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)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7877 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)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7881 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)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7887 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)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7894 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)
7895 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)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7908 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)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7926 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7927 gtk_widget_show_all(menubar);
7929 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7930 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7932 if (prefs_common.toolbar_detachable) {
7933 handlebox = gtk_handle_box_new();
7935 handlebox = gtk_hbox_new(FALSE, 0);
7937 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7939 gtk_widget_realize(handlebox);
7940 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7943 vbox2 = gtk_vbox_new(FALSE, 2);
7944 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7945 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7948 notebook = gtk_notebook_new();
7949 gtk_widget_show(notebook);
7951 /* header labels and entries */
7952 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7953 compose_create_header(compose),
7954 gtk_label_new_with_mnemonic(_("Hea_der")));
7955 /* attachment list */
7956 attach_hbox = gtk_hbox_new(FALSE, 0);
7957 gtk_widget_show(attach_hbox);
7959 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7960 gtk_widget_show(attach_lab1);
7961 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7963 attach_lab2 = gtk_label_new("");
7964 gtk_widget_show(attach_lab2);
7965 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7967 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7968 compose_create_attach(compose),
7971 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7972 compose_create_others(compose),
7973 gtk_label_new_with_mnemonic(_("Othe_rs")));
7976 subject_hbox = gtk_hbox_new(FALSE, 0);
7977 gtk_widget_show(subject_hbox);
7979 subject_frame = gtk_frame_new(NULL);
7980 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7981 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7982 gtk_widget_show(subject_frame);
7984 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7985 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7986 gtk_widget_show(subject);
7988 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7989 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7990 gtk_widget_show(label);
7993 subject_entry = claws_spell_entry_new();
7995 subject_entry = gtk_entry_new();
7997 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7998 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7999 G_CALLBACK(compose_grab_focus_cb), compose);
8000 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8001 gtk_widget_show(subject_entry);
8002 compose->subject_entry = subject_entry;
8003 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8005 edit_vbox = gtk_vbox_new(FALSE, 0);
8007 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8010 ruler_hbox = gtk_hbox_new(FALSE, 0);
8011 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8013 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8014 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8015 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8019 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8020 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8021 GTK_POLICY_AUTOMATIC,
8022 GTK_POLICY_AUTOMATIC);
8023 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8025 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8027 text = gtk_text_view_new();
8028 if (prefs_common.show_compose_margin) {
8029 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8030 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8032 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8033 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8034 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8035 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8036 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8038 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8039 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8040 G_CALLBACK(compose_edit_size_alloc),
8042 g_signal_connect(G_OBJECT(buffer), "changed",
8043 G_CALLBACK(compose_changed_cb), compose);
8044 g_signal_connect(G_OBJECT(text), "grab_focus",
8045 G_CALLBACK(compose_grab_focus_cb), compose);
8046 g_signal_connect(G_OBJECT(buffer), "insert_text",
8047 G_CALLBACK(text_inserted), compose);
8048 g_signal_connect(G_OBJECT(text), "button_press_event",
8049 G_CALLBACK(text_clicked), compose);
8050 g_signal_connect(G_OBJECT(text), "popup-menu",
8051 G_CALLBACK(compose_popup_menu), compose);
8052 g_signal_connect(G_OBJECT(subject_entry), "changed",
8053 G_CALLBACK(compose_changed_cb), compose);
8054 g_signal_connect(G_OBJECT(subject_entry), "activate",
8055 G_CALLBACK(compose_subject_entry_activated), compose);
8058 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8059 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8060 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8061 g_signal_connect(G_OBJECT(text), "drag_data_received",
8062 G_CALLBACK(compose_insert_drag_received_cb),
8064 g_signal_connect(G_OBJECT(text), "drag-drop",
8065 G_CALLBACK(compose_drag_drop),
8067 g_signal_connect(G_OBJECT(text), "key-press-event",
8068 G_CALLBACK(completion_set_focus_to_subject),
8070 gtk_widget_show_all(vbox);
8072 /* pane between attach clist and text */
8073 paned = gtk_vpaned_new();
8074 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8075 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8076 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8077 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8078 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8079 G_CALLBACK(compose_notebook_size_alloc), paned);
8081 gtk_widget_show_all(paned);
8084 if (prefs_common.textfont) {
8085 PangoFontDescription *font_desc;
8087 font_desc = pango_font_description_from_string
8088 (prefs_common.textfont);
8090 gtk_widget_modify_font(text, font_desc);
8091 pango_font_description_free(font_desc);
8095 gtk_action_group_add_actions(action_group, compose_popup_entries,
8096 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8097 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8098 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8099 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8100 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8101 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8102 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8104 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8106 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8107 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8108 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8110 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8112 undostruct = undo_init(text);
8113 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8116 address_completion_start(window);
8118 compose->window = window;
8119 compose->vbox = vbox;
8120 compose->menubar = menubar;
8121 compose->handlebox = handlebox;
8123 compose->vbox2 = vbox2;
8125 compose->paned = paned;
8127 compose->attach_label = attach_lab2;
8129 compose->notebook = notebook;
8130 compose->edit_vbox = edit_vbox;
8131 compose->ruler_hbox = ruler_hbox;
8132 compose->ruler = ruler;
8133 compose->scrolledwin = scrolledwin;
8134 compose->text = text;
8136 compose->focused_editable = NULL;
8138 compose->popupmenu = popupmenu;
8140 compose->tmpl_menu = tmpl_menu;
8142 compose->mode = mode;
8143 compose->rmode = mode;
8145 compose->targetinfo = NULL;
8146 compose->replyinfo = NULL;
8147 compose->fwdinfo = NULL;
8149 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8150 g_str_equal, (GDestroyNotify) g_free, NULL);
8152 compose->replyto = NULL;
8154 compose->bcc = NULL;
8155 compose->followup_to = NULL;
8157 compose->ml_post = NULL;
8159 compose->inreplyto = NULL;
8160 compose->references = NULL;
8161 compose->msgid = NULL;
8162 compose->boundary = NULL;
8164 compose->autowrap = prefs_common.autowrap;
8165 compose->autoindent = prefs_common.auto_indent;
8166 compose->use_signing = FALSE;
8167 compose->use_encryption = FALSE;
8168 compose->privacy_system = NULL;
8169 compose->encdata = NULL;
8171 compose->modified = FALSE;
8173 compose->return_receipt = FALSE;
8175 compose->to_list = NULL;
8176 compose->newsgroup_list = NULL;
8178 compose->undostruct = undostruct;
8180 compose->sig_str = NULL;
8182 compose->exteditor_file = NULL;
8183 compose->exteditor_pid = -1;
8184 compose->exteditor_tag = -1;
8185 compose->exteditor_socket = NULL;
8186 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8188 compose->folder_update_callback_id =
8189 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8190 compose_update_folder_hook,
8191 (gpointer) compose);
8194 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8195 if (mode != COMPOSE_REDIRECT) {
8196 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8197 strcmp(prefs_common.dictionary, "")) {
8198 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8199 prefs_common.alt_dictionary,
8200 conv_get_locale_charset_str(),
8201 prefs_common.color[COL_MISSPELLED],
8202 prefs_common.check_while_typing,
8203 prefs_common.recheck_when_changing_dict,
8204 prefs_common.use_alternate,
8205 prefs_common.use_both_dicts,
8206 GTK_TEXT_VIEW(text),
8207 GTK_WINDOW(compose->window),
8208 compose_dict_changed,
8209 compose_spell_menu_changed,
8212 alertpanel_error(_("Spell checker could not "
8214 gtkaspell_checkers_strerror());
8215 gtkaspell_checkers_reset_error();
8217 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8221 compose->gtkaspell = gtkaspell;
8222 compose_spell_menu_changed(compose);
8223 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8226 compose_select_account(compose, account, TRUE);
8228 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8229 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8231 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8232 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8234 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8235 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8237 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8238 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8241 if (account->protocol != A_NNTP)
8242 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8243 prefs_common_translated_header_name("To:"));
8245 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8246 prefs_common_translated_header_name("Newsgroups:"));
8248 #ifndef USE_ALT_ADDRBOOK
8249 addressbook_set_target_compose(compose);
8251 if (mode != COMPOSE_REDIRECT)
8252 compose_set_template_menu(compose);
8254 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8257 compose_list = g_list_append(compose_list, compose);
8259 if (!prefs_common.show_ruler)
8260 gtk_widget_hide(ruler_hbox);
8262 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8265 compose->priority = PRIORITY_NORMAL;
8266 compose_update_priority_menu_item(compose);
8268 compose_set_out_encoding(compose);
8271 compose_update_actions_menu(compose);
8273 /* Privacy Systems menu */
8274 compose_update_privacy_systems_menu(compose);
8276 activate_privacy_system(compose, account, TRUE);
8277 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8279 gtk_widget_realize(window);
8281 gtk_widget_show(window);
8287 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8292 GtkWidget *optmenubox;
8293 GtkWidget *fromlabel;
8296 GtkWidget *from_name = NULL;
8298 gint num = 0, def_menu = 0;
8300 accounts = account_get_list();
8301 cm_return_val_if_fail(accounts != NULL, NULL);
8303 optmenubox = gtk_event_box_new();
8304 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8305 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8307 hbox = gtk_hbox_new(FALSE, 4);
8308 from_name = gtk_entry_new();
8310 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8311 G_CALLBACK(compose_grab_focus_cb), compose);
8312 g_signal_connect_after(G_OBJECT(from_name), "activate",
8313 G_CALLBACK(from_name_activate_cb), optmenu);
8315 for (; accounts != NULL; accounts = accounts->next, num++) {
8316 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8317 gchar *name, *from = NULL;
8319 if (ac == compose->account) def_menu = num;
8321 name = g_markup_printf_escaped("<i>%s</i>",
8324 if (ac == compose->account) {
8325 if (ac->name && *ac->name) {
8327 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8328 from = g_strdup_printf("%s <%s>",
8330 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8332 from = g_strdup_printf("%s",
8334 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8336 if (cur_account != compose->account) {
8337 gtk_widget_modify_base(
8338 GTK_WIDGET(from_name),
8339 GTK_STATE_NORMAL, &default_header_bgcolor);
8340 gtk_widget_modify_text(
8341 GTK_WIDGET(from_name),
8342 GTK_STATE_NORMAL, &default_header_color);
8345 COMBOBOX_ADD(menu, name, ac->account_id);
8350 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8352 g_signal_connect(G_OBJECT(optmenu), "changed",
8353 G_CALLBACK(account_activated),
8355 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8356 G_CALLBACK(compose_entry_popup_extend),
8359 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8360 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8362 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8363 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8364 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8366 /* Putting only the GtkEntry into focus chain of parent hbox causes
8367 * the account selector combobox next to it to be unreachable when
8368 * navigating widgets in GtkTable with up/down arrow keys.
8369 * Note: gtk_widget_set_can_focus() was not enough. */
8371 l = g_list_prepend(l, from_name);
8372 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8375 CLAWS_SET_TIP(optmenubox,
8376 _("Account to use for this email"));
8377 CLAWS_SET_TIP(from_name,
8378 _("Sender address to be used"));
8380 compose->account_combo = optmenu;
8381 compose->from_name = from_name;
8386 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8388 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8389 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8390 Compose *compose = (Compose *) data;
8392 compose->priority = value;
8396 static void compose_reply_change_mode(Compose *compose,
8399 gboolean was_modified = compose->modified;
8401 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8403 cm_return_if_fail(compose->replyinfo != NULL);
8405 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8407 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8409 if (action == COMPOSE_REPLY_TO_ALL)
8411 if (action == COMPOSE_REPLY_TO_SENDER)
8413 if (action == COMPOSE_REPLY_TO_LIST)
8416 compose_remove_header_entries(compose);
8417 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8418 if (compose->account->set_autocc && compose->account->auto_cc)
8419 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8421 if (compose->account->set_autobcc && compose->account->auto_bcc)
8422 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8424 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8425 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8426 compose_show_first_last_header(compose, TRUE);
8427 compose->modified = was_modified;
8428 compose_set_title(compose);
8431 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8433 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8434 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8435 Compose *compose = (Compose *) data;
8438 compose_reply_change_mode(compose, value);
8441 static void compose_update_priority_menu_item(Compose * compose)
8443 GtkWidget *menuitem = NULL;
8444 switch (compose->priority) {
8445 case PRIORITY_HIGHEST:
8446 menuitem = gtk_ui_manager_get_widget
8447 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8450 menuitem = gtk_ui_manager_get_widget
8451 (compose->ui_manager, "/Menu/Options/Priority/High");
8453 case PRIORITY_NORMAL:
8454 menuitem = gtk_ui_manager_get_widget
8455 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8458 menuitem = gtk_ui_manager_get_widget
8459 (compose->ui_manager, "/Menu/Options/Priority/Low");
8461 case PRIORITY_LOWEST:
8462 menuitem = gtk_ui_manager_get_widget
8463 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8466 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8469 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8471 Compose *compose = (Compose *) data;
8473 gboolean can_sign = FALSE, can_encrypt = FALSE;
8475 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8477 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8480 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8481 g_free(compose->privacy_system);
8482 compose->privacy_system = NULL;
8483 g_free(compose->encdata);
8484 compose->encdata = NULL;
8485 if (systemid != NULL) {
8486 compose->privacy_system = g_strdup(systemid);
8488 can_sign = privacy_system_can_sign(systemid);
8489 can_encrypt = privacy_system_can_encrypt(systemid);
8492 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8494 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8495 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8496 if (compose->toolbar->privacy_sign_btn != NULL) {
8497 gtk_widget_set_sensitive(
8498 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8500 gtk_toggle_tool_button_set_active(
8501 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8502 can_sign ? compose->use_signing : FALSE);
8504 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8505 gtk_widget_set_sensitive(
8506 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8508 gtk_toggle_tool_button_set_active(
8509 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8510 can_encrypt ? compose->use_encryption : FALSE);
8514 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8516 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8517 GtkWidget *menuitem = NULL;
8518 GList *children, *amenu;
8519 gboolean can_sign = FALSE, can_encrypt = FALSE;
8520 gboolean found = FALSE;
8522 if (compose->privacy_system != NULL) {
8524 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8525 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8526 cm_return_if_fail(menuitem != NULL);
8528 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8531 while (amenu != NULL) {
8532 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8533 if (systemid != NULL) {
8534 if (strcmp(systemid, compose->privacy_system) == 0 &&
8535 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8536 menuitem = GTK_WIDGET(amenu->data);
8538 can_sign = privacy_system_can_sign(systemid);
8539 can_encrypt = privacy_system_can_encrypt(systemid);
8543 } else if (strlen(compose->privacy_system) == 0 &&
8544 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8545 menuitem = GTK_WIDGET(amenu->data);
8548 can_encrypt = FALSE;
8553 amenu = amenu->next;
8555 g_list_free(children);
8556 if (menuitem != NULL)
8557 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8559 if (warn && !found && strlen(compose->privacy_system)) {
8560 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8561 "will not be able to sign or encrypt this message."),
8562 compose->privacy_system);
8566 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8568 if (compose->toolbar->privacy_sign_btn != NULL) {
8569 gtk_widget_set_sensitive(
8570 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8573 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8574 gtk_widget_set_sensitive(
8575 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8580 static void compose_set_out_encoding(Compose *compose)
8582 CharSet out_encoding;
8583 const gchar *branch = NULL;
8584 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8586 switch(out_encoding) {
8587 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8588 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8589 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8590 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8591 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8592 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8593 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8594 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8595 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8596 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8597 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8598 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8599 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8600 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8601 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8602 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8603 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8604 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8605 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8606 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8607 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8608 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8609 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8610 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8611 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8612 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8613 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8614 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8615 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8616 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8617 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8618 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8619 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8620 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8622 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8625 static void compose_set_template_menu(Compose *compose)
8627 GSList *tmpl_list, *cur;
8631 tmpl_list = template_get_config();
8633 menu = gtk_menu_new();
8635 gtk_menu_set_accel_group (GTK_MENU (menu),
8636 gtk_ui_manager_get_accel_group(compose->ui_manager));
8637 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8638 Template *tmpl = (Template *)cur->data;
8639 gchar *accel_path = NULL;
8640 item = gtk_menu_item_new_with_label(tmpl->name);
8641 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8642 g_signal_connect(G_OBJECT(item), "activate",
8643 G_CALLBACK(compose_template_activate_cb),
8645 g_object_set_data(G_OBJECT(item), "template", tmpl);
8646 gtk_widget_show(item);
8647 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8648 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8652 gtk_widget_show(menu);
8653 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8656 void compose_update_actions_menu(Compose *compose)
8658 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8661 static void compose_update_privacy_systems_menu(Compose *compose)
8663 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8664 GSList *systems, *cur;
8666 GtkWidget *system_none;
8668 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8669 GtkWidget *privacy_menu = gtk_menu_new();
8671 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8672 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8674 g_signal_connect(G_OBJECT(system_none), "activate",
8675 G_CALLBACK(compose_set_privacy_system_cb), compose);
8677 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8678 gtk_widget_show(system_none);
8680 systems = privacy_get_system_ids();
8681 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8682 gchar *systemid = cur->data;
8684 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8685 widget = gtk_radio_menu_item_new_with_label(group,
8686 privacy_system_get_name(systemid));
8687 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8688 g_strdup(systemid), g_free);
8689 g_signal_connect(G_OBJECT(widget), "activate",
8690 G_CALLBACK(compose_set_privacy_system_cb), compose);
8692 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8693 gtk_widget_show(widget);
8696 g_slist_free(systems);
8697 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8698 gtk_widget_show_all(privacy_menu);
8699 gtk_widget_show_all(privacy_menuitem);
8702 void compose_reflect_prefs_all(void)
8707 for (cur = compose_list; cur != NULL; cur = cur->next) {
8708 compose = (Compose *)cur->data;
8709 compose_set_template_menu(compose);
8713 void compose_reflect_prefs_pixmap_theme(void)
8718 for (cur = compose_list; cur != NULL; cur = cur->next) {
8719 compose = (Compose *)cur->data;
8720 toolbar_update(TOOLBAR_COMPOSE, compose);
8724 static const gchar *compose_quote_char_from_context(Compose *compose)
8726 const gchar *qmark = NULL;
8728 cm_return_val_if_fail(compose != NULL, NULL);
8730 switch (compose->mode) {
8731 /* use forward-specific quote char */
8732 case COMPOSE_FORWARD:
8733 case COMPOSE_FORWARD_AS_ATTACH:
8734 case COMPOSE_FORWARD_INLINE:
8735 if (compose->folder && compose->folder->prefs &&
8736 compose->folder->prefs->forward_with_format)
8737 qmark = compose->folder->prefs->forward_quotemark;
8738 else if (compose->account->forward_with_format)
8739 qmark = compose->account->forward_quotemark;
8741 qmark = prefs_common.fw_quotemark;
8744 /* use reply-specific quote char in all other modes */
8746 if (compose->folder && compose->folder->prefs &&
8747 compose->folder->prefs->reply_with_format)
8748 qmark = compose->folder->prefs->reply_quotemark;
8749 else if (compose->account->reply_with_format)
8750 qmark = compose->account->reply_quotemark;
8752 qmark = prefs_common.quotemark;
8756 if (qmark == NULL || *qmark == '\0')
8762 static void compose_template_apply(Compose *compose, Template *tmpl,
8766 GtkTextBuffer *buffer;
8770 gchar *parsed_str = NULL;
8771 gint cursor_pos = 0;
8772 const gchar *err_msg = _("The body of the template has an error at line %d.");
8775 /* process the body */
8777 text = GTK_TEXT_VIEW(compose->text);
8778 buffer = gtk_text_view_get_buffer(text);
8781 qmark = compose_quote_char_from_context(compose);
8783 if (compose->replyinfo != NULL) {
8786 gtk_text_buffer_set_text(buffer, "", -1);
8787 mark = gtk_text_buffer_get_insert(buffer);
8788 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8790 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8791 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8793 } else if (compose->fwdinfo != NULL) {
8796 gtk_text_buffer_set_text(buffer, "", -1);
8797 mark = gtk_text_buffer_get_insert(buffer);
8798 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8800 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8801 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8804 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8806 GtkTextIter start, end;
8809 gtk_text_buffer_get_start_iter(buffer, &start);
8810 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8811 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8813 /* clear the buffer now */
8815 gtk_text_buffer_set_text(buffer, "", -1);
8817 parsed_str = compose_quote_fmt(compose, dummyinfo,
8818 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8819 procmsg_msginfo_free( &dummyinfo );
8825 gtk_text_buffer_set_text(buffer, "", -1);
8826 mark = gtk_text_buffer_get_insert(buffer);
8827 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8830 if (replace && parsed_str && compose->account->auto_sig)
8831 compose_insert_sig(compose, FALSE);
8833 if (replace && parsed_str) {
8834 gtk_text_buffer_get_start_iter(buffer, &iter);
8835 gtk_text_buffer_place_cursor(buffer, &iter);
8839 cursor_pos = quote_fmt_get_cursor_pos();
8840 compose->set_cursor_pos = cursor_pos;
8841 if (cursor_pos == -1)
8843 gtk_text_buffer_get_start_iter(buffer, &iter);
8844 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8845 gtk_text_buffer_place_cursor(buffer, &iter);
8848 /* process the other fields */
8850 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8851 compose_template_apply_fields(compose, tmpl);
8852 quote_fmt_reset_vartable();
8853 quote_fmtlex_destroy();
8855 compose_changed_cb(NULL, compose);
8858 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8859 gtkaspell_highlight_all(compose->gtkaspell);
8863 static void compose_template_apply_fields_error(const gchar *header)
8868 tr = g_strdup(C_("'%s' stands for a header name",
8869 "Template '%s' format error."));
8870 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8871 alertpanel_error("%s", text);
8877 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8879 MsgInfo* dummyinfo = NULL;
8880 MsgInfo *msginfo = NULL;
8883 if (compose->replyinfo != NULL)
8884 msginfo = compose->replyinfo;
8885 else if (compose->fwdinfo != NULL)
8886 msginfo = compose->fwdinfo;
8888 dummyinfo = compose_msginfo_new_from_compose(compose);
8889 msginfo = dummyinfo;
8892 if (tmpl->from && *tmpl->from != '\0') {
8894 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8895 compose->gtkaspell);
8897 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8899 quote_fmt_scan_string(tmpl->from);
8902 buf = quote_fmt_get_buffer();
8904 compose_template_apply_fields_error("From");
8906 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8909 quote_fmt_reset_vartable();
8910 quote_fmtlex_destroy();
8913 if (tmpl->to && *tmpl->to != '\0') {
8915 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8916 compose->gtkaspell);
8918 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8920 quote_fmt_scan_string(tmpl->to);
8923 buf = quote_fmt_get_buffer();
8925 compose_template_apply_fields_error("To");
8927 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8930 quote_fmt_reset_vartable();
8931 quote_fmtlex_destroy();
8934 if (tmpl->cc && *tmpl->cc != '\0') {
8936 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8937 compose->gtkaspell);
8939 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8941 quote_fmt_scan_string(tmpl->cc);
8944 buf = quote_fmt_get_buffer();
8946 compose_template_apply_fields_error("Cc");
8948 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8951 quote_fmt_reset_vartable();
8952 quote_fmtlex_destroy();
8955 if (tmpl->bcc && *tmpl->bcc != '\0') {
8957 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8958 compose->gtkaspell);
8960 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8962 quote_fmt_scan_string(tmpl->bcc);
8965 buf = quote_fmt_get_buffer();
8967 compose_template_apply_fields_error("Bcc");
8969 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8972 quote_fmt_reset_vartable();
8973 quote_fmtlex_destroy();
8976 if (tmpl->replyto && *tmpl->replyto != '\0') {
8978 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8979 compose->gtkaspell);
8981 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8983 quote_fmt_scan_string(tmpl->replyto);
8986 buf = quote_fmt_get_buffer();
8988 compose_template_apply_fields_error("Reply-To");
8990 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8993 quote_fmt_reset_vartable();
8994 quote_fmtlex_destroy();
8997 /* process the subject */
8998 if (tmpl->subject && *tmpl->subject != '\0') {
9000 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9001 compose->gtkaspell);
9003 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9005 quote_fmt_scan_string(tmpl->subject);
9008 buf = quote_fmt_get_buffer();
9010 compose_template_apply_fields_error("Subject");
9012 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9015 quote_fmt_reset_vartable();
9016 quote_fmtlex_destroy();
9019 procmsg_msginfo_free( &dummyinfo );
9022 static void compose_destroy(Compose *compose)
9024 GtkAllocation allocation;
9025 GtkTextBuffer *buffer;
9026 GtkClipboard *clipboard;
9028 compose_list = g_list_remove(compose_list, compose);
9031 gboolean enable = TRUE;
9032 g_slist_foreach(compose->passworded_ldap_servers,
9033 _ldap_srv_func, &enable);
9034 g_slist_free(compose->passworded_ldap_servers);
9037 if (compose->updating) {
9038 debug_print("danger, not destroying anything now\n");
9039 compose->deferred_destroy = TRUE;
9043 /* NOTE: address_completion_end() does nothing with the window
9044 * however this may change. */
9045 address_completion_end(compose->window);
9047 slist_free_strings_full(compose->to_list);
9048 slist_free_strings_full(compose->newsgroup_list);
9049 slist_free_strings_full(compose->header_list);
9051 slist_free_strings_full(extra_headers);
9052 extra_headers = NULL;
9054 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9056 g_hash_table_destroy(compose->email_hashtable);
9058 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9059 compose->folder_update_callback_id);
9061 procmsg_msginfo_free(&(compose->targetinfo));
9062 procmsg_msginfo_free(&(compose->replyinfo));
9063 procmsg_msginfo_free(&(compose->fwdinfo));
9065 g_free(compose->replyto);
9066 g_free(compose->cc);
9067 g_free(compose->bcc);
9068 g_free(compose->newsgroups);
9069 g_free(compose->followup_to);
9071 g_free(compose->ml_post);
9073 g_free(compose->inreplyto);
9074 g_free(compose->references);
9075 g_free(compose->msgid);
9076 g_free(compose->boundary);
9078 g_free(compose->redirect_filename);
9079 if (compose->undostruct)
9080 undo_destroy(compose->undostruct);
9082 g_free(compose->sig_str);
9084 g_free(compose->exteditor_file);
9086 g_free(compose->orig_charset);
9088 g_free(compose->privacy_system);
9089 g_free(compose->encdata);
9091 #ifndef USE_ALT_ADDRBOOK
9092 if (addressbook_get_target_compose() == compose)
9093 addressbook_set_target_compose(NULL);
9096 if (compose->gtkaspell) {
9097 gtkaspell_delete(compose->gtkaspell);
9098 compose->gtkaspell = NULL;
9102 if (!compose->batch) {
9103 gtk_widget_get_allocation(compose->window, &allocation);
9104 prefs_common.compose_width = allocation.width;
9105 prefs_common.compose_height = allocation.height;
9108 if (!gtk_widget_get_parent(compose->paned))
9109 gtk_widget_destroy(compose->paned);
9110 gtk_widget_destroy(compose->popupmenu);
9112 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9113 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9114 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9116 gtk_widget_destroy(compose->window);
9117 toolbar_destroy(compose->toolbar);
9118 g_free(compose->toolbar);
9119 cm_mutex_free(compose->mutex);
9123 static void compose_attach_info_free(AttachInfo *ainfo)
9125 g_free(ainfo->file);
9126 g_free(ainfo->content_type);
9127 g_free(ainfo->name);
9128 g_free(ainfo->charset);
9132 static void compose_attach_update_label(Compose *compose)
9137 GtkTreeModel *model;
9141 if (compose == NULL)
9144 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9145 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9146 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9150 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9151 total_size = ainfo->size;
9152 while(gtk_tree_model_iter_next(model, &iter)) {
9153 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9154 total_size += ainfo->size;
9157 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9158 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9162 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9164 Compose *compose = (Compose *)data;
9165 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9166 GtkTreeSelection *selection;
9168 GtkTreeModel *model;
9170 selection = gtk_tree_view_get_selection(tree_view);
9171 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9176 for (cur = sel; cur != NULL; cur = cur->next) {
9177 GtkTreePath *path = cur->data;
9178 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9181 gtk_tree_path_free(path);
9184 for (cur = sel; cur != NULL; cur = cur->next) {
9185 GtkTreeRowReference *ref = cur->data;
9186 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9189 if (gtk_tree_model_get_iter(model, &iter, path))
9190 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9192 gtk_tree_path_free(path);
9193 gtk_tree_row_reference_free(ref);
9197 compose_attach_update_label(compose);
9200 static struct _AttachProperty
9203 GtkWidget *mimetype_entry;
9204 GtkWidget *encoding_optmenu;
9205 GtkWidget *path_entry;
9206 GtkWidget *filename_entry;
9208 GtkWidget *cancel_btn;
9211 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9213 gtk_tree_path_free((GtkTreePath *)ptr);
9216 static void compose_attach_property(GtkAction *action, gpointer data)
9218 Compose *compose = (Compose *)data;
9219 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9221 GtkComboBox *optmenu;
9222 GtkTreeSelection *selection;
9224 GtkTreeModel *model;
9227 static gboolean cancelled;
9229 /* only if one selected */
9230 selection = gtk_tree_view_get_selection(tree_view);
9231 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9234 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9238 path = (GtkTreePath *) sel->data;
9239 gtk_tree_model_get_iter(model, &iter, path);
9240 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9243 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9249 if (!attach_prop.window)
9250 compose_attach_property_create(&cancelled);
9251 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9252 gtk_widget_grab_focus(attach_prop.ok_btn);
9253 gtk_widget_show(attach_prop.window);
9254 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9255 GTK_WINDOW(compose->window));
9257 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9258 if (ainfo->encoding == ENC_UNKNOWN)
9259 combobox_select_by_data(optmenu, ENC_BASE64);
9261 combobox_select_by_data(optmenu, ainfo->encoding);
9263 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9264 ainfo->content_type ? ainfo->content_type : "");
9265 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9266 ainfo->file ? ainfo->file : "");
9267 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9268 ainfo->name ? ainfo->name : "");
9271 const gchar *entry_text;
9273 gchar *cnttype = NULL;
9280 gtk_widget_hide(attach_prop.window);
9281 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9286 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9287 if (*entry_text != '\0') {
9290 text = g_strstrip(g_strdup(entry_text));
9291 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9292 cnttype = g_strdup(text);
9295 alertpanel_error(_("Invalid MIME type."));
9301 ainfo->encoding = combobox_get_active_data(optmenu);
9303 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9304 if (*entry_text != '\0') {
9305 if (is_file_exist(entry_text) &&
9306 (size = get_file_size(entry_text)) > 0)
9307 file = g_strdup(entry_text);
9310 (_("File doesn't exist or is empty."));
9316 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9317 if (*entry_text != '\0') {
9318 g_free(ainfo->name);
9319 ainfo->name = g_strdup(entry_text);
9323 g_free(ainfo->content_type);
9324 ainfo->content_type = cnttype;
9327 g_free(ainfo->file);
9331 ainfo->size = (goffset)size;
9333 /* update tree store */
9334 text = to_human_readable(ainfo->size);
9335 gtk_tree_model_get_iter(model, &iter, path);
9336 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9337 COL_MIMETYPE, ainfo->content_type,
9339 COL_NAME, ainfo->name,
9340 COL_CHARSET, ainfo->charset,
9346 gtk_tree_path_free(path);
9349 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9351 label = gtk_label_new(str); \
9352 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9353 GTK_FILL, 0, 0, 0); \
9354 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9356 entry = gtk_entry_new(); \
9357 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9358 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9361 static void compose_attach_property_create(gboolean *cancelled)
9367 GtkWidget *mimetype_entry;
9370 GtkListStore *optmenu_menu;
9371 GtkWidget *path_entry;
9372 GtkWidget *filename_entry;
9375 GtkWidget *cancel_btn;
9376 GList *mime_type_list, *strlist;
9379 debug_print("Creating attach_property window...\n");
9381 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9382 gtk_widget_set_size_request(window, 480, -1);
9383 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9384 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9385 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9386 g_signal_connect(G_OBJECT(window), "delete_event",
9387 G_CALLBACK(attach_property_delete_event),
9389 g_signal_connect(G_OBJECT(window), "key_press_event",
9390 G_CALLBACK(attach_property_key_pressed),
9393 vbox = gtk_vbox_new(FALSE, 8);
9394 gtk_container_add(GTK_CONTAINER(window), vbox);
9396 table = gtk_table_new(4, 2, FALSE);
9397 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9398 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9399 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9401 label = gtk_label_new(_("MIME type"));
9402 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9404 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9405 mimetype_entry = gtk_combo_box_text_new_with_entry();
9406 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9407 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9409 /* stuff with list */
9410 mime_type_list = procmime_get_mime_type_list();
9412 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9413 MimeType *type = (MimeType *) mime_type_list->data;
9416 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9418 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9421 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9422 (GCompareFunc)strcmp2);
9425 for (mime_type_list = strlist; mime_type_list != NULL;
9426 mime_type_list = mime_type_list->next) {
9427 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9428 g_free(mime_type_list->data);
9430 g_list_free(strlist);
9431 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9432 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9434 label = gtk_label_new(_("Encoding"));
9435 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9437 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9439 hbox = gtk_hbox_new(FALSE, 0);
9440 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9441 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9443 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9444 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9446 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9447 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9448 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9449 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9450 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9452 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9454 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9455 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9457 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9458 &ok_btn, GTK_STOCK_OK,
9460 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9461 gtk_widget_grab_default(ok_btn);
9463 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9464 G_CALLBACK(attach_property_ok),
9466 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9467 G_CALLBACK(attach_property_cancel),
9470 gtk_widget_show_all(vbox);
9472 attach_prop.window = window;
9473 attach_prop.mimetype_entry = mimetype_entry;
9474 attach_prop.encoding_optmenu = optmenu;
9475 attach_prop.path_entry = path_entry;
9476 attach_prop.filename_entry = filename_entry;
9477 attach_prop.ok_btn = ok_btn;
9478 attach_prop.cancel_btn = cancel_btn;
9481 #undef SET_LABEL_AND_ENTRY
9483 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9489 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9495 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9496 gboolean *cancelled)
9504 static gboolean attach_property_key_pressed(GtkWidget *widget,
9506 gboolean *cancelled)
9508 if (event && event->keyval == GDK_KEY_Escape) {
9512 if (event && event->keyval == GDK_KEY_Return) {
9520 static void compose_exec_ext_editor(Compose *compose)
9525 GdkNativeWindow socket_wid = 0;
9529 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9530 G_DIR_SEPARATOR, compose);
9532 if (compose_get_ext_editor_uses_socket()) {
9533 /* Only allow one socket */
9534 if (compose->exteditor_socket != NULL) {
9535 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9536 /* Move the focus off of the socket */
9537 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9542 /* Create the receiving GtkSocket */
9543 socket = gtk_socket_new ();
9544 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9545 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9547 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9548 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9549 /* Realize the socket so that we can use its ID */
9550 gtk_widget_realize(socket);
9551 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9552 compose->exteditor_socket = socket;
9555 if (pipe(pipe_fds) < 0) {
9561 if ((pid = fork()) < 0) {
9568 /* close the write side of the pipe */
9571 compose->exteditor_file = g_strdup(tmp);
9572 compose->exteditor_pid = pid;
9574 compose_set_ext_editor_sensitive(compose, FALSE);
9577 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9579 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9581 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9585 } else { /* process-monitoring process */
9591 /* close the read side of the pipe */
9594 if (compose_write_body_to_file(compose, tmp) < 0) {
9595 fd_write_all(pipe_fds[1], "2\n", 2);
9599 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9601 fd_write_all(pipe_fds[1], "1\n", 2);
9605 /* wait until editor is terminated */
9606 waitpid(pid_ed, NULL, 0);
9608 fd_write_all(pipe_fds[1], "0\n", 2);
9615 #endif /* G_OS_UNIX */
9618 static gboolean compose_can_autosave(Compose *compose)
9620 if (compose->privacy_system && compose->use_encryption)
9621 return prefs_common.autosave && prefs_common.autosave_encrypted;
9623 return prefs_common.autosave;
9627 static gboolean compose_get_ext_editor_cmd_valid()
9629 gboolean has_s = FALSE;
9630 gboolean has_w = FALSE;
9631 const gchar *p = prefs_common_get_ext_editor_cmd();
9634 while ((p = strchr(p, '%'))) {
9640 } else if (*p == 'w') {
9651 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9658 cm_return_val_if_fail(file != NULL, -1);
9660 if ((pid = fork()) < 0) {
9665 if (pid != 0) return pid;
9667 /* grandchild process */
9669 if (setpgid(0, getppid()))
9672 if (compose_get_ext_editor_cmd_valid()) {
9673 if (compose_get_ext_editor_uses_socket()) {
9674 p = g_strdup(prefs_common_get_ext_editor_cmd());
9675 s = strstr(p, "%w");
9677 if (strstr(p, "%s") < s)
9678 buf = g_strdup_printf(p, file, socket_wid);
9680 buf = g_strdup_printf(p, socket_wid, file);
9683 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9686 if (prefs_common_get_ext_editor_cmd())
9687 g_warning("External editor command-line is invalid: '%s'",
9688 prefs_common_get_ext_editor_cmd());
9689 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9692 cmdline = strsplit_with_quote(buf, " ", 0);
9694 execvp(cmdline[0], cmdline);
9697 g_strfreev(cmdline);
9702 static gboolean compose_ext_editor_kill(Compose *compose)
9704 pid_t pgid = compose->exteditor_pid * -1;
9707 ret = kill(pgid, 0);
9709 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9713 msg = g_strdup_printf
9714 (_("The external editor is still working.\n"
9715 "Force terminating the process?\n"
9716 "process group id: %d"), -pgid);
9717 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9718 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9723 if (val == G_ALERTALTERNATE) {
9724 g_source_remove(compose->exteditor_tag);
9725 g_io_channel_shutdown(compose->exteditor_ch,
9727 g_io_channel_unref(compose->exteditor_ch);
9729 if (kill(pgid, SIGTERM) < 0) perror("kill");
9730 waitpid(compose->exteditor_pid, NULL, 0);
9732 g_warning("Terminated process group id: %d. "
9733 "Temporary file: %s", -pgid, compose->exteditor_file);
9735 compose_set_ext_editor_sensitive(compose, TRUE);
9737 g_free(compose->exteditor_file);
9738 compose->exteditor_file = NULL;
9739 compose->exteditor_pid = -1;
9740 compose->exteditor_ch = NULL;
9741 compose->exteditor_tag = -1;
9749 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9753 Compose *compose = (Compose *)data;
9756 debug_print("Compose: input from monitoring process\n");
9758 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9763 g_io_channel_shutdown(source, FALSE, NULL);
9764 g_io_channel_unref(source);
9766 waitpid(compose->exteditor_pid, NULL, 0);
9768 if (buf[0] == '0') { /* success */
9769 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9770 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9771 GtkTextIter start, end;
9774 gtk_text_buffer_set_text(buffer, "", -1);
9775 compose_insert_file(compose, compose->exteditor_file);
9776 compose_changed_cb(NULL, compose);
9778 /* Check if we should save the draft or not */
9779 if (compose_can_autosave(compose))
9780 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9782 if (claws_unlink(compose->exteditor_file) < 0)
9783 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9785 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9786 gtk_text_buffer_get_start_iter(buffer, &start);
9787 gtk_text_buffer_get_end_iter(buffer, &end);
9788 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9789 if (chars && strlen(chars) > 0)
9790 compose->modified = TRUE;
9792 } else if (buf[0] == '1') { /* failed */
9793 g_warning("Couldn't exec external editor");
9794 if (claws_unlink(compose->exteditor_file) < 0)
9795 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9796 } else if (buf[0] == '2') {
9797 g_warning("Couldn't write to file");
9798 } else if (buf[0] == '3') {
9799 g_warning("Pipe read failed");
9802 compose_set_ext_editor_sensitive(compose, TRUE);
9804 g_free(compose->exteditor_file);
9805 compose->exteditor_file = NULL;
9806 compose->exteditor_pid = -1;
9807 compose->exteditor_ch = NULL;
9808 compose->exteditor_tag = -1;
9809 if (compose->exteditor_socket) {
9810 gtk_widget_destroy(compose->exteditor_socket);
9811 compose->exteditor_socket = NULL;
9818 static char *ext_editor_menu_entries[] = {
9819 "Menu/Message/Send",
9820 "Menu/Message/SendLater",
9821 "Menu/Message/InsertFile",
9822 "Menu/Message/InsertSig",
9823 "Menu/Message/ReplaceSig",
9824 "Menu/Message/Save",
9825 "Menu/Message/Print",
9830 "Menu/Tools/ShowRuler",
9831 "Menu/Tools/Actions",
9836 static void compose_set_ext_editor_sensitive(Compose *compose,
9841 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9842 cm_menu_set_sensitive_full(compose->ui_manager,
9843 ext_editor_menu_entries[i], sensitive);
9846 if (compose_get_ext_editor_uses_socket()) {
9848 if (compose->exteditor_socket)
9849 gtk_widget_hide(compose->exteditor_socket);
9850 gtk_widget_show(compose->scrolledwin);
9851 if (prefs_common.show_ruler)
9852 gtk_widget_show(compose->ruler_hbox);
9853 /* Fix the focus, as it doesn't go anywhere when the
9854 * socket is hidden or destroyed */
9855 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9857 g_assert (compose->exteditor_socket != NULL);
9858 /* Fix the focus, as it doesn't go anywhere when the
9859 * edit box is hidden */
9860 if (gtk_widget_is_focus(compose->text))
9861 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9862 gtk_widget_hide(compose->scrolledwin);
9863 gtk_widget_hide(compose->ruler_hbox);
9864 gtk_widget_show(compose->exteditor_socket);
9867 gtk_widget_set_sensitive(compose->text, sensitive);
9869 if (compose->toolbar->send_btn)
9870 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9871 if (compose->toolbar->sendl_btn)
9872 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9873 if (compose->toolbar->draft_btn)
9874 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9875 if (compose->toolbar->insert_btn)
9876 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9877 if (compose->toolbar->sig_btn)
9878 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9879 if (compose->toolbar->exteditor_btn)
9880 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9881 if (compose->toolbar->linewrap_current_btn)
9882 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9883 if (compose->toolbar->linewrap_all_btn)
9884 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9887 static gboolean compose_get_ext_editor_uses_socket()
9889 return (prefs_common_get_ext_editor_cmd() &&
9890 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9893 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9895 compose->exteditor_socket = NULL;
9896 /* returning FALSE allows destruction of the socket */
9899 #endif /* G_OS_UNIX */
9902 * compose_undo_state_changed:
9904 * Change the sensivity of the menuentries undo and redo
9906 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9907 gint redo_state, gpointer data)
9909 Compose *compose = (Compose *)data;
9911 switch (undo_state) {
9912 case UNDO_STATE_TRUE:
9913 if (!undostruct->undo_state) {
9914 undostruct->undo_state = TRUE;
9915 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9918 case UNDO_STATE_FALSE:
9919 if (undostruct->undo_state) {
9920 undostruct->undo_state = FALSE;
9921 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9924 case UNDO_STATE_UNCHANGED:
9926 case UNDO_STATE_REFRESH:
9927 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9930 g_warning("Undo state not recognized");
9934 switch (redo_state) {
9935 case UNDO_STATE_TRUE:
9936 if (!undostruct->redo_state) {
9937 undostruct->redo_state = TRUE;
9938 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9941 case UNDO_STATE_FALSE:
9942 if (undostruct->redo_state) {
9943 undostruct->redo_state = FALSE;
9944 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9947 case UNDO_STATE_UNCHANGED:
9949 case UNDO_STATE_REFRESH:
9950 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9953 g_warning("Redo state not recognized");
9958 /* callback functions */
9960 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9961 GtkAllocation *allocation,
9964 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9967 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9968 * includes "non-client" (windows-izm) in calculation, so this calculation
9969 * may not be accurate.
9971 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9972 GtkAllocation *allocation,
9973 GtkSHRuler *shruler)
9975 if (prefs_common.show_ruler) {
9976 gint char_width = 0, char_height = 0;
9977 gint line_width_in_chars;
9979 gtkut_get_font_size(GTK_WIDGET(widget),
9980 &char_width, &char_height);
9981 line_width_in_chars =
9982 (allocation->width - allocation->x) / char_width;
9984 /* got the maximum */
9985 gtk_shruler_set_range(GTK_SHRULER(shruler),
9986 0.0, line_width_in_chars, 0);
9995 ComposePrefType type;
9996 gboolean entry_marked;
9999 static void account_activated(GtkComboBox *optmenu, gpointer data)
10001 Compose *compose = (Compose *)data;
10004 gchar *folderidentifier;
10005 gint account_id = 0;
10006 GtkTreeModel *menu;
10008 GSList *list, *saved_list = NULL;
10009 HeaderEntryState *state;
10011 /* Get ID of active account in the combo box */
10012 menu = gtk_combo_box_get_model(optmenu);
10013 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10014 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10016 ac = account_find_from_id(account_id);
10017 cm_return_if_fail(ac != NULL);
10019 if (ac != compose->account) {
10020 compose_select_account(compose, ac, FALSE);
10022 for (list = compose->header_list; list; list = list->next) {
10023 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10025 if (hentry->type == PREF_ACCOUNT || !list->next) {
10026 compose_destroy_headerentry(compose, hentry);
10029 state = g_malloc0(sizeof(HeaderEntryState));
10030 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10031 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10032 state->entry = gtk_editable_get_chars(
10033 GTK_EDITABLE(hentry->entry), 0, -1);
10034 state->type = hentry->type;
10036 saved_list = g_slist_append(saved_list, state);
10037 compose_destroy_headerentry(compose, hentry);
10040 compose->header_last = NULL;
10041 g_slist_free(compose->header_list);
10042 compose->header_list = NULL;
10043 compose->header_nextrow = 1;
10044 compose_create_header_entry(compose);
10046 if (ac->set_autocc && ac->auto_cc)
10047 compose_entry_append(compose, ac->auto_cc,
10048 COMPOSE_CC, PREF_ACCOUNT);
10049 if (ac->set_autobcc && ac->auto_bcc)
10050 compose_entry_append(compose, ac->auto_bcc,
10051 COMPOSE_BCC, PREF_ACCOUNT);
10052 if (ac->set_autoreplyto && ac->auto_replyto)
10053 compose_entry_append(compose, ac->auto_replyto,
10054 COMPOSE_REPLYTO, PREF_ACCOUNT);
10056 for (list = saved_list; list; list = list->next) {
10057 state = (HeaderEntryState *) list->data;
10059 compose_add_header_entry(compose, state->header,
10060 state->entry, state->type);
10062 g_free(state->header);
10063 g_free(state->entry);
10066 g_slist_free(saved_list);
10068 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10069 (ac->protocol == A_NNTP) ?
10070 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10073 /* Set message save folder */
10074 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10075 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10077 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10078 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10080 compose_set_save_to(compose, NULL);
10081 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10082 folderidentifier = folder_item_get_identifier(account_get_special_folder
10083 (compose->account, F_OUTBOX));
10084 compose_set_save_to(compose, folderidentifier);
10085 g_free(folderidentifier);
10089 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10090 GtkTreeViewColumn *column, Compose *compose)
10092 compose_attach_property(NULL, compose);
10095 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10098 Compose *compose = (Compose *)data;
10099 GtkTreeSelection *attach_selection;
10100 gint attach_nr_selected;
10103 if (!event) return FALSE;
10105 if (event->button == 3) {
10106 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10107 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10109 /* If no rows, or just one row is selected, right-click should
10110 * open menu relevant to the row being right-clicked on. We
10111 * achieve that by selecting the clicked row first. If more
10112 * than one row is selected, we shouldn't modify the selection,
10113 * as user may want to remove selected rows (attachments). */
10114 if (attach_nr_selected < 2) {
10115 gtk_tree_selection_unselect_all(attach_selection);
10116 attach_nr_selected = 0;
10117 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10118 event->x, event->y, &path, NULL, NULL, NULL);
10119 if (path != NULL) {
10120 gtk_tree_selection_select_path(attach_selection, path);
10121 gtk_tree_path_free(path);
10122 attach_nr_selected++;
10126 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10127 /* Properties menu item makes no sense with more than one row
10128 * selected, the properties dialog can only edit one attachment. */
10129 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10131 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10132 NULL, NULL, event->button, event->time);
10139 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10142 Compose *compose = (Compose *)data;
10144 if (!event) return FALSE;
10146 switch (event->keyval) {
10147 case GDK_KEY_Delete:
10148 compose_attach_remove_selected(NULL, compose);
10154 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10156 toolbar_comp_set_sensitive(compose, allow);
10157 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10158 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10160 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10162 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10163 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10164 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10166 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10170 static void compose_send_cb(GtkAction *action, gpointer data)
10172 Compose *compose = (Compose *)data;
10175 if (compose->exteditor_tag != -1) {
10176 debug_print("ignoring send: external editor still open\n");
10180 if (prefs_common.work_offline &&
10181 !inc_offline_should_override(TRUE,
10182 _("Claws Mail needs network access in order "
10183 "to send this email.")))
10186 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10187 g_source_remove(compose->draft_timeout_tag);
10188 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10191 compose_send(compose);
10194 static void compose_send_later_cb(GtkAction *action, gpointer data)
10196 Compose *compose = (Compose *)data;
10200 compose_allow_user_actions(compose, FALSE);
10201 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10202 compose_allow_user_actions(compose, TRUE);
10206 compose_close(compose);
10207 } else if (val == -1) {
10208 alertpanel_error(_("Could not queue message."));
10209 } else if (val == -2) {
10210 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10211 } else if (val == -3) {
10212 if (privacy_peek_error())
10213 alertpanel_error(_("Could not queue message for sending:\n\n"
10214 "Signature failed: %s"), privacy_get_error());
10215 } else if (val == -4) {
10216 alertpanel_error(_("Could not queue message for sending:\n\n"
10217 "Charset conversion failed."));
10218 } else if (val == -5) {
10219 alertpanel_error(_("Could not queue message for sending:\n\n"
10220 "Couldn't get recipient encryption key."));
10221 } else if (val == -6) {
10224 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10227 #define DRAFTED_AT_EXIT "drafted_at_exit"
10228 static void compose_register_draft(MsgInfo *info)
10230 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10231 DRAFTED_AT_EXIT, NULL);
10232 FILE *fp = g_fopen(filepath, "ab");
10235 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10243 gboolean compose_draft (gpointer data, guint action)
10245 Compose *compose = (Compose *)data;
10250 MsgFlags flag = {0, 0};
10251 static gboolean lock = FALSE;
10252 MsgInfo *newmsginfo;
10254 gboolean target_locked = FALSE;
10255 gboolean err = FALSE;
10257 if (lock) return FALSE;
10259 if (compose->sending)
10262 draft = account_get_special_folder(compose->account, F_DRAFT);
10263 cm_return_val_if_fail(draft != NULL, FALSE);
10265 if (!g_mutex_trylock(compose->mutex)) {
10266 /* we don't want to lock the mutex once it's available,
10267 * because as the only other part of compose.c locking
10268 * it is compose_close - which means once unlocked,
10269 * the compose struct will be freed */
10270 debug_print("couldn't lock mutex, probably sending\n");
10276 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10277 G_DIR_SEPARATOR, compose);
10278 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10279 FILE_OP_ERROR(tmp, "fopen");
10283 /* chmod for security */
10284 if (change_file_mode_rw(fp, tmp) < 0) {
10285 FILE_OP_ERROR(tmp, "chmod");
10286 g_warning("can't change file mode");
10289 /* Save draft infos */
10290 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10291 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10293 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10294 gchar *savefolderid;
10296 savefolderid = compose_get_save_to(compose);
10297 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10298 g_free(savefolderid);
10300 if (compose->return_receipt) {
10301 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10303 if (compose->privacy_system) {
10304 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10305 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10306 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10309 /* Message-ID of message replying to */
10310 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10311 gchar *folderid = NULL;
10313 if (compose->replyinfo->folder)
10314 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10315 if (folderid == NULL)
10316 folderid = g_strdup("NULL");
10318 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10321 /* Message-ID of message forwarding to */
10322 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10323 gchar *folderid = NULL;
10325 if (compose->fwdinfo->folder)
10326 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10327 if (folderid == NULL)
10328 folderid = g_strdup("NULL");
10330 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10334 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10335 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10337 sheaders = compose_get_manual_headers_info(compose);
10338 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10341 /* end of headers */
10342 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10349 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10353 if (fclose(fp) == EOF) {
10357 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10358 if (compose->targetinfo) {
10359 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10361 flag.perm_flags |= MSG_LOCKED;
10363 flag.tmp_flags = MSG_DRAFT;
10365 folder_item_scan(draft);
10366 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10367 MsgInfo *tmpinfo = NULL;
10368 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10369 if (compose->msgid) {
10370 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10373 msgnum = tmpinfo->msgnum;
10374 procmsg_msginfo_free(&tmpinfo);
10375 debug_print("got draft msgnum %d from scanning\n", msgnum);
10377 debug_print("didn't get draft msgnum after scanning\n");
10380 debug_print("got draft msgnum %d from adding\n", msgnum);
10386 if (action != COMPOSE_AUTO_SAVE) {
10387 if (action != COMPOSE_DRAFT_FOR_EXIT)
10388 alertpanel_error(_("Could not save draft."));
10391 gtkut_window_popup(compose->window);
10392 val = alertpanel_full(_("Could not save draft"),
10393 _("Could not save draft.\n"
10394 "Do you want to cancel exit or discard this email?"),
10395 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10396 FALSE, NULL, ALERT_QUESTION);
10397 if (val == G_ALERTALTERNATE) {
10399 g_mutex_unlock(compose->mutex); /* must be done before closing */
10400 compose_close(compose);
10404 g_mutex_unlock(compose->mutex); /* must be done before closing */
10413 if (compose->mode == COMPOSE_REEDIT) {
10414 compose_remove_reedit_target(compose, TRUE);
10417 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10420 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10422 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10424 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10425 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10426 procmsg_msginfo_set_flags(newmsginfo, 0,
10427 MSG_HAS_ATTACHMENT);
10429 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10430 compose_register_draft(newmsginfo);
10432 procmsg_msginfo_free(&newmsginfo);
10435 folder_item_scan(draft);
10437 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10439 g_mutex_unlock(compose->mutex); /* must be done before closing */
10440 compose_close(compose);
10447 GError *error = NULL;
10452 goffset size, mtime;
10454 path = folder_item_fetch_msg(draft, msgnum);
10455 if (path == NULL) {
10456 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10460 f = g_file_new_for_path(path);
10461 fi = g_file_query_info(f, "standard::size,time::modified",
10462 G_FILE_QUERY_INFO_NONE, NULL, &error);
10463 if (error != NULL) {
10464 debug_print("couldn't query file info for '%s': %s\n",
10465 path, error->message);
10466 g_error_free(error);
10471 size = g_file_info_get_size(fi);
10472 g_file_info_get_modification_time(fi, &tv);
10474 g_object_unref(fi);
10477 if (g_stat(path, &s) < 0) {
10478 FILE_OP_ERROR(path, "stat");
10483 mtime = s.st_mtime;
10487 procmsg_msginfo_free(&(compose->targetinfo));
10488 compose->targetinfo = procmsg_msginfo_new();
10489 compose->targetinfo->msgnum = msgnum;
10490 compose->targetinfo->size = size;
10491 compose->targetinfo->mtime = mtime;
10492 compose->targetinfo->folder = draft;
10494 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10495 compose->mode = COMPOSE_REEDIT;
10497 if (action == COMPOSE_AUTO_SAVE) {
10498 compose->autosaved_draft = compose->targetinfo;
10500 compose->modified = FALSE;
10501 compose_set_title(compose);
10505 g_mutex_unlock(compose->mutex);
10509 void compose_clear_exit_drafts(void)
10511 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10512 DRAFTED_AT_EXIT, NULL);
10513 if (is_file_exist(filepath))
10514 claws_unlink(filepath);
10519 void compose_reopen_exit_drafts(void)
10521 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10522 DRAFTED_AT_EXIT, NULL);
10523 FILE *fp = g_fopen(filepath, "rb");
10527 while (fgets(buf, sizeof(buf), fp)) {
10528 gchar **parts = g_strsplit(buf, "\t", 2);
10529 const gchar *folder = parts[0];
10530 int msgnum = parts[1] ? atoi(parts[1]):-1;
10532 if (folder && *folder && msgnum > -1) {
10533 FolderItem *item = folder_find_item_from_identifier(folder);
10534 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10536 compose_reedit(info, FALSE);
10543 compose_clear_exit_drafts();
10546 static void compose_save_cb(GtkAction *action, gpointer data)
10548 Compose *compose = (Compose *)data;
10549 compose_draft(compose, COMPOSE_KEEP_EDITING);
10550 compose->rmode = COMPOSE_REEDIT;
10553 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10555 if (compose && file_list) {
10558 for ( tmp = file_list; tmp; tmp = tmp->next) {
10559 gchar *file = (gchar *) tmp->data;
10560 gchar *utf8_filename = conv_filename_to_utf8(file);
10561 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10562 compose_changed_cb(NULL, compose);
10567 g_free(utf8_filename);
10572 static void compose_attach_cb(GtkAction *action, gpointer data)
10574 Compose *compose = (Compose *)data;
10577 if (compose->redirect_filename != NULL)
10580 /* Set focus_window properly, in case we were called via popup menu,
10581 * which unsets it (via focus_out_event callback on compose window). */
10582 manage_window_focus_in(compose->window, NULL, NULL);
10584 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10587 compose_attach_from_list(compose, file_list, TRUE);
10588 g_list_free(file_list);
10592 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10594 Compose *compose = (Compose *)data;
10596 gint files_inserted = 0;
10598 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10603 for ( tmp = file_list; tmp; tmp = tmp->next) {
10604 gchar *file = (gchar *) tmp->data;
10605 gchar *filedup = g_strdup(file);
10606 gchar *shortfile = g_path_get_basename(filedup);
10607 ComposeInsertResult res;
10608 /* insert the file if the file is short or if the user confirmed that
10609 he/she wants to insert the large file */
10610 res = compose_insert_file(compose, file);
10611 if (res == COMPOSE_INSERT_READ_ERROR) {
10612 alertpanel_error(_("File '%s' could not be read."), shortfile);
10613 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10614 alertpanel_error(_("File '%s' contained invalid characters\n"
10615 "for the current encoding, insertion may be incorrect."),
10617 } else if (res == COMPOSE_INSERT_SUCCESS)
10624 g_list_free(file_list);
10628 if (files_inserted > 0 && compose->gtkaspell &&
10629 compose->gtkaspell->check_while_typing)
10630 gtkaspell_highlight_all(compose->gtkaspell);
10634 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10636 Compose *compose = (Compose *)data;
10638 compose_insert_sig(compose, FALSE);
10641 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10643 Compose *compose = (Compose *)data;
10645 compose_insert_sig(compose, TRUE);
10648 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10652 Compose *compose = (Compose *)data;
10654 gtkut_widget_get_uposition(widget, &x, &y);
10655 if (!compose->batch) {
10656 prefs_common.compose_x = x;
10657 prefs_common.compose_y = y;
10659 if (compose->sending || compose->updating)
10661 compose_close_cb(NULL, compose);
10665 void compose_close_toolbar(Compose *compose)
10667 compose_close_cb(NULL, compose);
10670 static void compose_close_cb(GtkAction *action, gpointer data)
10672 Compose *compose = (Compose *)data;
10676 if (compose->exteditor_tag != -1) {
10677 if (!compose_ext_editor_kill(compose))
10682 if (compose->modified) {
10683 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10684 if (!g_mutex_trylock(compose->mutex)) {
10685 /* we don't want to lock the mutex once it's available,
10686 * because as the only other part of compose.c locking
10687 * it is compose_close - which means once unlocked,
10688 * the compose struct will be freed */
10689 debug_print("couldn't lock mutex, probably sending\n");
10693 val = alertpanel(_("Discard message"),
10694 _("This message has been modified. Discard it?"),
10695 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10698 val = alertpanel(_("Save changes"),
10699 _("This message has been modified. Save the latest changes?"),
10700 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10701 ALERTFOCUS_SECOND);
10703 g_mutex_unlock(compose->mutex);
10705 case G_ALERTDEFAULT:
10706 if (compose_can_autosave(compose) && !reedit)
10707 compose_remove_draft(compose);
10709 case G_ALERTALTERNATE:
10710 compose_draft(data, COMPOSE_QUIT_EDITING);
10717 compose_close(compose);
10720 static void compose_print_cb(GtkAction *action, gpointer data)
10722 Compose *compose = (Compose *) data;
10724 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10725 if (compose->targetinfo)
10726 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10729 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10731 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10732 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10733 Compose *compose = (Compose *) data;
10736 compose->out_encoding = (CharSet)value;
10739 static void compose_address_cb(GtkAction *action, gpointer data)
10741 Compose *compose = (Compose *)data;
10743 #ifndef USE_ALT_ADDRBOOK
10744 addressbook_open(compose);
10746 GError* error = NULL;
10747 addressbook_connect_signals(compose);
10748 addressbook_dbus_open(TRUE, &error);
10750 g_warning("%s", error->message);
10751 g_error_free(error);
10756 static void about_show_cb(GtkAction *action, gpointer data)
10761 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10763 Compose *compose = (Compose *)data;
10768 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10769 cm_return_if_fail(tmpl != NULL);
10771 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10773 val = alertpanel(_("Apply template"), msg,
10774 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10777 if (val == G_ALERTDEFAULT)
10778 compose_template_apply(compose, tmpl, TRUE);
10779 else if (val == G_ALERTALTERNATE)
10780 compose_template_apply(compose, tmpl, FALSE);
10783 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10785 Compose *compose = (Compose *)data;
10788 if (compose->exteditor_tag != -1) {
10789 debug_print("ignoring open external editor: external editor still open\n");
10793 compose_exec_ext_editor(compose);
10796 static void compose_undo_cb(GtkAction *action, gpointer data)
10798 Compose *compose = (Compose *)data;
10799 gboolean prev_autowrap = compose->autowrap;
10801 compose->autowrap = FALSE;
10802 undo_undo(compose->undostruct);
10803 compose->autowrap = prev_autowrap;
10806 static void compose_redo_cb(GtkAction *action, gpointer data)
10808 Compose *compose = (Compose *)data;
10809 gboolean prev_autowrap = compose->autowrap;
10811 compose->autowrap = FALSE;
10812 undo_redo(compose->undostruct);
10813 compose->autowrap = prev_autowrap;
10816 static void entry_cut_clipboard(GtkWidget *entry)
10818 if (GTK_IS_EDITABLE(entry))
10819 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10820 else if (GTK_IS_TEXT_VIEW(entry))
10821 gtk_text_buffer_cut_clipboard(
10822 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10823 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10827 static void entry_copy_clipboard(GtkWidget *entry)
10829 if (GTK_IS_EDITABLE(entry))
10830 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10831 else if (GTK_IS_TEXT_VIEW(entry))
10832 gtk_text_buffer_copy_clipboard(
10833 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10834 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10837 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10838 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10840 if (GTK_IS_TEXT_VIEW(entry)) {
10841 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10842 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10843 GtkTextIter start_iter, end_iter;
10845 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10847 if (contents == NULL)
10850 /* we shouldn't delete the selection when middle-click-pasting, or we
10851 * can't mid-click-paste our own selection */
10852 if (clip != GDK_SELECTION_PRIMARY) {
10853 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10854 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10857 if (insert_place == NULL) {
10858 /* if insert_place isn't specified, insert at the cursor.
10859 * used for Ctrl-V pasting */
10860 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10861 start = gtk_text_iter_get_offset(&start_iter);
10862 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10864 /* if insert_place is specified, paste here.
10865 * used for mid-click-pasting */
10866 start = gtk_text_iter_get_offset(insert_place);
10867 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10868 if (prefs_common.primary_paste_unselects)
10869 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10873 /* paste unwrapped: mark the paste so it's not wrapped later */
10874 end = start + strlen(contents);
10875 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10876 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10877 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10878 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10879 /* rewrap paragraph now (after a mid-click-paste) */
10880 mark_start = gtk_text_buffer_get_insert(buffer);
10881 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10882 gtk_text_iter_backward_char(&start_iter);
10883 compose_beautify_paragraph(compose, &start_iter, TRUE);
10885 } else if (GTK_IS_EDITABLE(entry))
10886 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10888 compose->modified = TRUE;
10891 static void entry_allsel(GtkWidget *entry)
10893 if (GTK_IS_EDITABLE(entry))
10894 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10895 else if (GTK_IS_TEXT_VIEW(entry)) {
10896 GtkTextIter startiter, enditer;
10897 GtkTextBuffer *textbuf;
10899 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10900 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10901 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10903 gtk_text_buffer_move_mark_by_name(textbuf,
10904 "selection_bound", &startiter);
10905 gtk_text_buffer_move_mark_by_name(textbuf,
10906 "insert", &enditer);
10910 static void compose_cut_cb(GtkAction *action, gpointer data)
10912 Compose *compose = (Compose *)data;
10913 if (compose->focused_editable
10914 #ifndef GENERIC_UMPC
10915 && gtk_widget_has_focus(compose->focused_editable)
10918 entry_cut_clipboard(compose->focused_editable);
10921 static void compose_copy_cb(GtkAction *action, gpointer data)
10923 Compose *compose = (Compose *)data;
10924 if (compose->focused_editable
10925 #ifndef GENERIC_UMPC
10926 && gtk_widget_has_focus(compose->focused_editable)
10929 entry_copy_clipboard(compose->focused_editable);
10932 static void compose_paste_cb(GtkAction *action, gpointer data)
10934 Compose *compose = (Compose *)data;
10935 gint prev_autowrap;
10936 GtkTextBuffer *buffer;
10938 if (compose->focused_editable &&
10939 #ifndef GENERIC_UMPC
10940 gtk_widget_has_focus(compose->focused_editable)
10943 entry_paste_clipboard(compose, compose->focused_editable,
10944 prefs_common.linewrap_pastes,
10945 GDK_SELECTION_CLIPBOARD, NULL);
10950 #ifndef GENERIC_UMPC
10951 gtk_widget_has_focus(compose->text) &&
10953 compose->gtkaspell &&
10954 compose->gtkaspell->check_while_typing)
10955 gtkaspell_highlight_all(compose->gtkaspell);
10959 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10961 Compose *compose = (Compose *)data;
10962 gint wrap_quote = prefs_common.linewrap_quote;
10963 if (compose->focused_editable
10964 #ifndef GENERIC_UMPC
10965 && gtk_widget_has_focus(compose->focused_editable)
10968 /* let text_insert() (called directly or at a later time
10969 * after the gtk_editable_paste_clipboard) know that
10970 * text is to be inserted as a quotation. implemented
10971 * by using a simple refcount... */
10972 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10973 G_OBJECT(compose->focused_editable),
10974 "paste_as_quotation"));
10975 g_object_set_data(G_OBJECT(compose->focused_editable),
10976 "paste_as_quotation",
10977 GINT_TO_POINTER(paste_as_quotation + 1));
10978 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10979 entry_paste_clipboard(compose, compose->focused_editable,
10980 prefs_common.linewrap_pastes,
10981 GDK_SELECTION_CLIPBOARD, NULL);
10982 prefs_common.linewrap_quote = wrap_quote;
10986 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10988 Compose *compose = (Compose *)data;
10989 gint prev_autowrap;
10990 GtkTextBuffer *buffer;
10992 if (compose->focused_editable
10993 #ifndef GENERIC_UMPC
10994 && gtk_widget_has_focus(compose->focused_editable)
10997 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10998 GDK_SELECTION_CLIPBOARD, NULL);
11003 #ifndef GENERIC_UMPC
11004 gtk_widget_has_focus(compose->text) &&
11006 compose->gtkaspell &&
11007 compose->gtkaspell->check_while_typing)
11008 gtkaspell_highlight_all(compose->gtkaspell);
11012 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11014 Compose *compose = (Compose *)data;
11015 gint prev_autowrap;
11016 GtkTextBuffer *buffer;
11018 if (compose->focused_editable
11019 #ifndef GENERIC_UMPC
11020 && gtk_widget_has_focus(compose->focused_editable)
11023 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11024 GDK_SELECTION_CLIPBOARD, NULL);
11029 #ifndef GENERIC_UMPC
11030 gtk_widget_has_focus(compose->text) &&
11032 compose->gtkaspell &&
11033 compose->gtkaspell->check_while_typing)
11034 gtkaspell_highlight_all(compose->gtkaspell);
11038 static void compose_allsel_cb(GtkAction *action, gpointer data)
11040 Compose *compose = (Compose *)data;
11041 if (compose->focused_editable
11042 #ifndef GENERIC_UMPC
11043 && gtk_widget_has_focus(compose->focused_editable)
11046 entry_allsel(compose->focused_editable);
11049 static void textview_move_beginning_of_line (GtkTextView *text)
11051 GtkTextBuffer *buffer;
11055 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11057 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11058 mark = gtk_text_buffer_get_insert(buffer);
11059 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11060 gtk_text_iter_set_line_offset(&ins, 0);
11061 gtk_text_buffer_place_cursor(buffer, &ins);
11064 static void textview_move_forward_character (GtkTextView *text)
11066 GtkTextBuffer *buffer;
11070 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11072 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11073 mark = gtk_text_buffer_get_insert(buffer);
11074 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11075 if (gtk_text_iter_forward_cursor_position(&ins))
11076 gtk_text_buffer_place_cursor(buffer, &ins);
11079 static void textview_move_backward_character (GtkTextView *text)
11081 GtkTextBuffer *buffer;
11085 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11087 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11088 mark = gtk_text_buffer_get_insert(buffer);
11089 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11090 if (gtk_text_iter_backward_cursor_position(&ins))
11091 gtk_text_buffer_place_cursor(buffer, &ins);
11094 static void textview_move_forward_word (GtkTextView *text)
11096 GtkTextBuffer *buffer;
11101 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11103 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11104 mark = gtk_text_buffer_get_insert(buffer);
11105 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11106 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11107 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11108 gtk_text_iter_backward_word_start(&ins);
11109 gtk_text_buffer_place_cursor(buffer, &ins);
11113 static void textview_move_backward_word (GtkTextView *text)
11115 GtkTextBuffer *buffer;
11119 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11121 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11122 mark = gtk_text_buffer_get_insert(buffer);
11123 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11124 if (gtk_text_iter_backward_word_starts(&ins, 1))
11125 gtk_text_buffer_place_cursor(buffer, &ins);
11128 static void textview_move_end_of_line (GtkTextView *text)
11130 GtkTextBuffer *buffer;
11134 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11136 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11137 mark = gtk_text_buffer_get_insert(buffer);
11138 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11139 if (gtk_text_iter_forward_to_line_end(&ins))
11140 gtk_text_buffer_place_cursor(buffer, &ins);
11143 static void textview_move_next_line (GtkTextView *text)
11145 GtkTextBuffer *buffer;
11150 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11152 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11153 mark = gtk_text_buffer_get_insert(buffer);
11154 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11155 offset = gtk_text_iter_get_line_offset(&ins);
11156 if (gtk_text_iter_forward_line(&ins)) {
11157 gtk_text_iter_set_line_offset(&ins, offset);
11158 gtk_text_buffer_place_cursor(buffer, &ins);
11162 static void textview_move_previous_line (GtkTextView *text)
11164 GtkTextBuffer *buffer;
11169 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11171 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11172 mark = gtk_text_buffer_get_insert(buffer);
11173 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11174 offset = gtk_text_iter_get_line_offset(&ins);
11175 if (gtk_text_iter_backward_line(&ins)) {
11176 gtk_text_iter_set_line_offset(&ins, offset);
11177 gtk_text_buffer_place_cursor(buffer, &ins);
11181 static void textview_delete_forward_character (GtkTextView *text)
11183 GtkTextBuffer *buffer;
11185 GtkTextIter ins, end_iter;
11187 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11189 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11190 mark = gtk_text_buffer_get_insert(buffer);
11191 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11193 if (gtk_text_iter_forward_char(&end_iter)) {
11194 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11198 static void textview_delete_backward_character (GtkTextView *text)
11200 GtkTextBuffer *buffer;
11202 GtkTextIter ins, end_iter;
11204 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11206 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11207 mark = gtk_text_buffer_get_insert(buffer);
11208 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11210 if (gtk_text_iter_backward_char(&end_iter)) {
11211 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11215 static void textview_delete_forward_word (GtkTextView *text)
11217 GtkTextBuffer *buffer;
11219 GtkTextIter ins, end_iter;
11221 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11223 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11224 mark = gtk_text_buffer_get_insert(buffer);
11225 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11227 if (gtk_text_iter_forward_word_end(&end_iter)) {
11228 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11232 static void textview_delete_backward_word (GtkTextView *text)
11234 GtkTextBuffer *buffer;
11236 GtkTextIter ins, end_iter;
11238 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11240 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11241 mark = gtk_text_buffer_get_insert(buffer);
11242 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11244 if (gtk_text_iter_backward_word_start(&end_iter)) {
11245 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11249 static void textview_delete_line (GtkTextView *text)
11251 GtkTextBuffer *buffer;
11253 GtkTextIter ins, start_iter, end_iter;
11255 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11257 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11258 mark = gtk_text_buffer_get_insert(buffer);
11259 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11262 gtk_text_iter_set_line_offset(&start_iter, 0);
11265 if (gtk_text_iter_ends_line(&end_iter)){
11266 if (!gtk_text_iter_forward_char(&end_iter))
11267 gtk_text_iter_backward_char(&start_iter);
11270 gtk_text_iter_forward_to_line_end(&end_iter);
11271 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11274 static void textview_delete_to_line_end (GtkTextView *text)
11276 GtkTextBuffer *buffer;
11278 GtkTextIter ins, end_iter;
11280 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11282 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11283 mark = gtk_text_buffer_get_insert(buffer);
11284 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11286 if (gtk_text_iter_ends_line(&end_iter))
11287 gtk_text_iter_forward_char(&end_iter);
11289 gtk_text_iter_forward_to_line_end(&end_iter);
11290 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11293 #define DO_ACTION(name, act) { \
11294 if(!strcmp(name, a_name)) { \
11298 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11300 const gchar *a_name = gtk_action_get_name(action);
11301 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11302 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11303 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11304 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11305 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11306 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11307 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11308 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11309 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11310 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11311 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11312 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11313 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11314 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11315 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11318 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11320 Compose *compose = (Compose *)data;
11321 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11322 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11324 action = compose_call_advanced_action_from_path(gaction);
11327 void (*do_action) (GtkTextView *text);
11328 } action_table[] = {
11329 {textview_move_beginning_of_line},
11330 {textview_move_forward_character},
11331 {textview_move_backward_character},
11332 {textview_move_forward_word},
11333 {textview_move_backward_word},
11334 {textview_move_end_of_line},
11335 {textview_move_next_line},
11336 {textview_move_previous_line},
11337 {textview_delete_forward_character},
11338 {textview_delete_backward_character},
11339 {textview_delete_forward_word},
11340 {textview_delete_backward_word},
11341 {textview_delete_line},
11342 {textview_delete_to_line_end}
11345 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11347 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11348 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11349 if (action_table[action].do_action)
11350 action_table[action].do_action(text);
11352 g_warning("Not implemented yet.");
11356 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11358 GtkAllocation allocation;
11362 if (GTK_IS_EDITABLE(widget)) {
11363 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11364 gtk_editable_set_position(GTK_EDITABLE(widget),
11367 if ((parent = gtk_widget_get_parent(widget))
11368 && (parent = gtk_widget_get_parent(parent))
11369 && (parent = gtk_widget_get_parent(parent))) {
11370 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11371 gtk_widget_get_allocation(widget, &allocation);
11372 gint y = allocation.y;
11373 gint height = allocation.height;
11374 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11375 (GTK_SCROLLED_WINDOW(parent));
11377 gfloat value = gtk_adjustment_get_value(shown);
11378 gfloat upper = gtk_adjustment_get_upper(shown);
11379 gfloat page_size = gtk_adjustment_get_page_size(shown);
11380 if (y < (int)value) {
11381 gtk_adjustment_set_value(shown, y - 1);
11383 if ((y + height) > ((int)value + (int)page_size)) {
11384 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11385 gtk_adjustment_set_value(shown,
11386 y + height - (int)page_size - 1);
11388 gtk_adjustment_set_value(shown,
11389 (int)upper - (int)page_size - 1);
11396 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11397 compose->focused_editable = widget;
11399 #ifdef GENERIC_UMPC
11400 if (GTK_IS_TEXT_VIEW(widget)
11401 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11402 g_object_ref(compose->notebook);
11403 g_object_ref(compose->edit_vbox);
11404 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11405 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11406 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11407 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11408 g_object_unref(compose->notebook);
11409 g_object_unref(compose->edit_vbox);
11410 g_signal_handlers_block_by_func(G_OBJECT(widget),
11411 G_CALLBACK(compose_grab_focus_cb),
11413 gtk_widget_grab_focus(widget);
11414 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11415 G_CALLBACK(compose_grab_focus_cb),
11417 } else if (!GTK_IS_TEXT_VIEW(widget)
11418 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11419 g_object_ref(compose->notebook);
11420 g_object_ref(compose->edit_vbox);
11421 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11422 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11423 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11424 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11425 g_object_unref(compose->notebook);
11426 g_object_unref(compose->edit_vbox);
11427 g_signal_handlers_block_by_func(G_OBJECT(widget),
11428 G_CALLBACK(compose_grab_focus_cb),
11430 gtk_widget_grab_focus(widget);
11431 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11432 G_CALLBACK(compose_grab_focus_cb),
11438 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11440 compose->modified = TRUE;
11441 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11442 #ifndef GENERIC_UMPC
11443 compose_set_title(compose);
11447 static void compose_wrap_cb(GtkAction *action, gpointer data)
11449 Compose *compose = (Compose *)data;
11450 compose_beautify_paragraph(compose, NULL, TRUE);
11453 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11455 Compose *compose = (Compose *)data;
11456 compose_wrap_all_full(compose, TRUE);
11459 static void compose_find_cb(GtkAction *action, gpointer data)
11461 Compose *compose = (Compose *)data;
11463 message_search_compose(compose);
11466 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11469 Compose *compose = (Compose *)data;
11470 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11471 if (compose->autowrap)
11472 compose_wrap_all_full(compose, TRUE);
11473 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11476 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11479 Compose *compose = (Compose *)data;
11480 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11483 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11485 Compose *compose = (Compose *)data;
11487 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11488 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11491 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11493 Compose *compose = (Compose *)data;
11495 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11496 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11499 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11501 g_free(compose->privacy_system);
11502 g_free(compose->encdata);
11504 compose->privacy_system = g_strdup(account->default_privacy_system);
11505 compose_update_privacy_system_menu_item(compose, warn);
11508 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11510 Compose *compose = (Compose *)data;
11512 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11513 gtk_widget_show(compose->ruler_hbox);
11514 prefs_common.show_ruler = TRUE;
11516 gtk_widget_hide(compose->ruler_hbox);
11517 gtk_widget_queue_resize(compose->edit_vbox);
11518 prefs_common.show_ruler = FALSE;
11522 static void compose_attach_drag_received_cb (GtkWidget *widget,
11523 GdkDragContext *context,
11526 GtkSelectionData *data,
11529 gpointer user_data)
11531 Compose *compose = (Compose *)user_data;
11535 type = gtk_selection_data_get_data_type(data);
11536 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11537 && gtk_drag_get_source_widget(context) !=
11538 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11539 list = uri_list_extract_filenames(
11540 (const gchar *)gtk_selection_data_get_data(data));
11541 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11542 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11543 compose_attach_append
11544 (compose, (const gchar *)tmp->data,
11545 utf8_filename, NULL, NULL);
11546 g_free(utf8_filename);
11548 if (list) compose_changed_cb(NULL, compose);
11549 list_free_strings(list);
11551 } else if (gtk_drag_get_source_widget(context)
11552 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11553 /* comes from our summaryview */
11554 SummaryView * summaryview = NULL;
11555 GSList * list = NULL, *cur = NULL;
11557 if (mainwindow_get_mainwindow())
11558 summaryview = mainwindow_get_mainwindow()->summaryview;
11561 list = summary_get_selected_msg_list(summaryview);
11563 for (cur = list; cur; cur = cur->next) {
11564 MsgInfo *msginfo = (MsgInfo *)cur->data;
11565 gchar *file = NULL;
11567 file = procmsg_get_message_file_full(msginfo,
11570 compose_attach_append(compose, (const gchar *)file,
11571 (const gchar *)file, "message/rfc822", NULL);
11575 g_slist_free(list);
11579 static gboolean compose_drag_drop(GtkWidget *widget,
11580 GdkDragContext *drag_context,
11582 guint time, gpointer user_data)
11584 /* not handling this signal makes compose_insert_drag_received_cb
11589 static gboolean completion_set_focus_to_subject
11590 (GtkWidget *widget,
11591 GdkEventKey *event,
11594 cm_return_val_if_fail(compose != NULL, FALSE);
11596 /* make backtab move to subject field */
11597 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11598 gtk_widget_grab_focus(compose->subject_entry);
11604 static void compose_insert_drag_received_cb (GtkWidget *widget,
11605 GdkDragContext *drag_context,
11608 GtkSelectionData *data,
11611 gpointer user_data)
11613 Compose *compose = (Compose *)user_data;
11619 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11621 type = gtk_selection_data_get_data_type(data);
11622 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11623 AlertValue val = G_ALERTDEFAULT;
11624 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11626 list = uri_list_extract_filenames(ddata);
11627 num_files = g_list_length(list);
11628 if (list == NULL && strstr(ddata, "://")) {
11629 /* Assume a list of no files, and data has ://, is a remote link */
11630 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11631 gchar *tmpfile = get_tmp_file();
11632 str_write_to_file(tmpdata, tmpfile);
11634 compose_insert_file(compose, tmpfile);
11635 claws_unlink(tmpfile);
11637 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11638 compose_beautify_paragraph(compose, NULL, TRUE);
11641 switch (prefs_common.compose_dnd_mode) {
11642 case COMPOSE_DND_ASK:
11643 msg = g_strdup_printf(
11645 "Do you want to insert the contents of the file "
11646 "into the message body, or attach it to the email?",
11647 "Do you want to insert the contents of the %d files "
11648 "into the message body, or attach them to the email?",
11651 val = alertpanel_full(_("Insert or attach?"), msg,
11652 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11654 TRUE, NULL, ALERT_QUESTION);
11657 case COMPOSE_DND_INSERT:
11658 val = G_ALERTALTERNATE;
11660 case COMPOSE_DND_ATTACH:
11661 val = G_ALERTOTHER;
11664 /* unexpected case */
11665 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11668 if (val & G_ALERTDISABLE) {
11669 val &= ~G_ALERTDISABLE;
11670 /* remember what action to perform by default, only if we don't click Cancel */
11671 if (val == G_ALERTALTERNATE)
11672 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11673 else if (val == G_ALERTOTHER)
11674 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11677 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11678 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11679 list_free_strings(list);
11682 } else if (val == G_ALERTOTHER) {
11683 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11684 list_free_strings(list);
11689 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11690 compose_insert_file(compose, (const gchar *)tmp->data);
11692 list_free_strings(list);
11694 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11699 static void compose_header_drag_received_cb (GtkWidget *widget,
11700 GdkDragContext *drag_context,
11703 GtkSelectionData *data,
11706 gpointer user_data)
11708 GtkEditable *entry = (GtkEditable *)user_data;
11709 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11711 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11714 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11715 gchar *decoded=g_new(gchar, strlen(email));
11718 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11719 gtk_editable_delete_text(entry, 0, -1);
11720 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11721 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11725 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11728 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11730 Compose *compose = (Compose *)data;
11732 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11733 compose->return_receipt = TRUE;
11735 compose->return_receipt = FALSE;
11738 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11740 Compose *compose = (Compose *)data;
11742 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11743 compose->remove_references = TRUE;
11745 compose->remove_references = FALSE;
11748 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11749 ComposeHeaderEntry *headerentry)
11751 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11752 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11753 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11757 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11758 GdkEventKey *event,
11759 ComposeHeaderEntry *headerentry)
11761 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11762 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11763 !(event->state & GDK_MODIFIER_MASK) &&
11764 (event->keyval == GDK_KEY_BackSpace) &&
11765 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11766 gtk_container_remove
11767 (GTK_CONTAINER(headerentry->compose->header_table),
11768 headerentry->combo);
11769 gtk_container_remove
11770 (GTK_CONTAINER(headerentry->compose->header_table),
11771 headerentry->entry);
11772 headerentry->compose->header_list =
11773 g_slist_remove(headerentry->compose->header_list,
11775 g_free(headerentry);
11776 } else if (event->keyval == GDK_KEY_Tab) {
11777 if (headerentry->compose->header_last == headerentry) {
11778 /* Override default next focus, and give it to subject_entry
11779 * instead of notebook tabs
11781 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11782 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11789 static gboolean scroll_postpone(gpointer data)
11791 Compose *compose = (Compose *)data;
11793 if (compose->batch)
11796 GTK_EVENTS_FLUSH();
11797 compose_show_first_last_header(compose, FALSE);
11801 static void compose_headerentry_changed_cb(GtkWidget *entry,
11802 ComposeHeaderEntry *headerentry)
11804 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11805 compose_create_header_entry(headerentry->compose);
11806 g_signal_handlers_disconnect_matched
11807 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11808 0, 0, NULL, NULL, headerentry);
11810 if (!headerentry->compose->batch)
11811 g_timeout_add(0, scroll_postpone, headerentry->compose);
11815 static gboolean compose_defer_auto_save_draft(Compose *compose)
11817 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11818 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11822 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11824 GtkAdjustment *vadj;
11826 cm_return_if_fail(compose);
11831 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11832 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11833 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11834 gtk_widget_get_parent(compose->header_table)));
11835 gtk_adjustment_set_value(vadj, (show_first ?
11836 gtk_adjustment_get_lower(vadj) :
11837 (gtk_adjustment_get_upper(vadj) -
11838 gtk_adjustment_get_page_size(vadj))));
11839 gtk_adjustment_changed(vadj);
11842 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11843 const gchar *text, gint len, Compose *compose)
11845 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11846 (G_OBJECT(compose->text), "paste_as_quotation"));
11849 cm_return_if_fail(text != NULL);
11851 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11852 G_CALLBACK(text_inserted),
11854 if (paste_as_quotation) {
11856 const gchar *qmark;
11858 GtkTextIter start_iter;
11861 len = strlen(text);
11863 new_text = g_strndup(text, len);
11865 qmark = compose_quote_char_from_context(compose);
11867 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11868 gtk_text_buffer_place_cursor(buffer, iter);
11870 pos = gtk_text_iter_get_offset(iter);
11872 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11873 _("Quote format error at line %d."));
11874 quote_fmt_reset_vartable();
11876 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11877 GINT_TO_POINTER(paste_as_quotation - 1));
11879 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11880 gtk_text_buffer_place_cursor(buffer, iter);
11881 gtk_text_buffer_delete_mark(buffer, mark);
11883 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11884 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11885 compose_beautify_paragraph(compose, &start_iter, FALSE);
11886 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11887 gtk_text_buffer_delete_mark(buffer, mark);
11889 if (strcmp(text, "\n") || compose->automatic_break
11890 || gtk_text_iter_starts_line(iter)) {
11891 GtkTextIter before_ins;
11892 gtk_text_buffer_insert(buffer, iter, text, len);
11893 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11894 before_ins = *iter;
11895 gtk_text_iter_backward_chars(&before_ins, len);
11896 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11899 /* check if the preceding is just whitespace or quote */
11900 GtkTextIter start_line;
11901 gchar *tmp = NULL, *quote = NULL;
11902 gint quote_len = 0, is_normal = 0;
11903 start_line = *iter;
11904 gtk_text_iter_set_line_offset(&start_line, 0);
11905 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11908 if (*tmp == '\0') {
11911 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11919 gtk_text_buffer_insert(buffer, iter, text, len);
11921 gtk_text_buffer_insert_with_tags_by_name(buffer,
11922 iter, text, len, "no_join", NULL);
11927 if (!paste_as_quotation) {
11928 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11929 compose_beautify_paragraph(compose, iter, FALSE);
11930 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11931 gtk_text_buffer_delete_mark(buffer, mark);
11934 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11935 G_CALLBACK(text_inserted),
11937 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11939 if (compose_can_autosave(compose) &&
11940 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11941 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11942 compose->draft_timeout_tag = g_timeout_add
11943 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11947 static void compose_check_all(GtkAction *action, gpointer data)
11949 Compose *compose = (Compose *)data;
11950 if (!compose->gtkaspell)
11953 if (gtk_widget_has_focus(compose->subject_entry))
11954 claws_spell_entry_check_all(
11955 CLAWS_SPELL_ENTRY(compose->subject_entry));
11957 gtkaspell_check_all(compose->gtkaspell);
11960 static void compose_highlight_all(GtkAction *action, gpointer data)
11962 Compose *compose = (Compose *)data;
11963 if (compose->gtkaspell) {
11964 claws_spell_entry_recheck_all(
11965 CLAWS_SPELL_ENTRY(compose->subject_entry));
11966 gtkaspell_highlight_all(compose->gtkaspell);
11970 static void compose_check_backwards(GtkAction *action, gpointer data)
11972 Compose *compose = (Compose *)data;
11973 if (!compose->gtkaspell) {
11974 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11978 if (gtk_widget_has_focus(compose->subject_entry))
11979 claws_spell_entry_check_backwards(
11980 CLAWS_SPELL_ENTRY(compose->subject_entry));
11982 gtkaspell_check_backwards(compose->gtkaspell);
11985 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11987 Compose *compose = (Compose *)data;
11988 if (!compose->gtkaspell) {
11989 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11993 if (gtk_widget_has_focus(compose->subject_entry))
11994 claws_spell_entry_check_forwards_go(
11995 CLAWS_SPELL_ENTRY(compose->subject_entry));
11997 gtkaspell_check_forwards_go(compose->gtkaspell);
12002 *\brief Guess originating forward account from MsgInfo and several
12003 * "common preference" settings. Return NULL if no guess.
12005 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12007 PrefsAccount *account = NULL;
12009 cm_return_val_if_fail(msginfo, NULL);
12010 cm_return_val_if_fail(msginfo->folder, NULL);
12011 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12013 if (msginfo->folder->prefs->enable_default_account)
12014 account = account_find_from_id(msginfo->folder->prefs->default_account);
12016 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12018 Xstrdup_a(to, msginfo->to, return NULL);
12019 extract_address(to);
12020 account = account_find_from_address(to, FALSE);
12023 if (!account && prefs_common.forward_account_autosel) {
12025 if (!procheader_get_header_from_msginfo
12026 (msginfo, &cc, "Cc:")) {
12027 gchar *buf = cc + strlen("Cc:");
12028 extract_address(buf);
12029 account = account_find_from_address(buf, FALSE);
12034 if (!account && prefs_common.forward_account_autosel) {
12035 gchar *deliveredto = NULL;
12036 if (!procheader_get_header_from_msginfo
12037 (msginfo, &deliveredto, "Delivered-To:")) {
12038 gchar *buf = deliveredto + strlen("Delivered-To:");
12039 extract_address(buf);
12040 account = account_find_from_address(buf, FALSE);
12041 g_free(deliveredto);
12046 account = msginfo->folder->folder->account;
12051 gboolean compose_close(Compose *compose)
12055 cm_return_val_if_fail(compose, FALSE);
12057 if (!g_mutex_trylock(compose->mutex)) {
12058 /* we have to wait for the (possibly deferred by auto-save)
12059 * drafting to be done, before destroying the compose under
12061 debug_print("waiting for drafting to finish...\n");
12062 compose_allow_user_actions(compose, FALSE);
12063 if (compose->close_timeout_tag == 0) {
12064 compose->close_timeout_tag =
12065 g_timeout_add (500, (GSourceFunc) compose_close,
12071 if (compose->draft_timeout_tag >= 0) {
12072 g_source_remove(compose->draft_timeout_tag);
12073 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12076 gtkut_widget_get_uposition(compose->window, &x, &y);
12077 if (!compose->batch) {
12078 prefs_common.compose_x = x;
12079 prefs_common.compose_y = y;
12081 g_mutex_unlock(compose->mutex);
12082 compose_destroy(compose);
12087 * Add entry field for each address in list.
12088 * \param compose E-Mail composition object.
12089 * \param listAddress List of (formatted) E-Mail addresses.
12091 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12094 node = listAddress;
12096 addr = ( gchar * ) node->data;
12097 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12098 node = g_list_next( node );
12102 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12103 guint action, gboolean opening_multiple)
12105 gchar *body = NULL;
12106 GSList *new_msglist = NULL;
12107 MsgInfo *tmp_msginfo = NULL;
12108 gboolean originally_enc = FALSE;
12109 gboolean originally_sig = FALSE;
12110 Compose *compose = NULL;
12111 gchar *s_system = NULL;
12113 cm_return_if_fail(msgview != NULL);
12115 cm_return_if_fail(msginfo_list != NULL);
12117 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12118 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12119 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12121 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12122 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12123 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12124 orig_msginfo, mimeinfo);
12125 if (tmp_msginfo != NULL) {
12126 new_msglist = g_slist_append(NULL, tmp_msginfo);
12128 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12129 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12130 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12132 tmp_msginfo->folder = orig_msginfo->folder;
12133 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12134 if (orig_msginfo->tags) {
12135 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12136 tmp_msginfo->folder->tags_dirty = TRUE;
12142 if (!opening_multiple)
12143 body = messageview_get_selection(msgview);
12146 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12147 procmsg_msginfo_free(&tmp_msginfo);
12148 g_slist_free(new_msglist);
12150 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12152 if (compose && originally_enc) {
12153 compose_force_encryption(compose, compose->account, FALSE, s_system);
12156 if (compose && originally_sig && compose->account->default_sign_reply) {
12157 compose_force_signing(compose, compose->account, s_system);
12161 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12164 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12167 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12168 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12169 GSList *cur = msginfo_list;
12170 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12171 "messages. Opening the windows "
12172 "could take some time. Do you "
12173 "want to continue?"),
12174 g_slist_length(msginfo_list));
12175 if (g_slist_length(msginfo_list) > 9
12176 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12177 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12182 /* We'll open multiple compose windows */
12183 /* let the WM place the next windows */
12184 compose_force_window_origin = FALSE;
12185 for (; cur; cur = cur->next) {
12187 tmplist.data = cur->data;
12188 tmplist.next = NULL;
12189 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12191 compose_force_window_origin = TRUE;
12193 /* forwarding multiple mails as attachments is done via a
12194 * single compose window */
12195 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12199 void compose_check_for_email_account(Compose *compose)
12201 PrefsAccount *ac = NULL, *curr = NULL;
12207 if (compose->account && compose->account->protocol == A_NNTP) {
12208 ac = account_get_cur_account();
12209 if (ac->protocol == A_NNTP) {
12210 list = account_get_list();
12212 for( ; list != NULL ; list = g_list_next(list)) {
12213 curr = (PrefsAccount *) list->data;
12214 if (curr->protocol != A_NNTP) {
12220 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12225 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12226 const gchar *address)
12228 GSList *msginfo_list = NULL;
12229 gchar *body = messageview_get_selection(msgview);
12232 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12234 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12235 compose_check_for_email_account(compose);
12236 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12237 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12238 compose_reply_set_subject(compose, msginfo);
12241 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12244 void compose_set_position(Compose *compose, gint pos)
12246 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12248 gtkut_text_view_set_position(text, pos);
12251 gboolean compose_search_string(Compose *compose,
12252 const gchar *str, gboolean case_sens)
12254 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12256 return gtkut_text_view_search_string(text, str, case_sens);
12259 gboolean compose_search_string_backward(Compose *compose,
12260 const gchar *str, gboolean case_sens)
12262 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12264 return gtkut_text_view_search_string_backward(text, str, case_sens);
12267 /* allocate a msginfo structure and populate its data from a compose data structure */
12268 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12270 MsgInfo *newmsginfo;
12272 gchar date[RFC822_DATE_BUFFSIZE];
12274 cm_return_val_if_fail( compose != NULL, NULL );
12276 newmsginfo = procmsg_msginfo_new();
12279 get_rfc822_date(date, sizeof(date));
12280 newmsginfo->date = g_strdup(date);
12283 if (compose->from_name) {
12284 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12285 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12289 if (compose->subject_entry)
12290 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12292 /* to, cc, reply-to, newsgroups */
12293 for (list = compose->header_list; list; list = list->next) {
12294 gchar *header = gtk_editable_get_chars(
12296 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12297 gchar *entry = gtk_editable_get_chars(
12298 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12300 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12301 if ( newmsginfo->to == NULL ) {
12302 newmsginfo->to = g_strdup(entry);
12303 } else if (entry && *entry) {
12304 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12305 g_free(newmsginfo->to);
12306 newmsginfo->to = tmp;
12309 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12310 if ( newmsginfo->cc == NULL ) {
12311 newmsginfo->cc = g_strdup(entry);
12312 } else if (entry && *entry) {
12313 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12314 g_free(newmsginfo->cc);
12315 newmsginfo->cc = tmp;
12318 if ( strcasecmp(header,
12319 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12320 if ( newmsginfo->newsgroups == NULL ) {
12321 newmsginfo->newsgroups = g_strdup(entry);
12322 } else if (entry && *entry) {
12323 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12324 g_free(newmsginfo->newsgroups);
12325 newmsginfo->newsgroups = tmp;
12333 /* other data is unset */
12339 /* update compose's dictionaries from folder dict settings */
12340 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12341 FolderItem *folder_item)
12343 cm_return_if_fail(compose != NULL);
12345 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12346 FolderItemPrefs *prefs = folder_item->prefs;
12348 if (prefs->enable_default_dictionary)
12349 gtkaspell_change_dict(compose->gtkaspell,
12350 prefs->default_dictionary, FALSE);
12351 if (folder_item->prefs->enable_default_alt_dictionary)
12352 gtkaspell_change_alt_dict(compose->gtkaspell,
12353 prefs->default_alt_dictionary);
12354 if (prefs->enable_default_dictionary
12355 || prefs->enable_default_alt_dictionary)
12356 compose_spell_menu_changed(compose);
12361 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12363 Compose *compose = (Compose *)data;
12365 cm_return_if_fail(compose != NULL);
12367 gtk_widget_grab_focus(compose->text);
12370 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12372 gtk_combo_box_popup(GTK_COMBO_BOX(data));