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"
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
126 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
186 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
188 static GdkColor default_header_bgcolor = {
195 static GdkColor default_header_color = {
202 static GList *compose_list = NULL;
203 static GSList *extra_headers = NULL;
205 static Compose *compose_generic_new (PrefsAccount *account,
209 GList *listAddress );
211 static Compose *compose_create (PrefsAccount *account,
216 static void compose_entry_indicate (Compose *compose,
217 const gchar *address);
218 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
219 ComposeQuoteMode quote_mode,
223 static Compose *compose_forward_multiple (PrefsAccount *account,
224 GSList *msginfo_list);
225 static Compose *compose_reply (MsgInfo *msginfo,
226 ComposeQuoteMode quote_mode,
231 static Compose *compose_reply_mode (ComposeMode mode,
232 GSList *msginfo_list,
234 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
235 static void compose_update_privacy_systems_menu(Compose *compose);
237 static GtkWidget *compose_account_option_menu_create
239 static void compose_set_out_encoding (Compose *compose);
240 static void compose_set_template_menu (Compose *compose);
241 static void compose_destroy (Compose *compose);
243 static MailField compose_entries_set (Compose *compose,
245 ComposeEntryType to_type);
246 static gint compose_parse_header (Compose *compose,
248 static gint compose_parse_manual_headers (Compose *compose,
250 HeaderEntry *entries);
251 static gchar *compose_parse_references (const gchar *ref,
254 static gchar *compose_quote_fmt (Compose *compose,
260 gboolean need_unescape,
261 const gchar *err_msg);
263 static void compose_reply_set_entry (Compose *compose,
269 followup_and_reply_to);
270 static void compose_reedit_set_entry (Compose *compose,
273 static void compose_insert_sig (Compose *compose,
275 static ComposeInsertResult compose_insert_file (Compose *compose,
278 static gboolean compose_attach_append (Compose *compose,
281 const gchar *content_type,
282 const gchar *charset);
283 static void compose_attach_parts (Compose *compose,
286 static gboolean compose_beautify_paragraph (Compose *compose,
287 GtkTextIter *par_iter,
289 static void compose_wrap_all (Compose *compose);
290 static void compose_wrap_all_full (Compose *compose,
293 static void compose_set_title (Compose *compose);
294 static void compose_select_account (Compose *compose,
295 PrefsAccount *account,
298 static PrefsAccount *compose_current_mail_account(void);
299 /* static gint compose_send (Compose *compose); */
300 static gboolean compose_check_for_valid_recipient
302 static gboolean compose_check_entries (Compose *compose,
303 gboolean check_everything);
304 static gint compose_write_to_file (Compose *compose,
307 gboolean attach_parts);
308 static gint compose_write_body_to_file (Compose *compose,
310 static gint compose_remove_reedit_target (Compose *compose,
312 static void compose_remove_draft (Compose *compose);
313 static gint compose_queue_sub (Compose *compose,
317 gboolean perform_checks,
318 gboolean remove_reedit_target);
319 static int compose_add_attachments (Compose *compose,
321 static gchar *compose_get_header (Compose *compose);
322 static gchar *compose_get_manual_headers_info (Compose *compose);
324 static void compose_convert_header (Compose *compose,
329 gboolean addr_field);
331 static void compose_attach_info_free (AttachInfo *ainfo);
332 static void compose_attach_remove_selected (GtkAction *action,
335 static void compose_template_apply (Compose *compose,
338 static void compose_attach_property (GtkAction *action,
340 static void compose_attach_property_create (gboolean *cancelled);
341 static void attach_property_ok (GtkWidget *widget,
342 gboolean *cancelled);
343 static void attach_property_cancel (GtkWidget *widget,
344 gboolean *cancelled);
345 static gint attach_property_delete_event (GtkWidget *widget,
347 gboolean *cancelled);
348 static gboolean attach_property_key_pressed (GtkWidget *widget,
350 gboolean *cancelled);
352 static void compose_exec_ext_editor (Compose *compose);
354 static gint compose_exec_ext_editor_real (const gchar *file,
355 GdkNativeWindow socket_wid);
356 static gboolean compose_ext_editor_kill (Compose *compose);
357 static gboolean compose_input_cb (GIOChannel *source,
358 GIOCondition condition,
360 static void compose_set_ext_editor_sensitive (Compose *compose,
362 static gboolean compose_get_ext_editor_cmd_valid();
363 static gboolean compose_get_ext_editor_uses_socket();
364 static gboolean compose_ext_editor_plug_removed_cb
367 #endif /* G_OS_UNIX */
369 static void compose_undo_state_changed (UndoMain *undostruct,
374 static void compose_create_header_entry (Compose *compose);
375 static void compose_add_header_entry (Compose *compose, const gchar *header,
376 gchar *text, ComposePrefType pref_type);
377 static void compose_remove_header_entries(Compose *compose);
379 static void compose_update_priority_menu_item(Compose * compose);
381 static void compose_spell_menu_changed (void *data);
382 static void compose_dict_changed (void *data);
384 static void compose_add_field_list ( Compose *compose,
385 GList *listAddress );
387 /* callback functions */
389 static void compose_notebook_size_alloc (GtkNotebook *notebook,
390 GtkAllocation *allocation,
392 static gboolean compose_edit_size_alloc (GtkEditable *widget,
393 GtkAllocation *allocation,
394 GtkSHRuler *shruler);
395 static void account_activated (GtkComboBox *optmenu,
397 static void attach_selected (GtkTreeView *tree_view,
398 GtkTreePath *tree_path,
399 GtkTreeViewColumn *column,
401 static gboolean attach_button_pressed (GtkWidget *widget,
402 GdkEventButton *event,
404 static gboolean attach_key_pressed (GtkWidget *widget,
407 static void compose_send_cb (GtkAction *action, gpointer data);
408 static void compose_send_later_cb (GtkAction *action, gpointer data);
410 static void compose_save_cb (GtkAction *action,
413 static void compose_attach_cb (GtkAction *action,
415 static void compose_insert_file_cb (GtkAction *action,
417 static void compose_insert_sig_cb (GtkAction *action,
419 static void compose_replace_sig_cb (GtkAction *action,
422 static void compose_close_cb (GtkAction *action,
424 static void compose_print_cb (GtkAction *action,
427 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
429 static void compose_address_cb (GtkAction *action,
431 static void about_show_cb (GtkAction *action,
433 static void compose_template_activate_cb(GtkWidget *widget,
436 static void compose_ext_editor_cb (GtkAction *action,
439 static gint compose_delete_cb (GtkWidget *widget,
443 static void compose_undo_cb (GtkAction *action,
445 static void compose_redo_cb (GtkAction *action,
447 static void compose_cut_cb (GtkAction *action,
449 static void compose_copy_cb (GtkAction *action,
451 static void compose_paste_cb (GtkAction *action,
453 static void compose_paste_as_quote_cb (GtkAction *action,
455 static void compose_paste_no_wrap_cb (GtkAction *action,
457 static void compose_paste_wrap_cb (GtkAction *action,
459 static void compose_allsel_cb (GtkAction *action,
462 static void compose_advanced_action_cb (GtkAction *action,
465 static void compose_grab_focus_cb (GtkWidget *widget,
468 static void compose_changed_cb (GtkTextBuffer *textbuf,
471 static void compose_wrap_cb (GtkAction *action,
473 static void compose_wrap_all_cb (GtkAction *action,
475 static void compose_find_cb (GtkAction *action,
477 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
479 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
482 static void compose_toggle_ruler_cb (GtkToggleAction *action,
484 static void compose_toggle_sign_cb (GtkToggleAction *action,
486 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
488 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
489 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
490 static void activate_privacy_system (Compose *compose,
491 PrefsAccount *account,
493 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
495 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
497 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
498 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
499 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
501 static void compose_attach_drag_received_cb (GtkWidget *widget,
502 GdkDragContext *drag_context,
505 GtkSelectionData *data,
509 static void compose_insert_drag_received_cb (GtkWidget *widget,
510 GdkDragContext *drag_context,
513 GtkSelectionData *data,
517 static void compose_header_drag_received_cb (GtkWidget *widget,
518 GdkDragContext *drag_context,
521 GtkSelectionData *data,
526 static gboolean compose_drag_drop (GtkWidget *widget,
527 GdkDragContext *drag_context,
529 guint time, gpointer user_data);
530 static gboolean completion_set_focus_to_subject
535 static void text_inserted (GtkTextBuffer *buffer,
540 static Compose *compose_generic_reply(MsgInfo *msginfo,
541 ComposeQuoteMode quote_mode,
545 gboolean followup_and_reply_to,
548 static void compose_headerentry_changed_cb (GtkWidget *entry,
549 ComposeHeaderEntry *headerentry);
550 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
552 ComposeHeaderEntry *headerentry);
553 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
554 ComposeHeaderEntry *headerentry);
556 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
558 static void compose_allow_user_actions (Compose *compose, gboolean allow);
560 static void compose_nothing_cb (GtkAction *action, gpointer data)
566 static void compose_check_all (GtkAction *action, gpointer data);
567 static void compose_highlight_all (GtkAction *action, gpointer data);
568 static void compose_check_backwards (GtkAction *action, gpointer data);
569 static void compose_check_forwards_go (GtkAction *action, gpointer data);
572 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
574 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
577 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
578 FolderItem *folder_item);
580 static void compose_attach_update_label(Compose *compose);
581 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
582 gboolean respect_default_to);
583 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
584 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
586 static GtkActionEntry compose_popup_entries[] =
588 {"Compose", NULL, "Compose", NULL, NULL, NULL },
589 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
590 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
591 {"Compose/---", NULL, "---", NULL, NULL, NULL },
592 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
595 static GtkActionEntry compose_entries[] =
597 {"Menu", NULL, "Menu", NULL, NULL, NULL },
599 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
600 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
602 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
604 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
605 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
606 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
608 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
609 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
610 {"Message/---", NULL, "---", NULL, NULL, NULL },
612 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
613 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
614 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
615 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
616 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
617 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
618 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
619 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
620 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
621 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
624 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
625 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
626 {"Edit/---", NULL, "---", NULL, NULL, NULL },
628 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
629 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
630 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
632 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
633 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
634 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
635 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
637 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
639 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
640 {"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*/
641 {"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*/
642 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
643 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
644 {"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*/
645 {"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*/
646 {"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*/
647 {"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*/
648 {"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*/
649 {"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*/
650 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
651 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
652 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
653 {"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*/
655 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
656 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
658 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
659 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
660 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
661 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
662 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
665 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
666 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
667 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
668 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
670 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
671 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
675 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
676 {"Options/---", NULL, "---", NULL, NULL, NULL },
677 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
678 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
680 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
681 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
683 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
684 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
685 #define ENC_ACTION(cs_char,c_char,string) \
686 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
688 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
689 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
690 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
691 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
692 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
693 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
694 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
695 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
696 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
699 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
701 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
702 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
703 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
704 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
707 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
710 static GtkToggleActionEntry compose_toggle_entries[] =
712 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
713 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
714 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
715 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
716 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
717 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
718 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
721 static GtkRadioActionEntry compose_radio_rm_entries[] =
723 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
724 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
725 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
729 static GtkRadioActionEntry compose_radio_prio_entries[] =
731 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
732 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
733 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
738 static GtkRadioActionEntry compose_radio_enc_entries[] =
740 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
775 static GtkTargetEntry compose_mime_types[] =
777 {"text/uri-list", 0, 0},
778 {"UTF8_STRING", 0, 0},
782 static gboolean compose_put_existing_to_front(MsgInfo *info)
784 const GList *compose_list = compose_get_compose_list();
785 const GList *elem = NULL;
788 for (elem = compose_list; elem != NULL && elem->data != NULL;
790 Compose *c = (Compose*)elem->data;
792 if (!c->targetinfo || !c->targetinfo->msgid ||
796 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
797 gtkut_window_popup(c->window);
805 static GdkColor quote_color1 =
806 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
807 static GdkColor quote_color2 =
808 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
809 static GdkColor quote_color3 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
812 static GdkColor quote_bgcolor1 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_bgcolor2 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor3 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor signature_color = {
826 static GdkColor uri_color = {
833 static void compose_create_tags(GtkTextView *text, Compose *compose)
835 GtkTextBuffer *buffer;
836 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
837 #if !GTK_CHECK_VERSION(2, 24, 0)
844 buffer = gtk_text_view_get_buffer(text);
846 if (prefs_common.enable_color) {
847 /* grab the quote colors, converting from an int to a GdkColor */
848 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
850 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
852 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
854 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
856 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
858 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
860 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
862 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
865 signature_color = quote_color1 = quote_color2 = quote_color3 =
866 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
869 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
870 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
871 "foreground-gdk", "e_color1,
872 "paragraph-background-gdk", "e_bgcolor1,
874 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
875 "foreground-gdk", "e_color2,
876 "paragraph-background-gdk", "e_bgcolor2,
878 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
879 "foreground-gdk", "e_color3,
880 "paragraph-background-gdk", "e_bgcolor3,
883 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
884 "foreground-gdk", "e_color1,
886 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
887 "foreground-gdk", "e_color2,
889 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
890 "foreground-gdk", "e_color3,
894 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
895 "foreground-gdk", &signature_color,
898 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
899 "foreground-gdk", &uri_color,
901 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
902 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
904 #if !GTK_CHECK_VERSION(2, 24, 0)
905 color[0] = quote_color1;
906 color[1] = quote_color2;
907 color[2] = quote_color3;
908 color[3] = quote_bgcolor1;
909 color[4] = quote_bgcolor2;
910 color[5] = quote_bgcolor3;
911 color[6] = signature_color;
912 color[7] = uri_color;
914 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
915 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
917 for (i = 0; i < 8; i++) {
918 if (success[i] == FALSE) {
919 g_warning("Compose: color allocation failed.");
920 quote_color1 = quote_color2 = quote_color3 =
921 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
922 signature_color = uri_color = black;
928 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
931 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
934 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
936 return compose_generic_new(account, mailto, item, NULL, NULL);
939 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
941 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
944 #define SCROLL_TO_CURSOR(compose) { \
945 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
946 gtk_text_view_get_buffer( \
947 GTK_TEXT_VIEW(compose->text))); \
948 gtk_text_view_scroll_mark_onscreen( \
949 GTK_TEXT_VIEW(compose->text), \
953 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
956 if (folderidentifier) {
957 #if !GTK_CHECK_VERSION(2, 24, 0)
958 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
960 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
962 prefs_common.compose_save_to_history = add_history(
963 prefs_common.compose_save_to_history, folderidentifier);
964 #if !GTK_CHECK_VERSION(2, 24, 0)
965 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
966 prefs_common.compose_save_to_history);
968 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
969 prefs_common.compose_save_to_history);
973 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
974 if (folderidentifier)
975 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
977 gtk_entry_set_text(GTK_ENTRY(entry), "");
980 static gchar *compose_get_save_to(Compose *compose)
983 gchar *result = NULL;
984 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
985 result = gtk_editable_get_chars(entry, 0, -1);
988 #if !GTK_CHECK_VERSION(2, 24, 0)
989 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
991 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
993 prefs_common.compose_save_to_history = add_history(
994 prefs_common.compose_save_to_history, result);
995 #if !GTK_CHECK_VERSION(2, 24, 0)
996 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
997 prefs_common.compose_save_to_history);
999 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1000 prefs_common.compose_save_to_history);
1006 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1007 GList *attach_files, GList *listAddress )
1010 GtkTextView *textview;
1011 GtkTextBuffer *textbuf;
1013 const gchar *subject_format = NULL;
1014 const gchar *body_format = NULL;
1015 gchar *mailto_from = NULL;
1016 PrefsAccount *mailto_account = NULL;
1017 MsgInfo* dummyinfo = NULL;
1018 gint cursor_pos = -1;
1019 MailField mfield = NO_FIELD_PRESENT;
1023 /* check if mailto defines a from */
1024 if (mailto && *mailto != '\0') {
1025 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1026 /* mailto defines a from, check if we can get account prefs from it,
1027 if not, the account prefs will be guessed using other ways, but we'll keep
1030 mailto_account = account_find_from_address(mailto_from, TRUE);
1031 if (mailto_account == NULL) {
1033 Xstrdup_a(tmp_from, mailto_from, return NULL);
1034 extract_address(tmp_from);
1035 mailto_account = account_find_from_address(tmp_from, TRUE);
1039 account = mailto_account;
1042 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1043 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1044 account = account_find_from_id(item->prefs->default_account);
1046 /* if no account prefs set, fallback to the current one */
1047 if (!account) account = cur_account;
1048 cm_return_val_if_fail(account != NULL, NULL);
1050 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1052 /* override from name if mailto asked for it */
1054 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1055 g_free(mailto_from);
1057 /* override from name according to folder properties */
1058 if (item && item->prefs &&
1059 item->prefs->compose_with_format &&
1060 item->prefs->compose_override_from_format &&
1061 *item->prefs->compose_override_from_format != '\0') {
1066 dummyinfo = compose_msginfo_new_from_compose(compose);
1068 /* decode \-escape sequences in the internal representation of the quote format */
1069 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1070 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1073 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1074 compose->gtkaspell);
1076 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1078 quote_fmt_scan_string(tmp);
1081 buf = quote_fmt_get_buffer();
1083 alertpanel_error(_("New message From format error."));
1085 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1086 quote_fmt_reset_vartable();
1087 quote_fmtlex_destroy();
1092 compose->replyinfo = NULL;
1093 compose->fwdinfo = NULL;
1095 textview = GTK_TEXT_VIEW(compose->text);
1096 textbuf = gtk_text_view_get_buffer(textview);
1097 compose_create_tags(textview, compose);
1099 undo_block(compose->undostruct);
1101 compose_set_dictionaries_from_folder_prefs(compose, item);
1104 if (account->auto_sig)
1105 compose_insert_sig(compose, FALSE);
1106 gtk_text_buffer_get_start_iter(textbuf, &iter);
1107 gtk_text_buffer_place_cursor(textbuf, &iter);
1109 if (account->protocol != A_NNTP) {
1110 if (mailto && *mailto != '\0') {
1111 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1114 compose_set_folder_prefs(compose, item, TRUE);
1116 if (item && item->ret_rcpt) {
1117 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1120 if (mailto && *mailto != '\0') {
1121 if (!strchr(mailto, '@'))
1122 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1124 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1125 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1126 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1127 mfield = TO_FIELD_PRESENT;
1130 * CLAWS: just don't allow return receipt request, even if the user
1131 * may want to send an email. simple but foolproof.
1133 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1135 compose_add_field_list( compose, listAddress );
1137 if (item && item->prefs && item->prefs->compose_with_format) {
1138 subject_format = item->prefs->compose_subject_format;
1139 body_format = item->prefs->compose_body_format;
1140 } else if (account->compose_with_format) {
1141 subject_format = account->compose_subject_format;
1142 body_format = account->compose_body_format;
1143 } else if (prefs_common.compose_with_format) {
1144 subject_format = prefs_common.compose_subject_format;
1145 body_format = prefs_common.compose_body_format;
1148 if (subject_format || body_format) {
1151 && *subject_format != '\0' )
1153 gchar *subject = NULL;
1158 dummyinfo = compose_msginfo_new_from_compose(compose);
1160 /* decode \-escape sequences in the internal representation of the quote format */
1161 tmp = g_malloc(strlen(subject_format)+1);
1162 pref_get_unescaped_pref(tmp, subject_format);
1164 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1166 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1167 compose->gtkaspell);
1169 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1171 quote_fmt_scan_string(tmp);
1174 buf = quote_fmt_get_buffer();
1176 alertpanel_error(_("New message subject format error."));
1178 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1179 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1180 quote_fmt_reset_vartable();
1181 quote_fmtlex_destroy();
1185 mfield = SUBJECT_FIELD_PRESENT;
1189 && *body_format != '\0' )
1192 GtkTextBuffer *buffer;
1193 GtkTextIter start, end;
1197 dummyinfo = compose_msginfo_new_from_compose(compose);
1199 text = GTK_TEXT_VIEW(compose->text);
1200 buffer = gtk_text_view_get_buffer(text);
1201 gtk_text_buffer_get_start_iter(buffer, &start);
1202 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1203 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1205 compose_quote_fmt(compose, dummyinfo,
1207 NULL, tmp, FALSE, TRUE,
1208 _("The body of the \"New message\" template has an error at line %d."));
1209 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1210 quote_fmt_reset_vartable();
1214 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1215 gtkaspell_highlight_all(compose->gtkaspell);
1217 mfield = BODY_FIELD_PRESENT;
1221 procmsg_msginfo_free( &dummyinfo );
1227 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1228 ainfo = (AttachInfo *) curr->data;
1230 compose_insert_file(compose, ainfo->file);
1232 compose_attach_append(compose, ainfo->file, ainfo->file,
1233 ainfo->content_type, ainfo->charset);
1237 compose_show_first_last_header(compose, TRUE);
1239 /* Set save folder */
1240 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1241 gchar *folderidentifier;
1243 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1244 folderidentifier = folder_item_get_identifier(item);
1245 compose_set_save_to(compose, folderidentifier);
1246 g_free(folderidentifier);
1249 /* Place cursor according to provided input (mfield) */
1251 case NO_FIELD_PRESENT:
1252 if (compose->header_last)
1253 gtk_widget_grab_focus(compose->header_last->entry);
1255 case TO_FIELD_PRESENT:
1256 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1258 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1261 gtk_widget_grab_focus(compose->subject_entry);
1263 case SUBJECT_FIELD_PRESENT:
1264 textview = GTK_TEXT_VIEW(compose->text);
1267 textbuf = gtk_text_view_get_buffer(textview);
1270 mark = gtk_text_buffer_get_insert(textbuf);
1271 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1272 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1274 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1275 * only defers where it comes to the variable body
1276 * is not null. If no body is present compose->text
1277 * will be null in which case you cannot place the
1278 * cursor inside the component so. An empty component
1279 * is therefore created before placing the cursor
1281 case BODY_FIELD_PRESENT:
1282 cursor_pos = quote_fmt_get_cursor_pos();
1283 if (cursor_pos == -1)
1284 gtk_widget_grab_focus(compose->header_last->entry);
1286 gtk_widget_grab_focus(compose->text);
1290 undo_unblock(compose->undostruct);
1292 if (prefs_common.auto_exteditor)
1293 compose_exec_ext_editor(compose);
1295 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1297 SCROLL_TO_CURSOR(compose);
1299 compose->modified = FALSE;
1300 compose_set_title(compose);
1302 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1307 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1308 gboolean override_pref, const gchar *system)
1310 const gchar *privacy = NULL;
1312 cm_return_if_fail(compose != NULL);
1313 cm_return_if_fail(account != NULL);
1315 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1318 if (account->default_privacy_system && strlen(account->default_privacy_system))
1319 privacy = account->default_privacy_system;
1323 GSList *privacy_avail = privacy_get_system_ids();
1324 if (privacy_avail && g_slist_length(privacy_avail)) {
1325 privacy = (gchar *)(privacy_avail->data);
1327 g_slist_free_full(privacy_avail, g_free);
1329 if (privacy != NULL) {
1331 g_free(compose->privacy_system);
1332 compose->privacy_system = NULL;
1333 g_free(compose->encdata);
1334 compose->encdata = NULL;
1336 if (compose->privacy_system == NULL)
1337 compose->privacy_system = g_strdup(privacy);
1338 else if (*(compose->privacy_system) == '\0') {
1339 g_free(compose->privacy_system);
1340 g_free(compose->encdata);
1341 compose->encdata = NULL;
1342 compose->privacy_system = g_strdup(privacy);
1344 compose_update_privacy_system_menu_item(compose, FALSE);
1345 compose_use_encryption(compose, TRUE);
1349 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1351 const gchar *privacy = NULL;
1353 if (account->default_privacy_system && strlen(account->default_privacy_system))
1354 privacy = account->default_privacy_system;
1358 GSList *privacy_avail = privacy_get_system_ids();
1359 if (privacy_avail && g_slist_length(privacy_avail)) {
1360 privacy = (gchar *)(privacy_avail->data);
1364 if (privacy != NULL) {
1366 g_free(compose->privacy_system);
1367 compose->privacy_system = NULL;
1368 g_free(compose->encdata);
1369 compose->encdata = NULL;
1371 if (compose->privacy_system == NULL)
1372 compose->privacy_system = g_strdup(privacy);
1373 compose_update_privacy_system_menu_item(compose, FALSE);
1374 compose_use_signing(compose, TRUE);
1378 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1382 Compose *compose = NULL;
1384 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1386 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1387 cm_return_val_if_fail(msginfo != NULL, NULL);
1389 list_len = g_slist_length(msginfo_list);
1393 case COMPOSE_REPLY_TO_ADDRESS:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1395 FALSE, prefs_common.default_reply_list, FALSE, body);
1397 case COMPOSE_REPLY_WITH_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1399 FALSE, prefs_common.default_reply_list, FALSE, body);
1401 case COMPOSE_REPLY_WITHOUT_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1403 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1405 case COMPOSE_REPLY_TO_SENDER:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1407 FALSE, FALSE, TRUE, body);
1409 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1410 compose = compose_followup_and_reply_to(msginfo,
1411 COMPOSE_QUOTE_CHECK,
1412 FALSE, FALSE, body);
1414 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1416 FALSE, FALSE, TRUE, body);
1418 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1420 FALSE, FALSE, TRUE, NULL);
1422 case COMPOSE_REPLY_TO_ALL:
1423 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1424 TRUE, FALSE, FALSE, body);
1426 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1427 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1428 TRUE, FALSE, FALSE, body);
1430 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1431 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1432 TRUE, FALSE, FALSE, NULL);
1434 case COMPOSE_REPLY_TO_LIST:
1435 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1436 FALSE, TRUE, FALSE, body);
1438 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1439 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1440 FALSE, TRUE, FALSE, body);
1442 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1443 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1444 FALSE, TRUE, FALSE, NULL);
1446 case COMPOSE_FORWARD:
1447 if (prefs_common.forward_as_attachment) {
1448 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1451 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1455 case COMPOSE_FORWARD_INLINE:
1456 /* check if we reply to more than one Message */
1457 if (list_len == 1) {
1458 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1461 /* more messages FALL THROUGH */
1462 case COMPOSE_FORWARD_AS_ATTACH:
1463 compose = compose_forward_multiple(NULL, msginfo_list);
1465 case COMPOSE_REDIRECT:
1466 compose = compose_redirect(NULL, msginfo, FALSE);
1469 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1472 if (compose == NULL) {
1473 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1477 compose->rmode = mode;
1478 switch (compose->rmode) {
1480 case COMPOSE_REPLY_WITH_QUOTE:
1481 case COMPOSE_REPLY_WITHOUT_QUOTE:
1482 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1483 debug_print("reply mode Normal\n");
1484 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1485 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1487 case COMPOSE_REPLY_TO_SENDER:
1488 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1489 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1490 debug_print("reply mode Sender\n");
1491 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1493 case COMPOSE_REPLY_TO_ALL:
1494 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1495 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1496 debug_print("reply mode All\n");
1497 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1499 case COMPOSE_REPLY_TO_LIST:
1500 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1501 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1502 debug_print("reply mode List\n");
1503 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1505 case COMPOSE_REPLY_TO_ADDRESS:
1506 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1514 static Compose *compose_reply(MsgInfo *msginfo,
1515 ComposeQuoteMode quote_mode,
1521 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1522 to_sender, FALSE, body);
1525 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1526 ComposeQuoteMode quote_mode,
1531 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1532 to_sender, TRUE, body);
1535 static void compose_extract_original_charset(Compose *compose)
1537 MsgInfo *info = NULL;
1538 if (compose->replyinfo) {
1539 info = compose->replyinfo;
1540 } else if (compose->fwdinfo) {
1541 info = compose->fwdinfo;
1542 } else if (compose->targetinfo) {
1543 info = compose->targetinfo;
1546 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1547 MimeInfo *partinfo = mimeinfo;
1548 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1549 partinfo = procmime_mimeinfo_next(partinfo);
1551 compose->orig_charset =
1552 g_strdup(procmime_mimeinfo_get_parameter(
1553 partinfo, "charset"));
1555 procmime_mimeinfo_free_all(&mimeinfo);
1559 #define SIGNAL_BLOCK(buffer) { \
1560 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1561 G_CALLBACK(compose_changed_cb), \
1563 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1564 G_CALLBACK(text_inserted), \
1568 #define SIGNAL_UNBLOCK(buffer) { \
1569 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1570 G_CALLBACK(compose_changed_cb), \
1572 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1573 G_CALLBACK(text_inserted), \
1577 static Compose *compose_generic_reply(MsgInfo *msginfo,
1578 ComposeQuoteMode quote_mode,
1579 gboolean to_all, gboolean to_ml,
1581 gboolean followup_and_reply_to,
1585 PrefsAccount *account = NULL;
1586 GtkTextView *textview;
1587 GtkTextBuffer *textbuf;
1588 gboolean quote = FALSE;
1589 const gchar *qmark = NULL;
1590 const gchar *body_fmt = NULL;
1591 gchar *s_system = NULL;
1593 cm_return_val_if_fail(msginfo != NULL, NULL);
1594 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1596 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1598 cm_return_val_if_fail(account != NULL, NULL);
1600 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1602 compose->updating = TRUE;
1604 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1605 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1607 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1608 if (!compose->replyinfo)
1609 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1611 compose_extract_original_charset(compose);
1613 if (msginfo->folder && msginfo->folder->ret_rcpt)
1614 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1616 /* Set save folder */
1617 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1618 gchar *folderidentifier;
1620 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1621 folderidentifier = folder_item_get_identifier(msginfo->folder);
1622 compose_set_save_to(compose, folderidentifier);
1623 g_free(folderidentifier);
1626 if (compose_parse_header(compose, msginfo) < 0) {
1627 compose->updating = FALSE;
1628 compose_destroy(compose);
1632 /* override from name according to folder properties */
1633 if (msginfo->folder && msginfo->folder->prefs &&
1634 msginfo->folder->prefs->reply_with_format &&
1635 msginfo->folder->prefs->reply_override_from_format &&
1636 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1641 /* decode \-escape sequences in the internal representation of the quote format */
1642 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1643 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1646 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1647 compose->gtkaspell);
1649 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1651 quote_fmt_scan_string(tmp);
1654 buf = quote_fmt_get_buffer();
1656 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1658 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1659 quote_fmt_reset_vartable();
1660 quote_fmtlex_destroy();
1665 textview = (GTK_TEXT_VIEW(compose->text));
1666 textbuf = gtk_text_view_get_buffer(textview);
1667 compose_create_tags(textview, compose);
1669 undo_block(compose->undostruct);
1671 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1672 gtkaspell_block_check(compose->gtkaspell);
1675 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1676 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1677 /* use the reply format of folder (if enabled), or the account's one
1678 (if enabled) or fallback to the global reply format, which is always
1679 enabled (even if empty), and use the relevant quotemark */
1681 if (msginfo->folder && msginfo->folder->prefs &&
1682 msginfo->folder->prefs->reply_with_format) {
1683 qmark = msginfo->folder->prefs->reply_quotemark;
1684 body_fmt = msginfo->folder->prefs->reply_body_format;
1686 } else if (account->reply_with_format) {
1687 qmark = account->reply_quotemark;
1688 body_fmt = account->reply_body_format;
1691 qmark = prefs_common.quotemark;
1692 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1693 body_fmt = gettext(prefs_common.quotefmt);
1700 /* empty quotemark is not allowed */
1701 if (qmark == NULL || *qmark == '\0')
1703 compose_quote_fmt(compose, compose->replyinfo,
1704 body_fmt, qmark, body, FALSE, TRUE,
1705 _("The body of the \"Reply\" template has an error at line %d."));
1706 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1707 quote_fmt_reset_vartable();
1710 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1711 compose_force_encryption(compose, account, FALSE, s_system);
1714 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1715 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1716 compose_force_signing(compose, account, s_system);
1720 SIGNAL_BLOCK(textbuf);
1722 if (account->auto_sig)
1723 compose_insert_sig(compose, FALSE);
1725 compose_wrap_all(compose);
1728 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1729 gtkaspell_highlight_all(compose->gtkaspell);
1730 gtkaspell_unblock_check(compose->gtkaspell);
1732 SIGNAL_UNBLOCK(textbuf);
1734 gtk_widget_grab_focus(compose->text);
1736 undo_unblock(compose->undostruct);
1738 if (prefs_common.auto_exteditor)
1739 compose_exec_ext_editor(compose);
1741 compose->modified = FALSE;
1742 compose_set_title(compose);
1744 compose->updating = FALSE;
1745 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1746 SCROLL_TO_CURSOR(compose);
1748 if (compose->deferred_destroy) {
1749 compose_destroy(compose);
1757 #define INSERT_FW_HEADER(var, hdr) \
1758 if (msginfo->var && *msginfo->var) { \
1759 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1760 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1761 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1764 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1765 gboolean as_attach, const gchar *body,
1766 gboolean no_extedit,
1770 GtkTextView *textview;
1771 GtkTextBuffer *textbuf;
1772 gint cursor_pos = -1;
1775 cm_return_val_if_fail(msginfo != NULL, NULL);
1776 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1778 if (!account && !(account = compose_find_account(msginfo)))
1779 account = cur_account;
1781 if (!prefs_common.forward_as_attachment)
1782 mode = COMPOSE_FORWARD_INLINE;
1784 mode = COMPOSE_FORWARD;
1785 compose = compose_create(account, msginfo->folder, mode, batch);
1787 compose->updating = TRUE;
1788 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1789 if (!compose->fwdinfo)
1790 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1792 compose_extract_original_charset(compose);
1794 if (msginfo->subject && *msginfo->subject) {
1795 gchar *buf, *buf2, *p;
1797 buf = p = g_strdup(msginfo->subject);
1798 p += subject_get_prefix_length(p);
1799 memmove(buf, p, strlen(p) + 1);
1801 buf2 = g_strdup_printf("Fw: %s", buf);
1802 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1808 /* override from name according to folder properties */
1809 if (msginfo->folder && msginfo->folder->prefs &&
1810 msginfo->folder->prefs->forward_with_format &&
1811 msginfo->folder->prefs->forward_override_from_format &&
1812 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1816 MsgInfo *full_msginfo = NULL;
1819 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1821 full_msginfo = procmsg_msginfo_copy(msginfo);
1823 /* decode \-escape sequences in the internal representation of the quote format */
1824 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1825 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1828 gtkaspell_block_check(compose->gtkaspell);
1829 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1830 compose->gtkaspell);
1832 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1834 quote_fmt_scan_string(tmp);
1837 buf = quote_fmt_get_buffer();
1839 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1841 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1842 quote_fmt_reset_vartable();
1843 quote_fmtlex_destroy();
1846 procmsg_msginfo_free(&full_msginfo);
1849 textview = GTK_TEXT_VIEW(compose->text);
1850 textbuf = gtk_text_view_get_buffer(textview);
1851 compose_create_tags(textview, compose);
1853 undo_block(compose->undostruct);
1857 msgfile = procmsg_get_message_file(msginfo);
1858 if (!is_file_exist(msgfile))
1859 g_warning("%s: file does not exist", msgfile);
1861 compose_attach_append(compose, msgfile, msgfile,
1862 "message/rfc822", NULL);
1866 const gchar *qmark = NULL;
1867 const gchar *body_fmt = NULL;
1868 MsgInfo *full_msginfo;
1870 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1872 full_msginfo = procmsg_msginfo_copy(msginfo);
1874 /* use the forward format of folder (if enabled), or the account's one
1875 (if enabled) or fallback to the global forward format, which is always
1876 enabled (even if empty), and use the relevant quotemark */
1877 if (msginfo->folder && msginfo->folder->prefs &&
1878 msginfo->folder->prefs->forward_with_format) {
1879 qmark = msginfo->folder->prefs->forward_quotemark;
1880 body_fmt = msginfo->folder->prefs->forward_body_format;
1882 } else if (account->forward_with_format) {
1883 qmark = account->forward_quotemark;
1884 body_fmt = account->forward_body_format;
1887 qmark = prefs_common.fw_quotemark;
1888 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1889 body_fmt = gettext(prefs_common.fw_quotefmt);
1894 /* empty quotemark is not allowed */
1895 if (qmark == NULL || *qmark == '\0')
1898 compose_quote_fmt(compose, full_msginfo,
1899 body_fmt, qmark, body, FALSE, TRUE,
1900 _("The body of the \"Forward\" template has an error at line %d."));
1901 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1902 quote_fmt_reset_vartable();
1903 compose_attach_parts(compose, msginfo);
1905 procmsg_msginfo_free(&full_msginfo);
1908 SIGNAL_BLOCK(textbuf);
1910 if (account->auto_sig)
1911 compose_insert_sig(compose, FALSE);
1913 compose_wrap_all(compose);
1916 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1917 gtkaspell_highlight_all(compose->gtkaspell);
1918 gtkaspell_unblock_check(compose->gtkaspell);
1920 SIGNAL_UNBLOCK(textbuf);
1922 cursor_pos = quote_fmt_get_cursor_pos();
1923 if (cursor_pos == -1)
1924 gtk_widget_grab_focus(compose->header_last->entry);
1926 gtk_widget_grab_focus(compose->text);
1928 if (!no_extedit && prefs_common.auto_exteditor)
1929 compose_exec_ext_editor(compose);
1932 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1933 gchar *folderidentifier;
1935 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1936 folderidentifier = folder_item_get_identifier(msginfo->folder);
1937 compose_set_save_to(compose, folderidentifier);
1938 g_free(folderidentifier);
1941 undo_unblock(compose->undostruct);
1943 compose->modified = FALSE;
1944 compose_set_title(compose);
1946 compose->updating = FALSE;
1947 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1948 SCROLL_TO_CURSOR(compose);
1950 if (compose->deferred_destroy) {
1951 compose_destroy(compose);
1955 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1960 #undef INSERT_FW_HEADER
1962 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1965 GtkTextView *textview;
1966 GtkTextBuffer *textbuf;
1970 gboolean single_mail = TRUE;
1972 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1974 if (g_slist_length(msginfo_list) > 1)
1975 single_mail = FALSE;
1977 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1978 if (((MsgInfo *)msginfo->data)->folder == NULL)
1981 /* guess account from first selected message */
1983 !(account = compose_find_account(msginfo_list->data)))
1984 account = cur_account;
1986 cm_return_val_if_fail(account != NULL, NULL);
1988 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1989 if (msginfo->data) {
1990 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1991 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1995 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1996 g_warning("no msginfo_list");
2000 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
2002 compose->updating = TRUE;
2004 /* override from name according to folder properties */
2005 if (msginfo_list->data) {
2006 MsgInfo *msginfo = msginfo_list->data;
2008 if (msginfo->folder && msginfo->folder->prefs &&
2009 msginfo->folder->prefs->forward_with_format &&
2010 msginfo->folder->prefs->forward_override_from_format &&
2011 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2016 /* decode \-escape sequences in the internal representation of the quote format */
2017 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2018 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2022 compose->gtkaspell);
2024 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2026 quote_fmt_scan_string(tmp);
2029 buf = quote_fmt_get_buffer();
2031 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2033 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2034 quote_fmt_reset_vartable();
2035 quote_fmtlex_destroy();
2041 textview = GTK_TEXT_VIEW(compose->text);
2042 textbuf = gtk_text_view_get_buffer(textview);
2043 compose_create_tags(textview, compose);
2045 undo_block(compose->undostruct);
2046 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2047 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2049 if (!is_file_exist(msgfile))
2050 g_warning("%s: file does not exist", msgfile);
2052 compose_attach_append(compose, msgfile, msgfile,
2053 "message/rfc822", NULL);
2058 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2059 if (info->subject && *info->subject) {
2060 gchar *buf, *buf2, *p;
2062 buf = p = g_strdup(info->subject);
2063 p += subject_get_prefix_length(p);
2064 memmove(buf, p, strlen(p) + 1);
2066 buf2 = g_strdup_printf("Fw: %s", buf);
2067 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2073 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2074 _("Fw: multiple emails"));
2077 SIGNAL_BLOCK(textbuf);
2079 if (account->auto_sig)
2080 compose_insert_sig(compose, FALSE);
2082 compose_wrap_all(compose);
2084 SIGNAL_UNBLOCK(textbuf);
2086 gtk_text_buffer_get_start_iter(textbuf, &iter);
2087 gtk_text_buffer_place_cursor(textbuf, &iter);
2089 if (prefs_common.auto_exteditor)
2090 compose_exec_ext_editor(compose);
2092 gtk_widget_grab_focus(compose->header_last->entry);
2093 undo_unblock(compose->undostruct);
2094 compose->modified = FALSE;
2095 compose_set_title(compose);
2097 compose->updating = FALSE;
2098 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2099 SCROLL_TO_CURSOR(compose);
2101 if (compose->deferred_destroy) {
2102 compose_destroy(compose);
2106 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2111 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2113 GtkTextIter start = *iter;
2114 GtkTextIter end_iter;
2115 int start_pos = gtk_text_iter_get_offset(&start);
2117 if (!compose->account->sig_sep)
2120 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2121 start_pos+strlen(compose->account->sig_sep));
2123 /* check sig separator */
2124 str = gtk_text_iter_get_text(&start, &end_iter);
2125 if (!strcmp(str, compose->account->sig_sep)) {
2127 /* check end of line (\n) */
2128 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2129 start_pos+strlen(compose->account->sig_sep));
2130 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2131 start_pos+strlen(compose->account->sig_sep)+1);
2132 tmp = gtk_text_iter_get_text(&start, &end_iter);
2133 if (!strcmp(tmp,"\n")) {
2145 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2147 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2148 Compose *compose = (Compose *)data;
2149 FolderItem *old_item = NULL;
2150 FolderItem *new_item = NULL;
2151 gchar *old_id, *new_id;
2153 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2154 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2157 old_item = hookdata->item;
2158 new_item = hookdata->item2;
2160 old_id = folder_item_get_identifier(old_item);
2161 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2163 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2164 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2165 compose->targetinfo->folder = new_item;
2168 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2169 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2170 compose->replyinfo->folder = new_item;
2173 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2174 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2175 compose->fwdinfo->folder = new_item;
2183 static void compose_colorize_signature(Compose *compose)
2185 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2187 GtkTextIter end_iter;
2188 gtk_text_buffer_get_start_iter(buffer, &iter);
2189 while (gtk_text_iter_forward_line(&iter))
2190 if (compose_is_sig_separator(compose, buffer, &iter)) {
2191 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2192 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2196 #define BLOCK_WRAP() { \
2197 prev_autowrap = compose->autowrap; \
2198 buffer = gtk_text_view_get_buffer( \
2199 GTK_TEXT_VIEW(compose->text)); \
2200 compose->autowrap = FALSE; \
2202 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2203 G_CALLBACK(compose_changed_cb), \
2205 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2206 G_CALLBACK(text_inserted), \
2209 #define UNBLOCK_WRAP() { \
2210 compose->autowrap = prev_autowrap; \
2211 if (compose->autowrap) { \
2212 gint old = compose->draft_timeout_tag; \
2213 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2214 compose_wrap_all(compose); \
2215 compose->draft_timeout_tag = old; \
2218 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2219 G_CALLBACK(compose_changed_cb), \
2221 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2222 G_CALLBACK(text_inserted), \
2226 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2228 Compose *compose = NULL;
2229 PrefsAccount *account = NULL;
2230 GtkTextView *textview;
2231 GtkTextBuffer *textbuf;
2235 gboolean use_signing = FALSE;
2236 gboolean use_encryption = FALSE;
2237 gchar *privacy_system = NULL;
2238 int priority = PRIORITY_NORMAL;
2239 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2240 gboolean autowrap = prefs_common.autowrap;
2241 gboolean autoindent = prefs_common.auto_indent;
2242 HeaderEntry *manual_headers = NULL;
2244 cm_return_val_if_fail(msginfo != NULL, NULL);
2245 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2247 if (compose_put_existing_to_front(msginfo)) {
2251 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2252 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2253 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2254 gchar *queueheader_buf = NULL;
2257 /* Select Account from queue headers */
2258 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2259 "X-Claws-Account-Id:")) {
2260 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2261 account = account_find_from_id(id);
2262 g_free(queueheader_buf);
2264 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2265 "X-Sylpheed-Account-Id:")) {
2266 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2267 account = account_find_from_id(id);
2268 g_free(queueheader_buf);
2270 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2272 id = atoi(&queueheader_buf[strlen("NAID:")]);
2273 account = account_find_from_id(id);
2274 g_free(queueheader_buf);
2276 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2278 id = atoi(&queueheader_buf[strlen("MAID:")]);
2279 account = account_find_from_id(id);
2280 g_free(queueheader_buf);
2282 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2284 account = account_find_from_address(queueheader_buf, FALSE);
2285 g_free(queueheader_buf);
2287 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2289 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2290 use_signing = param;
2291 g_free(queueheader_buf);
2293 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2294 "X-Sylpheed-Sign:")) {
2295 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2296 use_signing = param;
2297 g_free(queueheader_buf);
2299 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2300 "X-Claws-Encrypt:")) {
2301 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2302 use_encryption = param;
2303 g_free(queueheader_buf);
2305 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2306 "X-Sylpheed-Encrypt:")) {
2307 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2308 use_encryption = param;
2309 g_free(queueheader_buf);
2311 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2312 "X-Claws-Auto-Wrapping:")) {
2313 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2315 g_free(queueheader_buf);
2317 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2318 "X-Claws-Auto-Indent:")) {
2319 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2321 g_free(queueheader_buf);
2323 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2324 "X-Claws-Privacy-System:")) {
2325 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2326 g_free(queueheader_buf);
2328 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2329 "X-Sylpheed-Privacy-System:")) {
2330 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2331 g_free(queueheader_buf);
2333 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2335 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2337 g_free(queueheader_buf);
2339 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2341 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2342 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2343 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2344 if (orig_item != NULL) {
2345 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2349 g_free(queueheader_buf);
2351 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2353 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2354 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2355 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2356 if (orig_item != NULL) {
2357 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2361 g_free(queueheader_buf);
2363 /* Get manual headers */
2364 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2365 "X-Claws-Manual-Headers:")) {
2366 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2367 if (listmh && *listmh != '\0') {
2368 debug_print("Got manual headers: %s\n", listmh);
2369 manual_headers = procheader_entries_from_str(listmh);
2372 g_free(queueheader_buf);
2375 account = msginfo->folder->folder->account;
2378 if (!account && prefs_common.reedit_account_autosel) {
2380 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2381 extract_address(from);
2382 account = account_find_from_address(from, FALSE);
2387 account = cur_account;
2389 cm_return_val_if_fail(account != NULL, NULL);
2391 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2393 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2394 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2395 compose->autowrap = autowrap;
2396 compose->replyinfo = replyinfo;
2397 compose->fwdinfo = fwdinfo;
2399 compose->updating = TRUE;
2400 compose->priority = priority;
2402 if (privacy_system != NULL) {
2403 compose->privacy_system = privacy_system;
2404 compose_use_signing(compose, use_signing);
2405 compose_use_encryption(compose, use_encryption);
2406 compose_update_privacy_system_menu_item(compose, FALSE);
2408 activate_privacy_system(compose, account, FALSE);
2411 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2413 compose_extract_original_charset(compose);
2415 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2416 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2417 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2418 gchar *queueheader_buf = NULL;
2420 /* Set message save folder */
2421 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2422 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2423 compose_set_save_to(compose, &queueheader_buf[4]);
2424 g_free(queueheader_buf);
2426 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2427 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2429 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2431 g_free(queueheader_buf);
2435 if (compose_parse_header(compose, msginfo) < 0) {
2436 compose->updating = FALSE;
2437 compose_destroy(compose);
2440 compose_reedit_set_entry(compose, msginfo);
2442 textview = GTK_TEXT_VIEW(compose->text);
2443 textbuf = gtk_text_view_get_buffer(textview);
2444 compose_create_tags(textview, compose);
2446 mark = gtk_text_buffer_get_insert(textbuf);
2447 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2449 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2450 G_CALLBACK(compose_changed_cb),
2453 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2454 fp = procmime_get_first_encrypted_text_content(msginfo);
2456 compose_force_encryption(compose, account, TRUE, NULL);
2459 fp = procmime_get_first_text_content(msginfo);
2462 g_warning("Can't get text part");
2466 gchar buf[BUFFSIZE];
2467 gboolean prev_autowrap;
2468 GtkTextBuffer *buffer;
2470 while (fgets(buf, sizeof(buf), fp) != NULL) {
2472 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2478 compose_attach_parts(compose, msginfo);
2480 compose_colorize_signature(compose);
2482 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2483 G_CALLBACK(compose_changed_cb),
2486 if (manual_headers != NULL) {
2487 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2488 procheader_entries_free(manual_headers);
2489 compose->updating = FALSE;
2490 compose_destroy(compose);
2493 procheader_entries_free(manual_headers);
2496 gtk_widget_grab_focus(compose->text);
2498 if (prefs_common.auto_exteditor) {
2499 compose_exec_ext_editor(compose);
2501 compose->modified = FALSE;
2502 compose_set_title(compose);
2504 compose->updating = FALSE;
2505 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2506 SCROLL_TO_CURSOR(compose);
2508 if (compose->deferred_destroy) {
2509 compose_destroy(compose);
2513 compose->sig_str = account_get_signature_str(compose->account);
2515 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2520 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2527 cm_return_val_if_fail(msginfo != NULL, NULL);
2530 account = account_get_reply_account(msginfo,
2531 prefs_common.reply_account_autosel);
2532 cm_return_val_if_fail(account != NULL, NULL);
2534 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2536 compose->updating = TRUE;
2538 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2539 compose->replyinfo = NULL;
2540 compose->fwdinfo = NULL;
2542 compose_show_first_last_header(compose, TRUE);
2544 gtk_widget_grab_focus(compose->header_last->entry);
2546 filename = procmsg_get_message_file(msginfo);
2548 if (filename == NULL) {
2549 compose->updating = FALSE;
2550 compose_destroy(compose);
2555 compose->redirect_filename = filename;
2557 /* Set save folder */
2558 item = msginfo->folder;
2559 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2560 gchar *folderidentifier;
2562 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2563 folderidentifier = folder_item_get_identifier(item);
2564 compose_set_save_to(compose, folderidentifier);
2565 g_free(folderidentifier);
2568 compose_attach_parts(compose, msginfo);
2570 if (msginfo->subject)
2571 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2573 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2575 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2576 _("The body of the \"Redirect\" template has an error at line %d."));
2577 quote_fmt_reset_vartable();
2578 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2580 compose_colorize_signature(compose);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2597 if (compose->toolbar->draft_btn)
2598 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2599 if (compose->toolbar->insert_btn)
2600 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2601 if (compose->toolbar->attach_btn)
2602 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2603 if (compose->toolbar->sig_btn)
2604 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2605 if (compose->toolbar->exteditor_btn)
2606 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2607 if (compose->toolbar->linewrap_current_btn)
2608 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2609 if (compose->toolbar->linewrap_all_btn)
2610 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2612 compose->modified = FALSE;
2613 compose_set_title(compose);
2614 compose->updating = FALSE;
2615 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2616 SCROLL_TO_CURSOR(compose);
2618 if (compose->deferred_destroy) {
2619 compose_destroy(compose);
2623 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2628 const GList *compose_get_compose_list(void)
2630 return compose_list;
2633 void compose_entry_append(Compose *compose, const gchar *address,
2634 ComposeEntryType type, ComposePrefType pref_type)
2636 const gchar *header;
2638 gboolean in_quote = FALSE;
2639 if (!address || *address == '\0') return;
2646 header = N_("Bcc:");
2648 case COMPOSE_REPLYTO:
2649 header = N_("Reply-To:");
2651 case COMPOSE_NEWSGROUPS:
2652 header = N_("Newsgroups:");
2654 case COMPOSE_FOLLOWUPTO:
2655 header = N_( "Followup-To:");
2657 case COMPOSE_INREPLYTO:
2658 header = N_( "In-Reply-To:");
2665 header = prefs_common_translated_header_name(header);
2667 cur = begin = (gchar *)address;
2669 /* we separate the line by commas, but not if we're inside a quoted
2671 while (*cur != '\0') {
2673 in_quote = !in_quote;
2674 if (*cur == ',' && !in_quote) {
2675 gchar *tmp = g_strdup(begin);
2677 tmp[cur-begin]='\0';
2680 while (*tmp == ' ' || *tmp == '\t')
2682 compose_add_header_entry(compose, header, tmp, pref_type);
2683 compose_entry_indicate(compose, tmp);
2690 gchar *tmp = g_strdup(begin);
2692 tmp[cur-begin]='\0';
2693 while (*tmp == ' ' || *tmp == '\t')
2695 compose_add_header_entry(compose, header, tmp, pref_type);
2696 compose_entry_indicate(compose, tmp);
2701 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2706 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2707 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2708 if (gtk_entry_get_text(entry) &&
2709 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2710 gtk_widget_modify_base(
2711 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2712 GTK_STATE_NORMAL, &default_header_bgcolor);
2713 gtk_widget_modify_text(
2714 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2715 GTK_STATE_NORMAL, &default_header_color);
2720 void compose_toolbar_cb(gint action, gpointer data)
2722 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2723 Compose *compose = (Compose*)toolbar_item->parent;
2725 cm_return_if_fail(compose != NULL);
2729 compose_send_cb(NULL, compose);
2732 compose_send_later_cb(NULL, compose);
2735 compose_draft(compose, COMPOSE_QUIT_EDITING);
2738 compose_insert_file_cb(NULL, compose);
2741 compose_attach_cb(NULL, compose);
2744 compose_insert_sig(compose, FALSE);
2747 compose_insert_sig(compose, TRUE);
2750 compose_ext_editor_cb(NULL, compose);
2752 case A_LINEWRAP_CURRENT:
2753 compose_beautify_paragraph(compose, NULL, TRUE);
2755 case A_LINEWRAP_ALL:
2756 compose_wrap_all_full(compose, TRUE);
2759 compose_address_cb(NULL, compose);
2762 case A_CHECK_SPELLING:
2763 compose_check_all(NULL, compose);
2766 case A_PRIVACY_SIGN:
2768 case A_PRIVACY_ENCRYPT:
2775 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2780 gchar *subject = NULL;
2784 gchar **attach = NULL;
2785 gchar *inreplyto = NULL;
2786 MailField mfield = NO_FIELD_PRESENT;
2788 /* get mailto parts but skip from */
2789 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2792 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2793 mfield = TO_FIELD_PRESENT;
2796 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2798 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2800 if (!g_utf8_validate (subject, -1, NULL)) {
2801 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2802 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2805 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2807 mfield = SUBJECT_FIELD_PRESENT;
2810 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2811 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2814 gboolean prev_autowrap = compose->autowrap;
2816 compose->autowrap = FALSE;
2818 mark = gtk_text_buffer_get_insert(buffer);
2819 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2821 if (!g_utf8_validate (body, -1, NULL)) {
2822 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2823 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2826 gtk_text_buffer_insert(buffer, &iter, body, -1);
2828 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2830 compose->autowrap = prev_autowrap;
2831 if (compose->autowrap)
2832 compose_wrap_all(compose);
2833 mfield = BODY_FIELD_PRESENT;
2837 gint i = 0, att = 0;
2838 gchar *warn_files = NULL;
2839 while (attach[i] != NULL) {
2840 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2841 if (utf8_filename) {
2842 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2843 gchar *tmp = g_strdup_printf("%s%s\n",
2844 warn_files?warn_files:"",
2850 g_free(utf8_filename);
2852 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2857 alertpanel_notice(ngettext(
2858 "The following file has been attached: \n%s",
2859 "The following files have been attached: \n%s", att), warn_files);
2864 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2877 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2879 static HeaderEntry hentry[] = {
2880 {"Reply-To:", NULL, TRUE },
2881 {"Cc:", NULL, TRUE },
2882 {"References:", NULL, FALSE },
2883 {"Bcc:", NULL, TRUE },
2884 {"Newsgroups:", NULL, TRUE },
2885 {"Followup-To:", NULL, TRUE },
2886 {"List-Post:", NULL, FALSE },
2887 {"X-Priority:", NULL, FALSE },
2888 {NULL, NULL, FALSE }
2905 cm_return_val_if_fail(msginfo != NULL, -1);
2907 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2908 procheader_get_header_fields(fp, hentry);
2911 if (hentry[H_REPLY_TO].body != NULL) {
2912 if (hentry[H_REPLY_TO].body[0] != '\0') {
2914 conv_unmime_header(hentry[H_REPLY_TO].body,
2917 g_free(hentry[H_REPLY_TO].body);
2918 hentry[H_REPLY_TO].body = NULL;
2920 if (hentry[H_CC].body != NULL) {
2921 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2922 g_free(hentry[H_CC].body);
2923 hentry[H_CC].body = NULL;
2925 if (hentry[H_REFERENCES].body != NULL) {
2926 if (compose->mode == COMPOSE_REEDIT)
2927 compose->references = hentry[H_REFERENCES].body;
2929 compose->references = compose_parse_references
2930 (hentry[H_REFERENCES].body, msginfo->msgid);
2931 g_free(hentry[H_REFERENCES].body);
2933 hentry[H_REFERENCES].body = NULL;
2935 if (hentry[H_BCC].body != NULL) {
2936 if (compose->mode == COMPOSE_REEDIT)
2938 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2939 g_free(hentry[H_BCC].body);
2940 hentry[H_BCC].body = NULL;
2942 if (hentry[H_NEWSGROUPS].body != NULL) {
2943 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2944 hentry[H_NEWSGROUPS].body = NULL;
2946 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2947 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2948 compose->followup_to =
2949 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2952 g_free(hentry[H_FOLLOWUP_TO].body);
2953 hentry[H_FOLLOWUP_TO].body = NULL;
2955 if (hentry[H_LIST_POST].body != NULL) {
2956 gchar *to = NULL, *start = NULL;
2958 extract_address(hentry[H_LIST_POST].body);
2959 if (hentry[H_LIST_POST].body[0] != '\0') {
2960 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2962 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2963 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2966 g_free(compose->ml_post);
2967 compose->ml_post = to;
2970 g_free(hentry[H_LIST_POST].body);
2971 hentry[H_LIST_POST].body = NULL;
2974 /* CLAWS - X-Priority */
2975 if (compose->mode == COMPOSE_REEDIT)
2976 if (hentry[H_X_PRIORITY].body != NULL) {
2979 priority = atoi(hentry[H_X_PRIORITY].body);
2980 g_free(hentry[H_X_PRIORITY].body);
2982 hentry[H_X_PRIORITY].body = NULL;
2984 if (priority < PRIORITY_HIGHEST ||
2985 priority > PRIORITY_LOWEST)
2986 priority = PRIORITY_NORMAL;
2988 compose->priority = priority;
2991 if (compose->mode == COMPOSE_REEDIT) {
2992 if (msginfo->inreplyto && *msginfo->inreplyto)
2993 compose->inreplyto = g_strdup(msginfo->inreplyto);
2995 if (msginfo->msgid && *msginfo->msgid &&
2996 compose->folder != NULL &&
2997 compose->folder->stype == F_DRAFT)
2998 compose->msgid = g_strdup(msginfo->msgid);
3000 if (msginfo->msgid && *msginfo->msgid)
3001 compose->inreplyto = g_strdup(msginfo->msgid);
3003 if (!compose->references) {
3004 if (msginfo->msgid && *msginfo->msgid) {
3005 if (msginfo->inreplyto && *msginfo->inreplyto)
3006 compose->references =
3007 g_strdup_printf("<%s>\n\t<%s>",
3011 compose->references =
3012 g_strconcat("<", msginfo->msgid, ">",
3014 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3015 compose->references =
3016 g_strconcat("<", msginfo->inreplyto, ">",
3025 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3030 cm_return_val_if_fail(msginfo != NULL, -1);
3032 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3033 procheader_get_header_fields(fp, entries);
3037 while (he != NULL && he->name != NULL) {
3039 GtkListStore *model = NULL;
3041 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3042 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3043 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3044 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3045 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3052 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3054 GSList *ref_id_list, *cur;
3058 ref_id_list = references_list_append(NULL, ref);
3059 if (!ref_id_list) return NULL;
3060 if (msgid && *msgid)
3061 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3066 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3067 /* "<" + Message-ID + ">" + CR+LF+TAB */
3068 len += strlen((gchar *)cur->data) + 5;
3070 if (len > MAX_REFERENCES_LEN) {
3071 /* remove second message-ID */
3072 if (ref_id_list && ref_id_list->next &&
3073 ref_id_list->next->next) {
3074 g_free(ref_id_list->next->data);
3075 ref_id_list = g_slist_remove
3076 (ref_id_list, ref_id_list->next->data);
3078 slist_free_strings_full(ref_id_list);
3085 new_ref = g_string_new("");
3086 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3087 if (new_ref->len > 0)
3088 g_string_append(new_ref, "\n\t");
3089 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3092 slist_free_strings_full(ref_id_list);
3094 new_ref_str = new_ref->str;
3095 g_string_free(new_ref, FALSE);
3100 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3101 const gchar *fmt, const gchar *qmark,
3102 const gchar *body, gboolean rewrap,
3103 gboolean need_unescape,
3104 const gchar *err_msg)
3106 MsgInfo* dummyinfo = NULL;
3107 gchar *quote_str = NULL;
3109 gboolean prev_autowrap;
3110 const gchar *trimmed_body = body;
3111 gint cursor_pos = -1;
3112 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3113 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3118 SIGNAL_BLOCK(buffer);
3121 dummyinfo = compose_msginfo_new_from_compose(compose);
3122 msginfo = dummyinfo;
3125 if (qmark != NULL) {
3127 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3128 compose->gtkaspell);
3130 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3132 quote_fmt_scan_string(qmark);
3135 buf = quote_fmt_get_buffer();
3138 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3140 Xstrdup_a(quote_str, buf, goto error)
3143 if (fmt && *fmt != '\0') {
3146 while (*trimmed_body == '\n')
3150 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3151 compose->gtkaspell);
3153 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3155 if (need_unescape) {
3158 /* decode \-escape sequences in the internal representation of the quote format */
3159 tmp = g_malloc(strlen(fmt)+1);
3160 pref_get_unescaped_pref(tmp, fmt);
3161 quote_fmt_scan_string(tmp);
3165 quote_fmt_scan_string(fmt);
3169 buf = quote_fmt_get_buffer();
3172 gint line = quote_fmt_get_line();
3173 alertpanel_error(err_msg, line);
3181 prev_autowrap = compose->autowrap;
3182 compose->autowrap = FALSE;
3184 mark = gtk_text_buffer_get_insert(buffer);
3185 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3186 if (g_utf8_validate(buf, -1, NULL)) {
3187 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3189 gchar *tmpout = NULL;
3190 tmpout = conv_codeset_strdup
3191 (buf, conv_get_locale_charset_str_no_utf8(),
3193 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3195 tmpout = g_malloc(strlen(buf)*2+1);
3196 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3198 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3202 cursor_pos = quote_fmt_get_cursor_pos();
3203 if (cursor_pos == -1)
3204 cursor_pos = gtk_text_iter_get_offset(&iter);
3205 compose->set_cursor_pos = cursor_pos;
3207 gtk_text_buffer_get_start_iter(buffer, &iter);
3208 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3209 gtk_text_buffer_place_cursor(buffer, &iter);
3211 compose->autowrap = prev_autowrap;
3212 if (compose->autowrap && rewrap)
3213 compose_wrap_all(compose);
3220 SIGNAL_UNBLOCK(buffer);
3222 procmsg_msginfo_free( &dummyinfo );
3227 /* if ml_post is of type addr@host and from is of type
3228 * addr-anything@host, return TRUE
3230 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3232 gchar *left_ml = NULL;
3233 gchar *right_ml = NULL;
3234 gchar *left_from = NULL;
3235 gchar *right_from = NULL;
3236 gboolean result = FALSE;
3238 if (!ml_post || !from)
3241 left_ml = g_strdup(ml_post);
3242 if (strstr(left_ml, "@")) {
3243 right_ml = strstr(left_ml, "@")+1;
3244 *(strstr(left_ml, "@")) = '\0';
3247 left_from = g_strdup(from);
3248 if (strstr(left_from, "@")) {
3249 right_from = strstr(left_from, "@")+1;
3250 *(strstr(left_from, "@")) = '\0';
3253 if (right_ml && right_from
3254 && !strncmp(left_from, left_ml, strlen(left_ml))
3255 && !strcmp(right_from, right_ml)) {
3264 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3265 gboolean respect_default_to)
3269 if (!folder || !folder->prefs)
3272 if (respect_default_to && folder->prefs->enable_default_to) {
3273 compose_entry_append(compose, folder->prefs->default_to,
3274 COMPOSE_TO, PREF_FOLDER);
3275 compose_entry_indicate(compose, folder->prefs->default_to);
3277 if (folder->prefs->enable_default_cc) {
3278 compose_entry_append(compose, folder->prefs->default_cc,
3279 COMPOSE_CC, PREF_FOLDER);
3280 compose_entry_indicate(compose, folder->prefs->default_cc);
3282 if (folder->prefs->enable_default_bcc) {
3283 compose_entry_append(compose, folder->prefs->default_bcc,
3284 COMPOSE_BCC, PREF_FOLDER);
3285 compose_entry_indicate(compose, folder->prefs->default_bcc);
3287 if (folder->prefs->enable_default_replyto) {
3288 compose_entry_append(compose, folder->prefs->default_replyto,
3289 COMPOSE_REPLYTO, PREF_FOLDER);
3290 compose_entry_indicate(compose, folder->prefs->default_replyto);
3294 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3299 if (!compose || !msginfo)
3302 if (msginfo->subject && *msginfo->subject) {
3303 buf = p = g_strdup(msginfo->subject);
3304 p += subject_get_prefix_length(p);
3305 memmove(buf, p, strlen(p) + 1);
3307 buf2 = g_strdup_printf("Re: %s", buf);
3308 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3313 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3316 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3317 gboolean to_all, gboolean to_ml,
3319 gboolean followup_and_reply_to)
3321 GSList *cc_list = NULL;
3324 gchar *replyto = NULL;
3325 gchar *ac_email = NULL;
3327 gboolean reply_to_ml = FALSE;
3328 gboolean default_reply_to = FALSE;
3330 cm_return_if_fail(compose->account != NULL);
3331 cm_return_if_fail(msginfo != NULL);
3333 reply_to_ml = to_ml && compose->ml_post;
3335 default_reply_to = msginfo->folder &&
3336 msginfo->folder->prefs->enable_default_reply_to;
3338 if (compose->account->protocol != A_NNTP) {
3339 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3341 if (reply_to_ml && !default_reply_to) {
3343 gboolean is_subscr = is_subscription(compose->ml_post,
3346 /* normal answer to ml post with a reply-to */
3347 compose_entry_append(compose,
3349 COMPOSE_TO, PREF_ML);
3350 if (compose->replyto)
3351 compose_entry_append(compose,
3353 COMPOSE_CC, PREF_ML);
3355 /* answer to subscription confirmation */
3356 if (compose->replyto)
3357 compose_entry_append(compose,
3359 COMPOSE_TO, PREF_ML);
3360 else if (msginfo->from)
3361 compose_entry_append(compose,
3363 COMPOSE_TO, PREF_ML);
3366 else if (!(to_all || to_sender) && default_reply_to) {
3367 compose_entry_append(compose,
3368 msginfo->folder->prefs->default_reply_to,
3369 COMPOSE_TO, PREF_FOLDER);
3370 compose_entry_indicate(compose,
3371 msginfo->folder->prefs->default_reply_to);
3377 compose_entry_append(compose, msginfo->from,
3378 COMPOSE_TO, PREF_NONE);
3380 Xstrdup_a(tmp1, msginfo->from, return);
3381 extract_address(tmp1);
3382 compose_entry_append(compose,
3383 (!account_find_from_address(tmp1, FALSE))
3386 COMPOSE_TO, PREF_NONE);
3387 if (compose->replyto)
3388 compose_entry_append(compose,
3390 COMPOSE_CC, PREF_NONE);
3392 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3393 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3394 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3395 if (compose->replyto) {
3396 compose_entry_append(compose,
3398 COMPOSE_TO, PREF_NONE);
3400 compose_entry_append(compose,
3401 msginfo->from ? msginfo->from : "",
3402 COMPOSE_TO, PREF_NONE);
3405 /* replying to own mail, use original recp */
3406 compose_entry_append(compose,
3407 msginfo->to ? msginfo->to : "",
3408 COMPOSE_TO, PREF_NONE);
3409 compose_entry_append(compose,
3410 msginfo->cc ? msginfo->cc : "",
3411 COMPOSE_CC, PREF_NONE);
3416 if (to_sender || (compose->followup_to &&
3417 !strncmp(compose->followup_to, "poster", 6)))
3418 compose_entry_append
3420 (compose->replyto ? compose->replyto :
3421 msginfo->from ? msginfo->from : ""),
3422 COMPOSE_TO, PREF_NONE);
3424 else if (followup_and_reply_to || to_all) {
3425 compose_entry_append
3427 (compose->replyto ? compose->replyto :
3428 msginfo->from ? msginfo->from : ""),
3429 COMPOSE_TO, PREF_NONE);
3431 compose_entry_append
3433 compose->followup_to ? compose->followup_to :
3434 compose->newsgroups ? compose->newsgroups : "",
3435 COMPOSE_NEWSGROUPS, PREF_NONE);
3437 compose_entry_append
3439 msginfo->cc ? msginfo->cc : "",
3440 COMPOSE_CC, PREF_NONE);
3443 compose_entry_append
3445 compose->followup_to ? compose->followup_to :
3446 compose->newsgroups ? compose->newsgroups : "",
3447 COMPOSE_NEWSGROUPS, PREF_NONE);
3449 compose_reply_set_subject(compose, msginfo);
3451 if (to_ml && compose->ml_post) return;
3452 if (!to_all || compose->account->protocol == A_NNTP) return;
3454 if (compose->replyto) {
3455 Xstrdup_a(replyto, compose->replyto, return);
3456 extract_address(replyto);
3458 if (msginfo->from) {
3459 Xstrdup_a(from, msginfo->from, return);
3460 extract_address(from);
3463 if (replyto && from)
3464 cc_list = address_list_append_with_comments(cc_list, from);
3465 if (to_all && msginfo->folder &&
3466 msginfo->folder->prefs->enable_default_reply_to)
3467 cc_list = address_list_append_with_comments(cc_list,
3468 msginfo->folder->prefs->default_reply_to);
3469 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3470 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3472 ac_email = g_utf8_strdown(compose->account->address, -1);
3475 for (cur = cc_list; cur != NULL; cur = cur->next) {
3476 gchar *addr = g_utf8_strdown(cur->data, -1);
3477 extract_address(addr);
3479 if (strcmp(ac_email, addr))
3480 compose_entry_append(compose, (gchar *)cur->data,
3481 COMPOSE_CC, PREF_NONE);
3483 debug_print("Cc address same as compose account's, ignoring\n");
3488 slist_free_strings_full(cc_list);
3494 #define SET_ENTRY(entry, str) \
3497 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3500 #define SET_ADDRESS(type, str) \
3503 compose_entry_append(compose, str, type, PREF_NONE); \
3506 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3508 cm_return_if_fail(msginfo != NULL);
3510 SET_ENTRY(subject_entry, msginfo->subject);
3511 SET_ENTRY(from_name, msginfo->from);
3512 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3513 SET_ADDRESS(COMPOSE_CC, compose->cc);
3514 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3515 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3516 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3517 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3519 compose_update_priority_menu_item(compose);
3520 compose_update_privacy_system_menu_item(compose, FALSE);
3521 compose_show_first_last_header(compose, TRUE);
3527 static void compose_insert_sig(Compose *compose, gboolean replace)
3529 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3530 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3532 GtkTextIter iter, iter_end;
3533 gint cur_pos, ins_pos;
3534 gboolean prev_autowrap;
3535 gboolean found = FALSE;
3536 gboolean exists = FALSE;
3538 cm_return_if_fail(compose->account != NULL);
3542 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3543 G_CALLBACK(compose_changed_cb),
3546 mark = gtk_text_buffer_get_insert(buffer);
3547 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3548 cur_pos = gtk_text_iter_get_offset (&iter);
3551 gtk_text_buffer_get_end_iter(buffer, &iter);
3553 exists = (compose->sig_str != NULL);
3556 GtkTextIter first_iter, start_iter, end_iter;
3558 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3560 if (!exists || compose->sig_str[0] == '\0')
3563 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3564 compose->signature_tag);
3567 /* include previous \n\n */
3568 gtk_text_iter_backward_chars(&first_iter, 1);
3569 start_iter = first_iter;
3570 end_iter = first_iter;
3572 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3573 compose->signature_tag);
3574 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3575 compose->signature_tag);
3577 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3583 g_free(compose->sig_str);
3584 compose->sig_str = account_get_signature_str(compose->account);
3586 cur_pos = gtk_text_iter_get_offset(&iter);
3588 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3589 g_free(compose->sig_str);
3590 compose->sig_str = NULL;
3592 if (compose->sig_inserted == FALSE)
3593 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3594 compose->sig_inserted = TRUE;
3596 cur_pos = gtk_text_iter_get_offset(&iter);
3597 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3599 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3600 gtk_text_iter_forward_chars(&iter, 1);
3601 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3602 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3604 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3605 cur_pos = gtk_text_buffer_get_char_count (buffer);
3608 /* put the cursor where it should be
3609 * either where the quote_fmt says, either where it was */
3610 if (compose->set_cursor_pos < 0)
3611 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3613 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3614 compose->set_cursor_pos);
3616 compose->set_cursor_pos = -1;
3617 gtk_text_buffer_place_cursor(buffer, &iter);
3618 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3619 G_CALLBACK(compose_changed_cb),
3625 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3628 GtkTextBuffer *buffer;
3631 const gchar *cur_encoding;
3632 gchar buf[BUFFSIZE];
3635 gboolean prev_autowrap;
3638 GString *file_contents = NULL;
3639 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3641 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3643 /* get the size of the file we are about to insert */
3644 ret = g_stat(file, &file_stat);
3646 gchar *shortfile = g_path_get_basename(file);
3647 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3649 return COMPOSE_INSERT_NO_FILE;
3650 } else if (prefs_common.warn_large_insert == TRUE) {
3652 /* ask user for confirmation if the file is large */
3653 if (prefs_common.warn_large_insert_size < 0 ||
3654 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3658 msg = g_strdup_printf(_("You are about to insert a file of %s "
3659 "in the message body. Are you sure you want to do that?"),
3660 to_human_readable(file_stat.st_size));
3661 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3662 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3665 /* do we ask for confirmation next time? */
3666 if (aval & G_ALERTDISABLE) {
3667 /* no confirmation next time, disable feature in preferences */
3668 aval &= ~G_ALERTDISABLE;
3669 prefs_common.warn_large_insert = FALSE;
3672 /* abort file insertion if user canceled action */
3673 if (aval != G_ALERTALTERNATE) {
3674 return COMPOSE_INSERT_NO_FILE;
3680 if ((fp = g_fopen(file, "rb")) == NULL) {
3681 FILE_OP_ERROR(file, "fopen");
3682 return COMPOSE_INSERT_READ_ERROR;
3685 prev_autowrap = compose->autowrap;
3686 compose->autowrap = FALSE;
3688 text = GTK_TEXT_VIEW(compose->text);
3689 buffer = gtk_text_view_get_buffer(text);
3690 mark = gtk_text_buffer_get_insert(buffer);
3691 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3693 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3694 G_CALLBACK(text_inserted),
3697 cur_encoding = conv_get_locale_charset_str_no_utf8();
3699 file_contents = g_string_new("");
3700 while (fgets(buf, sizeof(buf), fp) != NULL) {
3703 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3704 str = g_strdup(buf);
3706 codeconv_set_strict(TRUE);
3707 str = conv_codeset_strdup
3708 (buf, cur_encoding, CS_INTERNAL);
3709 codeconv_set_strict(FALSE);
3712 result = COMPOSE_INSERT_INVALID_CHARACTER;
3718 /* strip <CR> if DOS/Windows file,
3719 replace <CR> with <LF> if Macintosh file. */
3722 if (len > 0 && str[len - 1] != '\n') {
3724 if (str[len] == '\r') str[len] = '\n';
3727 file_contents = g_string_append(file_contents, str);
3731 if (result == COMPOSE_INSERT_SUCCESS) {
3732 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3734 compose_changed_cb(NULL, compose);
3735 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3736 G_CALLBACK(text_inserted),
3738 compose->autowrap = prev_autowrap;
3739 if (compose->autowrap)
3740 compose_wrap_all(compose);
3743 g_string_free(file_contents, TRUE);
3749 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3750 const gchar *filename,
3751 const gchar *content_type,
3752 const gchar *charset)
3760 GtkListStore *store;
3762 gboolean has_binary = FALSE;
3764 if (!is_file_exist(file)) {
3765 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3766 gboolean result = FALSE;
3767 if (file_from_uri && is_file_exist(file_from_uri)) {
3768 result = compose_attach_append(
3769 compose, file_from_uri,
3770 filename, content_type,
3773 g_free(file_from_uri);
3776 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3779 if ((size = get_file_size(file)) < 0) {
3780 alertpanel_error("Can't get file size of %s\n", filename);
3784 /* In batch mode, we allow 0-length files to be attached no questions asked */
3785 if (size == 0 && !compose->batch) {
3786 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3787 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3788 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3789 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3792 if (aval != G_ALERTALTERNATE) {
3796 if ((fp = g_fopen(file, "rb")) == NULL) {
3797 alertpanel_error(_("Can't read %s."), filename);
3802 ainfo = g_new0(AttachInfo, 1);
3803 auto_ainfo = g_auto_pointer_new_with_free
3804 (ainfo, (GFreeFunc) compose_attach_info_free);
3805 ainfo->file = g_strdup(file);
3808 ainfo->content_type = g_strdup(content_type);
3809 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3811 MsgFlags flags = {0, 0};
3813 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3814 ainfo->encoding = ENC_7BIT;
3816 ainfo->encoding = ENC_8BIT;
3818 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3819 if (msginfo && msginfo->subject)
3820 name = g_strdup(msginfo->subject);
3822 name = g_path_get_basename(filename ? filename : file);
3824 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3826 procmsg_msginfo_free(&msginfo);
3828 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3829 ainfo->charset = g_strdup(charset);
3830 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3832 ainfo->encoding = ENC_BASE64;
3834 name = g_path_get_basename(filename ? filename : file);
3835 ainfo->name = g_strdup(name);
3839 ainfo->content_type = procmime_get_mime_type(file);
3840 if (!ainfo->content_type) {
3841 ainfo->content_type =
3842 g_strdup("application/octet-stream");
3843 ainfo->encoding = ENC_BASE64;
3844 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3846 procmime_get_encoding_for_text_file(file, &has_binary);
3848 ainfo->encoding = ENC_BASE64;
3849 name = g_path_get_basename(filename ? filename : file);
3850 ainfo->name = g_strdup(name);
3854 if (ainfo->name != NULL
3855 && !strcmp(ainfo->name, ".")) {
3856 g_free(ainfo->name);
3860 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3861 g_free(ainfo->content_type);
3862 ainfo->content_type = g_strdup("application/octet-stream");
3863 g_free(ainfo->charset);
3864 ainfo->charset = NULL;
3867 ainfo->size = (goffset)size;
3868 size_text = to_human_readable((goffset)size);
3870 store = GTK_LIST_STORE(gtk_tree_view_get_model
3871 (GTK_TREE_VIEW(compose->attach_clist)));
3873 gtk_list_store_append(store, &iter);
3874 gtk_list_store_set(store, &iter,
3875 COL_MIMETYPE, ainfo->content_type,
3876 COL_SIZE, size_text,
3877 COL_NAME, ainfo->name,
3878 COL_CHARSET, ainfo->charset,
3880 COL_AUTODATA, auto_ainfo,
3883 g_auto_pointer_free(auto_ainfo);
3884 compose_attach_update_label(compose);
3888 void compose_use_signing(Compose *compose, gboolean use_signing)
3890 compose->use_signing = use_signing;
3891 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3894 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3896 compose->use_encryption = use_encryption;
3897 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3900 #define NEXT_PART_NOT_CHILD(info) \
3902 node = info->node; \
3903 while (node->children) \
3904 node = g_node_last_child(node); \
3905 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3908 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3912 MimeInfo *firsttext = NULL;
3913 MimeInfo *encrypted = NULL;
3916 const gchar *partname = NULL;
3918 mimeinfo = procmime_scan_message(msginfo);
3919 if (!mimeinfo) return;
3921 if (mimeinfo->node->children == NULL) {
3922 procmime_mimeinfo_free_all(&mimeinfo);
3926 /* find first content part */
3927 child = (MimeInfo *) mimeinfo->node->children->data;
3928 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3929 child = (MimeInfo *)child->node->children->data;
3932 if (child->type == MIMETYPE_TEXT) {
3934 debug_print("First text part found\n");
3935 } else if (compose->mode == COMPOSE_REEDIT &&
3936 child->type == MIMETYPE_APPLICATION &&
3937 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3938 encrypted = (MimeInfo *)child->node->parent->data;
3941 child = (MimeInfo *) mimeinfo->node->children->data;
3942 while (child != NULL) {
3945 if (child == encrypted) {
3946 /* skip this part of tree */
3947 NEXT_PART_NOT_CHILD(child);
3951 if (child->type == MIMETYPE_MULTIPART) {
3952 /* get the actual content */
3953 child = procmime_mimeinfo_next(child);
3957 if (child == firsttext) {
3958 child = procmime_mimeinfo_next(child);
3962 outfile = procmime_get_tmp_file_name(child);
3963 if ((err = procmime_get_part(outfile, child)) < 0)
3964 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3966 gchar *content_type;
3968 content_type = procmime_get_content_type_str(child->type, child->subtype);
3970 /* if we meet a pgp signature, we don't attach it, but
3971 * we force signing. */
3972 if ((strcmp(content_type, "application/pgp-signature") &&
3973 strcmp(content_type, "application/pkcs7-signature") &&
3974 strcmp(content_type, "application/x-pkcs7-signature"))
3975 || compose->mode == COMPOSE_REDIRECT) {
3976 partname = procmime_mimeinfo_get_parameter(child, "filename");
3977 if (partname == NULL)
3978 partname = procmime_mimeinfo_get_parameter(child, "name");
3979 if (partname == NULL)
3981 compose_attach_append(compose, outfile,
3982 partname, content_type,
3983 procmime_mimeinfo_get_parameter(child, "charset"));
3985 compose_force_signing(compose, compose->account, NULL);
3987 g_free(content_type);
3990 NEXT_PART_NOT_CHILD(child);
3992 procmime_mimeinfo_free_all(&mimeinfo);
3995 #undef NEXT_PART_NOT_CHILD
4000 WAIT_FOR_INDENT_CHAR,
4001 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4004 /* return indent length, we allow:
4005 indent characters followed by indent characters or spaces/tabs,
4006 alphabets and numbers immediately followed by indent characters,
4007 and the repeating sequences of the above
4008 If quote ends with multiple spaces, only the first one is included. */
4009 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4010 const GtkTextIter *start, gint *len)
4012 GtkTextIter iter = *start;
4016 IndentState state = WAIT_FOR_INDENT_CHAR;
4019 gint alnum_count = 0;
4020 gint space_count = 0;
4023 if (prefs_common.quote_chars == NULL) {
4027 while (!gtk_text_iter_ends_line(&iter)) {
4028 wc = gtk_text_iter_get_char(&iter);
4029 if (g_unichar_iswide(wc))
4031 clen = g_unichar_to_utf8(wc, ch);
4035 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4036 is_space = g_unichar_isspace(wc);
4038 if (state == WAIT_FOR_INDENT_CHAR) {
4039 if (!is_indent && !g_unichar_isalnum(wc))
4042 quote_len += alnum_count + space_count + 1;
4043 alnum_count = space_count = 0;
4044 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4047 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4048 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4052 else if (is_indent) {
4053 quote_len += alnum_count + space_count + 1;
4054 alnum_count = space_count = 0;
4057 state = WAIT_FOR_INDENT_CHAR;
4061 gtk_text_iter_forward_char(&iter);
4064 if (quote_len > 0 && space_count > 0)
4070 if (quote_len > 0) {
4072 gtk_text_iter_forward_chars(&iter, quote_len);
4073 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4079 /* return >0 if the line is itemized */
4080 static int compose_itemized_length(GtkTextBuffer *buffer,
4081 const GtkTextIter *start)
4083 GtkTextIter iter = *start;
4088 if (gtk_text_iter_ends_line(&iter))
4093 wc = gtk_text_iter_get_char(&iter);
4094 if (!g_unichar_isspace(wc))
4096 gtk_text_iter_forward_char(&iter);
4097 if (gtk_text_iter_ends_line(&iter))
4101 clen = g_unichar_to_utf8(wc, ch);
4102 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4104 wc == 0x2022 || /* BULLET */
4105 wc == 0x2023 || /* TRIANGULAR BULLET */
4106 wc == 0x2043 || /* HYPHEN BULLET */
4107 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4108 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4109 wc == 0x2219 || /* BULLET OPERATOR */
4110 wc == 0x25d8 || /* INVERSE BULLET */
4111 wc == 0x25e6 || /* WHITE BULLET */
4112 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4113 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4114 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4115 wc == 0x29be || /* CIRCLED WHITE BULLET */
4116 wc == 0x29bf /* CIRCLED BULLET */
4120 gtk_text_iter_forward_char(&iter);
4121 if (gtk_text_iter_ends_line(&iter))
4123 wc = gtk_text_iter_get_char(&iter);
4124 if (g_unichar_isspace(wc)) {
4130 /* return the string at the start of the itemization */
4131 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4132 const GtkTextIter *start)
4134 GtkTextIter iter = *start;
4137 GString *item_chars = g_string_new("");
4140 if (gtk_text_iter_ends_line(&iter))
4145 wc = gtk_text_iter_get_char(&iter);
4146 if (!g_unichar_isspace(wc))
4148 gtk_text_iter_forward_char(&iter);
4149 if (gtk_text_iter_ends_line(&iter))
4151 g_string_append_unichar(item_chars, wc);
4154 str = item_chars->str;
4155 g_string_free(item_chars, FALSE);
4159 /* return the number of spaces at a line's start */
4160 static int compose_left_offset_length(GtkTextBuffer *buffer,
4161 const GtkTextIter *start)
4163 GtkTextIter iter = *start;
4166 if (gtk_text_iter_ends_line(&iter))
4170 wc = gtk_text_iter_get_char(&iter);
4171 if (!g_unichar_isspace(wc))
4174 gtk_text_iter_forward_char(&iter);
4175 if (gtk_text_iter_ends_line(&iter))
4179 gtk_text_iter_forward_char(&iter);
4180 if (gtk_text_iter_ends_line(&iter))
4185 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4186 const GtkTextIter *start,
4187 GtkTextIter *break_pos,
4191 GtkTextIter iter = *start, line_end = *start;
4192 PangoLogAttr *attrs;
4199 gboolean can_break = FALSE;
4200 gboolean do_break = FALSE;
4201 gboolean was_white = FALSE;
4202 gboolean prev_dont_break = FALSE;
4204 gtk_text_iter_forward_to_line_end(&line_end);
4205 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4206 len = g_utf8_strlen(str, -1);
4210 g_warning("compose_get_line_break_pos: len = 0!");
4214 /* g_print("breaking line: %d: %s (len = %d)\n",
4215 gtk_text_iter_get_line(&iter), str, len); */
4217 attrs = g_new(PangoLogAttr, len + 1);
4219 pango_default_break(str, -1, NULL, attrs, len + 1);
4223 /* skip quote and leading spaces */
4224 for (i = 0; *p != '\0' && i < len; i++) {
4227 wc = g_utf8_get_char(p);
4228 if (i >= quote_len && !g_unichar_isspace(wc))
4230 if (g_unichar_iswide(wc))
4232 else if (*p == '\t')
4236 p = g_utf8_next_char(p);
4239 for (; *p != '\0' && i < len; i++) {
4240 PangoLogAttr *attr = attrs + i;
4244 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4247 was_white = attr->is_white;
4249 /* don't wrap URI */
4250 if ((uri_len = get_uri_len(p)) > 0) {
4252 if (pos > 0 && col > max_col) {
4262 wc = g_utf8_get_char(p);
4263 if (g_unichar_iswide(wc)) {
4265 if (prev_dont_break && can_break && attr->is_line_break)
4267 } else if (*p == '\t')
4271 if (pos > 0 && col > max_col) {
4276 if (*p == '-' || *p == '/')
4277 prev_dont_break = TRUE;
4279 prev_dont_break = FALSE;
4281 p = g_utf8_next_char(p);
4285 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4290 *break_pos = *start;
4291 gtk_text_iter_set_line_offset(break_pos, pos);
4296 static gboolean compose_join_next_line(Compose *compose,
4297 GtkTextBuffer *buffer,
4299 const gchar *quote_str)
4301 GtkTextIter iter_ = *iter, cur, prev, next, end;
4302 PangoLogAttr attrs[3];
4304 gchar *next_quote_str;
4307 gboolean keep_cursor = FALSE;
4309 if (!gtk_text_iter_forward_line(&iter_) ||
4310 gtk_text_iter_ends_line(&iter_)) {
4313 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4315 if ((quote_str || next_quote_str) &&
4316 strcmp2(quote_str, next_quote_str) != 0) {
4317 g_free(next_quote_str);
4320 g_free(next_quote_str);
4323 if (quote_len > 0) {
4324 gtk_text_iter_forward_chars(&end, quote_len);
4325 if (gtk_text_iter_ends_line(&end)) {
4330 /* don't join itemized lines */
4331 if (compose_itemized_length(buffer, &end) > 0) {
4335 /* don't join signature separator */
4336 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4339 /* delete quote str */
4341 gtk_text_buffer_delete(buffer, &iter_, &end);
4343 /* don't join line breaks put by the user */
4345 gtk_text_iter_backward_char(&cur);
4346 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4347 gtk_text_iter_forward_char(&cur);
4351 gtk_text_iter_forward_char(&cur);
4352 /* delete linebreak and extra spaces */
4353 while (gtk_text_iter_backward_char(&cur)) {
4354 wc1 = gtk_text_iter_get_char(&cur);
4355 if (!g_unichar_isspace(wc1))
4360 while (!gtk_text_iter_ends_line(&cur)) {
4361 wc1 = gtk_text_iter_get_char(&cur);
4362 if (!g_unichar_isspace(wc1))
4364 gtk_text_iter_forward_char(&cur);
4367 if (!gtk_text_iter_equal(&prev, &next)) {
4370 mark = gtk_text_buffer_get_insert(buffer);
4371 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4372 if (gtk_text_iter_equal(&prev, &cur))
4374 gtk_text_buffer_delete(buffer, &prev, &next);
4378 /* insert space if required */
4379 gtk_text_iter_backward_char(&prev);
4380 wc1 = gtk_text_iter_get_char(&prev);
4381 wc2 = gtk_text_iter_get_char(&next);
4382 gtk_text_iter_forward_char(&next);
4383 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4384 pango_default_break(str, -1, NULL, attrs, 3);
4385 if (!attrs[1].is_line_break ||
4386 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4387 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4389 gtk_text_iter_backward_char(&iter_);
4390 gtk_text_buffer_place_cursor(buffer, &iter_);
4399 #define ADD_TXT_POS(bp_, ep_, pti_) \
4400 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4401 last = last->next; \
4402 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4403 last->next = NULL; \
4405 g_warning("alloc error scanning URIs"); \
4408 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4410 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4411 GtkTextBuffer *buffer;
4412 GtkTextIter iter, break_pos, end_of_line;
4413 gchar *quote_str = NULL;
4415 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4416 gboolean prev_autowrap = compose->autowrap;
4417 gint startq_offset = -1, noq_offset = -1;
4418 gint uri_start = -1, uri_stop = -1;
4419 gint nouri_start = -1, nouri_stop = -1;
4420 gint num_blocks = 0;
4421 gint quotelevel = -1;
4422 gboolean modified = force;
4423 gboolean removed = FALSE;
4424 gboolean modified_before_remove = FALSE;
4426 gboolean start = TRUE;
4427 gint itemized_len = 0, rem_item_len = 0;
4428 gchar *itemized_chars = NULL;
4429 gboolean item_continuation = FALSE;
4434 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4438 compose->autowrap = FALSE;
4440 buffer = gtk_text_view_get_buffer(text);
4441 undo_wrapping(compose->undostruct, TRUE);
4446 mark = gtk_text_buffer_get_insert(buffer);
4447 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4451 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4452 if (gtk_text_iter_ends_line(&iter)) {
4453 while (gtk_text_iter_ends_line(&iter) &&
4454 gtk_text_iter_forward_line(&iter))
4457 while (gtk_text_iter_backward_line(&iter)) {
4458 if (gtk_text_iter_ends_line(&iter)) {
4459 gtk_text_iter_forward_line(&iter);
4465 /* move to line start */
4466 gtk_text_iter_set_line_offset(&iter, 0);
4469 itemized_len = compose_itemized_length(buffer, &iter);
4471 if (!itemized_len) {
4472 itemized_len = compose_left_offset_length(buffer, &iter);
4473 item_continuation = TRUE;
4477 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4479 /* go until paragraph end (empty line) */
4480 while (start || !gtk_text_iter_ends_line(&iter)) {
4481 gchar *scanpos = NULL;
4482 /* parse table - in order of priority */
4484 const gchar *needle; /* token */
4486 /* token search function */
4487 gchar *(*search) (const gchar *haystack,
4488 const gchar *needle);
4489 /* part parsing function */
4490 gboolean (*parse) (const gchar *start,
4491 const gchar *scanpos,
4495 /* part to URI function */
4496 gchar *(*build_uri) (const gchar *bp,
4500 static struct table parser[] = {
4501 {"http://", strcasestr, get_uri_part, make_uri_string},
4502 {"https://", strcasestr, get_uri_part, make_uri_string},
4503 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4504 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4505 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4506 {"www.", strcasestr, get_uri_part, make_http_string},
4507 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4508 {"@", strcasestr, get_email_part, make_email_string}
4510 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4511 gint last_index = PARSE_ELEMS;
4513 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4517 if (!prev_autowrap && num_blocks == 0) {
4519 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4520 G_CALLBACK(text_inserted),
4523 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4526 uri_start = uri_stop = -1;
4528 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4531 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4532 if (startq_offset == -1)
4533 startq_offset = gtk_text_iter_get_offset(&iter);
4534 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4535 if (quotelevel > 2) {
4536 /* recycle colors */
4537 if (prefs_common.recycle_quote_colors)
4546 if (startq_offset == -1)
4547 noq_offset = gtk_text_iter_get_offset(&iter);
4551 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4554 if (gtk_text_iter_ends_line(&iter)) {
4556 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4557 prefs_common.linewrap_len,
4559 GtkTextIter prev, next, cur;
4560 if (prev_autowrap != FALSE || force) {
4561 compose->automatic_break = TRUE;
4563 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4564 compose->automatic_break = FALSE;
4565 if (itemized_len && compose->autoindent) {
4566 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4567 if (!item_continuation)
4568 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4570 } else if (quote_str && wrap_quote) {
4571 compose->automatic_break = TRUE;
4573 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4574 compose->automatic_break = FALSE;
4575 if (itemized_len && compose->autoindent) {
4576 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4577 if (!item_continuation)
4578 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4582 /* remove trailing spaces */
4584 rem_item_len = itemized_len;
4585 while (compose->autoindent && rem_item_len-- > 0)
4586 gtk_text_iter_backward_char(&cur);
4587 gtk_text_iter_backward_char(&cur);
4590 while (!gtk_text_iter_starts_line(&cur)) {
4593 gtk_text_iter_backward_char(&cur);
4594 wc = gtk_text_iter_get_char(&cur);
4595 if (!g_unichar_isspace(wc))
4599 if (!gtk_text_iter_equal(&prev, &next)) {
4600 gtk_text_buffer_delete(buffer, &prev, &next);
4602 gtk_text_iter_forward_char(&break_pos);
4606 gtk_text_buffer_insert(buffer, &break_pos,
4610 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4612 /* move iter to current line start */
4613 gtk_text_iter_set_line_offset(&iter, 0);
4620 /* move iter to next line start */
4626 if (!prev_autowrap && num_blocks > 0) {
4628 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4629 G_CALLBACK(text_inserted),
4633 while (!gtk_text_iter_ends_line(&end_of_line)) {
4634 gtk_text_iter_forward_char(&end_of_line);
4636 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4638 nouri_start = gtk_text_iter_get_offset(&iter);
4639 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4641 walk_pos = gtk_text_iter_get_offset(&iter);
4642 /* FIXME: this looks phony. scanning for anything in the parse table */
4643 for (n = 0; n < PARSE_ELEMS; n++) {
4646 tmp = parser[n].search(walk, parser[n].needle);
4648 if (scanpos == NULL || tmp < scanpos) {
4657 /* check if URI can be parsed */
4658 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4659 (const gchar **)&ep, FALSE)
4660 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4664 strlen(parser[last_index].needle);
4667 uri_start = walk_pos + (bp - o_walk);
4668 uri_stop = walk_pos + (ep - o_walk);
4672 gtk_text_iter_forward_line(&iter);
4675 if (startq_offset != -1) {
4676 GtkTextIter startquote, endquote;
4677 gtk_text_buffer_get_iter_at_offset(
4678 buffer, &startquote, startq_offset);
4681 switch (quotelevel) {
4683 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4684 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4685 gtk_text_buffer_apply_tag_by_name(
4686 buffer, "quote0", &startquote, &endquote);
4687 gtk_text_buffer_remove_tag_by_name(
4688 buffer, "quote1", &startquote, &endquote);
4689 gtk_text_buffer_remove_tag_by_name(
4690 buffer, "quote2", &startquote, &endquote);
4695 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4696 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4697 gtk_text_buffer_apply_tag_by_name(
4698 buffer, "quote1", &startquote, &endquote);
4699 gtk_text_buffer_remove_tag_by_name(
4700 buffer, "quote0", &startquote, &endquote);
4701 gtk_text_buffer_remove_tag_by_name(
4702 buffer, "quote2", &startquote, &endquote);
4707 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4708 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4709 gtk_text_buffer_apply_tag_by_name(
4710 buffer, "quote2", &startquote, &endquote);
4711 gtk_text_buffer_remove_tag_by_name(
4712 buffer, "quote0", &startquote, &endquote);
4713 gtk_text_buffer_remove_tag_by_name(
4714 buffer, "quote1", &startquote, &endquote);
4720 } else if (noq_offset != -1) {
4721 GtkTextIter startnoquote, endnoquote;
4722 gtk_text_buffer_get_iter_at_offset(
4723 buffer, &startnoquote, noq_offset);
4726 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4727 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4728 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4729 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4730 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4731 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4732 gtk_text_buffer_remove_tag_by_name(
4733 buffer, "quote0", &startnoquote, &endnoquote);
4734 gtk_text_buffer_remove_tag_by_name(
4735 buffer, "quote1", &startnoquote, &endnoquote);
4736 gtk_text_buffer_remove_tag_by_name(
4737 buffer, "quote2", &startnoquote, &endnoquote);
4743 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4744 GtkTextIter nouri_start_iter, nouri_end_iter;
4745 gtk_text_buffer_get_iter_at_offset(
4746 buffer, &nouri_start_iter, nouri_start);
4747 gtk_text_buffer_get_iter_at_offset(
4748 buffer, &nouri_end_iter, nouri_stop);
4749 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4750 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4751 gtk_text_buffer_remove_tag_by_name(
4752 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4753 modified_before_remove = modified;
4758 if (uri_start >= 0 && uri_stop > 0) {
4759 GtkTextIter uri_start_iter, uri_end_iter, back;
4760 gtk_text_buffer_get_iter_at_offset(
4761 buffer, &uri_start_iter, uri_start);
4762 gtk_text_buffer_get_iter_at_offset(
4763 buffer, &uri_end_iter, uri_stop);
4764 back = uri_end_iter;
4765 gtk_text_iter_backward_char(&back);
4766 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4767 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4768 gtk_text_buffer_apply_tag_by_name(
4769 buffer, "link", &uri_start_iter, &uri_end_iter);
4771 if (removed && !modified_before_remove) {
4777 /* debug_print("not modified, out after %d lines\n", lines); */
4781 /* debug_print("modified, out after %d lines\n", lines); */
4783 g_free(itemized_chars);
4786 undo_wrapping(compose->undostruct, FALSE);
4787 compose->autowrap = prev_autowrap;
4792 void compose_action_cb(void *data)
4794 Compose *compose = (Compose *)data;
4795 compose_wrap_all(compose);
4798 static void compose_wrap_all(Compose *compose)
4800 compose_wrap_all_full(compose, FALSE);
4803 static void compose_wrap_all_full(Compose *compose, gboolean force)
4805 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4806 GtkTextBuffer *buffer;
4808 gboolean modified = TRUE;
4810 buffer = gtk_text_view_get_buffer(text);
4812 gtk_text_buffer_get_start_iter(buffer, &iter);
4814 undo_wrapping(compose->undostruct, TRUE);
4816 while (!gtk_text_iter_is_end(&iter) && modified)
4817 modified = compose_beautify_paragraph(compose, &iter, force);
4819 undo_wrapping(compose->undostruct, FALSE);
4823 static void compose_set_title(Compose *compose)
4829 edited = compose->modified ? _(" [Edited]") : "";
4831 subject = gtk_editable_get_chars(
4832 GTK_EDITABLE(compose->subject_entry), 0, -1);
4834 #ifndef GENERIC_UMPC
4835 if (subject && strlen(subject))
4836 str = g_strdup_printf(_("%s - Compose message%s"),
4839 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4841 str = g_strdup(_("Compose message"));
4844 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4850 * compose_current_mail_account:
4852 * Find a current mail account (the currently selected account, or the
4853 * default account, if a news account is currently selected). If a
4854 * mail account cannot be found, display an error message.
4856 * Return value: Mail account, or NULL if not found.
4858 static PrefsAccount *
4859 compose_current_mail_account(void)
4863 if (cur_account && cur_account->protocol != A_NNTP)
4866 ac = account_get_default();
4867 if (!ac || ac->protocol == A_NNTP) {
4868 alertpanel_error(_("Account for sending mail is not specified.\n"
4869 "Please select a mail account before sending."));
4876 #define QUOTE_IF_REQUIRED(out, str) \
4878 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4882 len = strlen(str) + 3; \
4883 if ((__tmp = alloca(len)) == NULL) { \
4884 g_warning("can't allocate memory"); \
4885 g_string_free(header, TRUE); \
4888 g_snprintf(__tmp, len, "\"%s\"", str); \
4893 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4894 g_warning("can't allocate memory"); \
4895 g_string_free(header, TRUE); \
4898 strcpy(__tmp, str); \
4904 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4906 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4910 len = strlen(str) + 3; \
4911 if ((__tmp = alloca(len)) == NULL) { \
4912 g_warning("can't allocate memory"); \
4915 g_snprintf(__tmp, len, "\"%s\"", str); \
4920 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4921 g_warning("can't allocate memory"); \
4924 strcpy(__tmp, str); \
4930 static void compose_select_account(Compose *compose, PrefsAccount *account,
4933 gchar *from = NULL, *header = NULL;
4934 ComposeHeaderEntry *header_entry;
4935 #if GTK_CHECK_VERSION(2, 24, 0)
4939 cm_return_if_fail(account != NULL);
4941 compose->account = account;
4942 if (account->name && *account->name) {
4944 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4945 qbuf = escape_internal_quotes(buf, '"');
4946 from = g_strdup_printf("%s <%s>",
4947 qbuf, account->address);
4950 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4952 from = g_strdup_printf("<%s>",
4954 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4959 compose_set_title(compose);
4961 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4962 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4964 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4965 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4966 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4968 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4970 activate_privacy_system(compose, account, FALSE);
4972 if (!init && compose->mode != COMPOSE_REDIRECT) {
4973 undo_block(compose->undostruct);
4974 compose_insert_sig(compose, TRUE);
4975 undo_unblock(compose->undostruct);
4978 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4979 #if !GTK_CHECK_VERSION(2, 24, 0)
4980 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4982 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4983 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4984 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4987 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4988 if (account->protocol == A_NNTP) {
4989 if (!strcmp(header, _("To:")))
4990 combobox_select_by_text(
4991 GTK_COMBO_BOX(header_entry->combo),
4994 if (!strcmp(header, _("Newsgroups:")))
4995 combobox_select_by_text(
4996 GTK_COMBO_BOX(header_entry->combo),
5004 /* use account's dict info if set */
5005 if (compose->gtkaspell) {
5006 if (account->enable_default_dictionary)
5007 gtkaspell_change_dict(compose->gtkaspell,
5008 account->default_dictionary, FALSE);
5009 if (account->enable_default_alt_dictionary)
5010 gtkaspell_change_alt_dict(compose->gtkaspell,
5011 account->default_alt_dictionary);
5012 if (account->enable_default_dictionary
5013 || account->enable_default_alt_dictionary)
5014 compose_spell_menu_changed(compose);
5019 gboolean compose_check_for_valid_recipient(Compose *compose) {
5020 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5021 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5022 gboolean recipient_found = FALSE;
5026 /* free to and newsgroup list */
5027 slist_free_strings_full(compose->to_list);
5028 compose->to_list = NULL;
5030 slist_free_strings_full(compose->newsgroup_list);
5031 compose->newsgroup_list = NULL;
5033 /* search header entries for to and newsgroup entries */
5034 for (list = compose->header_list; list; list = list->next) {
5037 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5038 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5041 if (entry[0] != '\0') {
5042 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5043 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5044 compose->to_list = address_list_append(compose->to_list, entry);
5045 recipient_found = TRUE;
5048 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5049 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5050 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5051 recipient_found = TRUE;
5058 return recipient_found;
5061 static gboolean compose_check_for_set_recipients(Compose *compose)
5063 if (compose->account->set_autocc && compose->account->auto_cc) {
5064 gboolean found_other = FALSE;
5066 /* search header entries for to and newsgroup entries */
5067 for (list = compose->header_list; list; list = list->next) {
5070 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5071 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5074 if (strcmp(entry, compose->account->auto_cc)
5075 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5086 if (compose->batch) {
5087 gtk_widget_show_all(compose->window);
5089 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5090 prefs_common_translated_header_name("Cc"));
5091 aval = alertpanel(_("Send"),
5093 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5095 if (aval != G_ALERTALTERNATE)
5099 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5100 gboolean found_other = FALSE;
5102 /* search header entries for to and newsgroup entries */
5103 for (list = compose->header_list; list; list = list->next) {
5106 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5107 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5110 if (strcmp(entry, compose->account->auto_bcc)
5111 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5123 if (compose->batch) {
5124 gtk_widget_show_all(compose->window);
5126 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5127 prefs_common_translated_header_name("Bcc"));
5128 aval = alertpanel(_("Send"),
5130 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5132 if (aval != G_ALERTALTERNATE)
5139 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5143 if (compose_check_for_valid_recipient(compose) == FALSE) {
5144 if (compose->batch) {
5145 gtk_widget_show_all(compose->window);
5147 alertpanel_error(_("Recipient is not specified."));
5151 if (compose_check_for_set_recipients(compose) == FALSE) {
5155 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5156 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5157 if (*str == '\0' && check_everything == TRUE &&
5158 compose->mode != COMPOSE_REDIRECT) {
5160 gchar *button_label;
5163 if (compose->sending)
5164 button_label = g_strconcat("+", _("_Send"), NULL);
5166 button_label = g_strconcat("+", _("_Queue"), NULL);
5167 message = g_strdup_printf(_("Subject is empty. %s"),
5168 compose->sending?_("Send it anyway?"):
5169 _("Queue it anyway?"));
5171 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5172 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5173 ALERT_QUESTION, G_ALERTDEFAULT);
5175 g_free(button_label);
5176 if (aval & G_ALERTDISABLE) {
5177 aval &= ~G_ALERTDISABLE;
5178 prefs_common.warn_empty_subj = FALSE;
5180 if (aval != G_ALERTALTERNATE)
5185 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5186 && check_everything == TRUE) {
5190 /* count To and Cc recipients */
5191 for (list = compose->header_list; list; list = list->next) {
5195 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5196 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5199 if ((entry[0] != '\0') &&
5200 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5201 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5207 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5209 gchar *button_label;
5212 if (compose->sending)
5213 button_label = g_strconcat("+", _("_Send"), NULL);
5215 button_label = g_strconcat("+", _("_Queue"), NULL);
5216 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5217 compose->sending?_("Send it anyway?"):
5218 _("Queue it anyway?"));
5220 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5221 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5222 ALERT_QUESTION, G_ALERTDEFAULT);
5224 if (aval & G_ALERTDISABLE) {
5225 aval &= ~G_ALERTDISABLE;
5226 prefs_common.warn_sending_many_recipients_num = 0;
5228 if (aval != G_ALERTALTERNATE)
5233 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5239 gint compose_send(Compose *compose)
5242 FolderItem *folder = NULL;
5244 gchar *msgpath = NULL;
5245 gboolean discard_window = FALSE;
5246 gchar *errstr = NULL;
5247 gchar *tmsgid = NULL;
5248 MainWindow *mainwin = mainwindow_get_mainwindow();
5249 gboolean queued_removed = FALSE;
5251 if (prefs_common.send_dialog_invisible
5252 || compose->batch == TRUE)
5253 discard_window = TRUE;
5255 compose_allow_user_actions (compose, FALSE);
5256 compose->sending = TRUE;
5258 if (compose_check_entries(compose, TRUE) == FALSE) {
5259 if (compose->batch) {
5260 gtk_widget_show_all(compose->window);
5266 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5269 if (compose->batch) {
5270 gtk_widget_show_all(compose->window);
5273 alertpanel_error(_("Could not queue message for sending:\n\n"
5274 "Charset conversion failed."));
5275 } else if (val == -5) {
5276 alertpanel_error(_("Could not queue message for sending:\n\n"
5277 "Couldn't get recipient encryption key."));
5278 } else if (val == -6) {
5280 } else if (val == -3) {
5281 if (privacy_peek_error())
5282 alertpanel_error(_("Could not queue message for sending:\n\n"
5283 "Signature failed: %s"), privacy_get_error());
5284 } else if (val == -2 && errno != 0) {
5285 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5287 alertpanel_error(_("Could not queue message for sending."));
5292 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5293 if (discard_window) {
5294 compose->sending = FALSE;
5295 compose_close(compose);
5296 /* No more compose access in the normal codepath
5297 * after this point! */
5302 alertpanel_error(_("The message was queued but could not be "
5303 "sent.\nUse \"Send queued messages\" from "
5304 "the main window to retry."));
5305 if (!discard_window) {
5312 if (msgpath == NULL) {
5313 msgpath = folder_item_fetch_msg(folder, msgnum);
5314 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5317 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5318 claws_unlink(msgpath);
5321 if (!discard_window) {
5323 if (!queued_removed)
5324 folder_item_remove_msg(folder, msgnum);
5325 folder_item_scan(folder);
5327 /* make sure we delete that */
5328 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5330 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5331 folder_item_remove_msg(folder, tmp->msgnum);
5332 procmsg_msginfo_free(&tmp);
5339 if (!queued_removed)
5340 folder_item_remove_msg(folder, msgnum);
5341 folder_item_scan(folder);
5343 /* make sure we delete that */
5344 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5346 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5347 folder_item_remove_msg(folder, tmp->msgnum);
5348 procmsg_msginfo_free(&tmp);
5351 if (!discard_window) {
5352 compose->sending = FALSE;
5353 compose_allow_user_actions (compose, TRUE);
5354 compose_close(compose);
5358 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5359 "the main window to retry."), errstr);
5362 alertpanel_error_log(_("The message was queued but could not be "
5363 "sent.\nUse \"Send queued messages\" from "
5364 "the main window to retry."));
5366 if (!discard_window) {
5375 toolbar_main_set_sensitive(mainwin);
5376 main_window_set_menu_sensitive(mainwin);
5382 compose_allow_user_actions (compose, TRUE);
5383 compose->sending = FALSE;
5384 compose->modified = TRUE;
5385 toolbar_main_set_sensitive(mainwin);
5386 main_window_set_menu_sensitive(mainwin);
5391 static gboolean compose_use_attach(Compose *compose)
5393 GtkTreeModel *model = gtk_tree_view_get_model
5394 (GTK_TREE_VIEW(compose->attach_clist));
5395 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5398 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5401 gchar buf[BUFFSIZE];
5403 gboolean first_to_address;
5404 gboolean first_cc_address;
5406 ComposeHeaderEntry *headerentry;
5407 const gchar *headerentryname;
5408 const gchar *cc_hdr;
5409 const gchar *to_hdr;
5410 gboolean err = FALSE;
5412 debug_print("Writing redirect header\n");
5414 cc_hdr = prefs_common_translated_header_name("Cc:");
5415 to_hdr = prefs_common_translated_header_name("To:");
5417 first_to_address = TRUE;
5418 for (list = compose->header_list; list; list = list->next) {
5419 headerentry = ((ComposeHeaderEntry *)list->data);
5420 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5422 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5423 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5424 Xstrdup_a(str, entstr, return -1);
5426 if (str[0] != '\0') {
5427 compose_convert_header
5428 (compose, buf, sizeof(buf), str,
5429 strlen("Resent-To") + 2, TRUE);
5431 if (first_to_address) {
5432 err |= (fprintf(fp, "Resent-To: ") < 0);
5433 first_to_address = FALSE;
5435 err |= (fprintf(fp, ",") < 0);
5437 err |= (fprintf(fp, "%s", buf) < 0);
5441 if (!first_to_address) {
5442 err |= (fprintf(fp, "\n") < 0);
5445 first_cc_address = TRUE;
5446 for (list = compose->header_list; list; list = list->next) {
5447 headerentry = ((ComposeHeaderEntry *)list->data);
5448 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5450 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5451 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5452 Xstrdup_a(str, strg, return -1);
5454 if (str[0] != '\0') {
5455 compose_convert_header
5456 (compose, buf, sizeof(buf), str,
5457 strlen("Resent-Cc") + 2, TRUE);
5459 if (first_cc_address) {
5460 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5461 first_cc_address = FALSE;
5463 err |= (fprintf(fp, ",") < 0);
5465 err |= (fprintf(fp, "%s", buf) < 0);
5469 if (!first_cc_address) {
5470 err |= (fprintf(fp, "\n") < 0);
5473 return (err ? -1:0);
5476 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5478 gchar date[RFC822_DATE_BUFFSIZE];
5479 gchar buf[BUFFSIZE];
5481 const gchar *entstr;
5482 /* struct utsname utsbuf; */
5483 gboolean err = FALSE;
5485 cm_return_val_if_fail(fp != NULL, -1);
5486 cm_return_val_if_fail(compose->account != NULL, -1);
5487 cm_return_val_if_fail(compose->account->address != NULL, -1);
5490 if (prefs_common.hide_timezone)
5491 get_rfc822_date_hide_tz(date, sizeof(date));
5493 get_rfc822_date(date, sizeof(date));
5494 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5497 if (compose->account->name && *compose->account->name) {
5498 compose_convert_header
5499 (compose, buf, sizeof(buf), compose->account->name,
5500 strlen("From: "), TRUE);
5501 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5502 buf, compose->account->address) < 0);
5504 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5507 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5508 if (*entstr != '\0') {
5509 Xstrdup_a(str, entstr, return -1);
5512 compose_convert_header(compose, buf, sizeof(buf), str,
5513 strlen("Subject: "), FALSE);
5514 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5518 /* Resent-Message-ID */
5519 if (compose->account->gen_msgid) {
5520 gchar *addr = prefs_account_generate_msgid(compose->account);
5521 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5523 g_free(compose->msgid);
5524 compose->msgid = addr;
5526 compose->msgid = NULL;
5529 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5532 /* separator between header and body */
5533 err |= (fputs("\n", fp) == EOF);
5535 return (err ? -1:0);
5538 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5543 gchar rewrite_buf[BUFFSIZE];
5545 gboolean skip = FALSE;
5546 gboolean err = FALSE;
5547 gchar *not_included[]={
5548 "Return-Path:", "Delivered-To:", "Received:",
5549 "Subject:", "X-UIDL:", "AF:",
5550 "NF:", "PS:", "SRH:",
5551 "SFN:", "DSR:", "MID:",
5552 "CFG:", "PT:", "S:",
5553 "RQ:", "SSV:", "NSV:",
5554 "SSH:", "R:", "MAID:",
5555 "NAID:", "RMID:", "FMID:",
5556 "SCF:", "RRCPT:", "NG:",
5557 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5558 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5559 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5560 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5561 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5566 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5567 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5571 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5573 for (i = 0; not_included[i] != NULL; i++) {
5574 if (g_ascii_strncasecmp(buf, not_included[i],
5575 strlen(not_included[i])) == 0) {
5585 if (fputs(buf, fdest) == -1) {
5591 if (!prefs_common.redirect_keep_from) {
5592 if (g_ascii_strncasecmp(buf, "From:",
5593 strlen("From:")) == 0) {
5594 err |= (fputs(" (by way of ", fdest) == EOF);
5595 if (compose->account->name
5596 && *compose->account->name) {
5597 gchar buffer[BUFFSIZE];
5599 compose_convert_header
5600 (compose, buffer, sizeof(buffer),
5601 compose->account->name,
5604 err |= (fprintf(fdest, "%s <%s>",
5606 compose->account->address) < 0);
5608 err |= (fprintf(fdest, "%s",
5609 compose->account->address) < 0);
5610 err |= (fputs(")", fdest) == EOF);
5616 if (fputs("\n", fdest) == -1)
5623 if (compose_redirect_write_headers(compose, fdest))
5626 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5627 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5641 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5643 GtkTextBuffer *buffer;
5644 GtkTextIter start, end, tmp;
5645 gchar *chars, *tmp_enc_file, *content;
5647 const gchar *out_codeset;
5648 EncodingType encoding = ENC_UNKNOWN;
5649 MimeInfo *mimemsg, *mimetext;
5651 const gchar *src_codeset = CS_INTERNAL;
5652 gchar *from_addr = NULL;
5653 gchar *from_name = NULL;
5656 if (action == COMPOSE_WRITE_FOR_SEND) {
5657 attach_parts = TRUE;
5659 /* We're sending the message, generate a Message-ID
5661 if (compose->msgid == NULL &&
5662 compose->account->gen_msgid) {
5663 compose->msgid = prefs_account_generate_msgid(compose->account);
5667 /* create message MimeInfo */
5668 mimemsg = procmime_mimeinfo_new();
5669 mimemsg->type = MIMETYPE_MESSAGE;
5670 mimemsg->subtype = g_strdup("rfc822");
5671 mimemsg->content = MIMECONTENT_MEM;
5672 mimemsg->tmp = TRUE; /* must free content later */
5673 mimemsg->data.mem = compose_get_header(compose);
5675 /* Create text part MimeInfo */
5676 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5677 gtk_text_buffer_get_end_iter(buffer, &end);
5680 /* We make sure that there is a newline at the end. */
5681 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5682 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5683 if (*chars != '\n') {
5684 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5689 /* get all composed text */
5690 gtk_text_buffer_get_start_iter(buffer, &start);
5691 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5693 out_codeset = conv_get_charset_str(compose->out_encoding);
5695 if (!out_codeset && is_ascii_str(chars)) {
5696 out_codeset = CS_US_ASCII;
5697 } else if (prefs_common.outgoing_fallback_to_ascii &&
5698 is_ascii_str(chars)) {
5699 out_codeset = CS_US_ASCII;
5700 encoding = ENC_7BIT;
5704 gchar *test_conv_global_out = NULL;
5705 gchar *test_conv_reply = NULL;
5707 /* automatic mode. be automatic. */
5708 codeconv_set_strict(TRUE);
5710 out_codeset = conv_get_outgoing_charset_str();
5712 debug_print("trying to convert to %s\n", out_codeset);
5713 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5716 if (!test_conv_global_out && compose->orig_charset
5717 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5718 out_codeset = compose->orig_charset;
5719 debug_print("failure; trying to convert to %s\n", out_codeset);
5720 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5723 if (!test_conv_global_out && !test_conv_reply) {
5725 out_codeset = CS_INTERNAL;
5726 debug_print("failure; finally using %s\n", out_codeset);
5728 g_free(test_conv_global_out);
5729 g_free(test_conv_reply);
5730 codeconv_set_strict(FALSE);
5733 if (encoding == ENC_UNKNOWN) {
5734 if (prefs_common.encoding_method == CTE_BASE64)
5735 encoding = ENC_BASE64;
5736 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5737 encoding = ENC_QUOTED_PRINTABLE;
5738 else if (prefs_common.encoding_method == CTE_8BIT)
5739 encoding = ENC_8BIT;
5741 encoding = procmime_get_encoding_for_charset(out_codeset);
5744 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5745 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5747 if (action == COMPOSE_WRITE_FOR_SEND) {
5748 codeconv_set_strict(TRUE);
5749 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5750 codeconv_set_strict(FALSE);
5755 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5756 "to the specified %s charset.\n"
5757 "Send it as %s?"), out_codeset, src_codeset);
5758 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5759 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5760 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5763 if (aval != G_ALERTALTERNATE) {
5768 out_codeset = src_codeset;
5774 out_codeset = src_codeset;
5779 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5780 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5781 strstr(buf, "\nFrom ") != NULL) {
5782 encoding = ENC_QUOTED_PRINTABLE;
5786 mimetext = procmime_mimeinfo_new();
5787 mimetext->content = MIMECONTENT_MEM;
5788 mimetext->tmp = TRUE; /* must free content later */
5789 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5790 * and free the data, which we need later. */
5791 mimetext->data.mem = g_strdup(buf);
5792 mimetext->type = MIMETYPE_TEXT;
5793 mimetext->subtype = g_strdup("plain");
5794 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5795 g_strdup(out_codeset));
5797 /* protect trailing spaces when signing message */
5798 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5799 privacy_system_can_sign(compose->privacy_system)) {
5800 encoding = ENC_QUOTED_PRINTABLE;
5804 debug_print("main text: %Id bytes encoded as %s in %d\n",
5806 debug_print("main text: %zd bytes encoded as %s in %d\n",
5808 strlen(buf), out_codeset, encoding);
5810 /* check for line length limit */
5811 if (action == COMPOSE_WRITE_FOR_SEND &&
5812 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5813 check_line_length(buf, 1000, &line) < 0) {
5816 msg = g_strdup_printf
5817 (_("Line %d exceeds the line length limit (998 bytes).\n"
5818 "The contents of the message might be broken on the way to the delivery.\n"
5820 "Send it anyway?"), line + 1);
5821 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5823 if (aval != G_ALERTALTERNATE) {
5829 if (encoding != ENC_UNKNOWN)
5830 procmime_encode_content(mimetext, encoding);
5832 /* append attachment parts */
5833 if (compose_use_attach(compose) && attach_parts) {
5834 MimeInfo *mimempart;
5835 gchar *boundary = NULL;
5836 mimempart = procmime_mimeinfo_new();
5837 mimempart->content = MIMECONTENT_EMPTY;
5838 mimempart->type = MIMETYPE_MULTIPART;
5839 mimempart->subtype = g_strdup("mixed");
5843 boundary = generate_mime_boundary(NULL);
5844 } while (strstr(buf, boundary) != NULL);
5846 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5849 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5851 g_node_append(mimempart->node, mimetext->node);
5852 g_node_append(mimemsg->node, mimempart->node);
5854 if (compose_add_attachments(compose, mimempart) < 0)
5857 g_node_append(mimemsg->node, mimetext->node);
5861 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5862 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5863 /* extract name and address */
5864 if (strstr(spec, " <") && strstr(spec, ">")) {
5865 from_addr = g_strdup(strrchr(spec, '<')+1);
5866 *(strrchr(from_addr, '>')) = '\0';
5867 from_name = g_strdup(spec);
5868 *(strrchr(from_name, '<')) = '\0';
5875 /* sign message if sending */
5876 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5877 privacy_system_can_sign(compose->privacy_system))
5878 if (!privacy_sign(compose->privacy_system, mimemsg,
5879 compose->account, from_addr)) {
5887 if (compose->use_encryption) {
5888 if (compose->encdata != NULL &&
5889 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5891 /* First, write an unencrypted copy and save it to outbox, if
5892 * user wants that. */
5893 if (compose->account->save_encrypted_as_clear_text) {
5894 debug_print("saving sent message unencrypted...\n");
5895 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5899 /* fp now points to a file with headers written,
5900 * let's make a copy. */
5902 content = file_read_stream_to_str(fp);
5904 str_write_to_file(content, tmp_enc_file);
5907 /* Now write the unencrypted body. */
5908 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5909 procmime_write_mimeinfo(mimemsg, tmpfp);
5912 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5914 outbox = folder_get_default_outbox();
5916 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5917 claws_unlink(tmp_enc_file);
5919 g_warning("Can't open file '%s'", tmp_enc_file);
5922 g_warning("couldn't get tempfile");
5925 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5926 debug_print("Couldn't encrypt mime structure: %s.\n",
5927 privacy_get_error());
5928 alertpanel_error(_("Couldn't encrypt the email: %s"),
5929 privacy_get_error());
5934 procmime_write_mimeinfo(mimemsg, fp);
5936 procmime_mimeinfo_free_all(&mimemsg);
5941 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5943 GtkTextBuffer *buffer;
5944 GtkTextIter start, end;
5949 if ((fp = g_fopen(file, "wb")) == NULL) {
5950 FILE_OP_ERROR(file, "fopen");
5954 /* chmod for security */
5955 if (change_file_mode_rw(fp, file) < 0) {
5956 FILE_OP_ERROR(file, "chmod");
5957 g_warning("can't change file mode");
5960 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5961 gtk_text_buffer_get_start_iter(buffer, &start);
5962 gtk_text_buffer_get_end_iter(buffer, &end);
5963 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5965 chars = conv_codeset_strdup
5966 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5975 len = strlen(chars);
5976 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5977 FILE_OP_ERROR(file, "fwrite");
5986 if (fclose(fp) == EOF) {
5987 FILE_OP_ERROR(file, "fclose");
5994 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5997 MsgInfo *msginfo = compose->targetinfo;
5999 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6000 if (!msginfo) return -1;
6002 if (!force && MSG_IS_LOCKED(msginfo->flags))
6005 item = msginfo->folder;
6006 cm_return_val_if_fail(item != NULL, -1);
6008 if (procmsg_msg_exist(msginfo) &&
6009 (folder_has_parent_of_type(item, F_QUEUE) ||
6010 folder_has_parent_of_type(item, F_DRAFT)
6011 || msginfo == compose->autosaved_draft)) {
6012 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6013 g_warning("can't remove the old message");
6016 debug_print("removed reedit target %d\n", msginfo->msgnum);
6023 static void compose_remove_draft(Compose *compose)
6026 MsgInfo *msginfo = compose->targetinfo;
6027 drafts = account_get_special_folder(compose->account, F_DRAFT);
6029 if (procmsg_msg_exist(msginfo)) {
6030 folder_item_remove_msg(drafts, msginfo->msgnum);
6035 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6036 gboolean remove_reedit_target)
6038 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6041 static gboolean compose_warn_encryption(Compose *compose)
6043 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6044 AlertValue val = G_ALERTALTERNATE;
6046 if (warning == NULL)
6049 val = alertpanel_full(_("Encryption warning"), warning,
6050 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6051 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6052 if (val & G_ALERTDISABLE) {
6053 val &= ~G_ALERTDISABLE;
6054 if (val == G_ALERTALTERNATE)
6055 privacy_inhibit_encrypt_warning(compose->privacy_system,
6059 if (val == G_ALERTALTERNATE) {
6066 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6067 gchar **msgpath, gboolean perform_checks,
6068 gboolean remove_reedit_target)
6075 PrefsAccount *mailac = NULL, *newsac = NULL;
6076 gboolean err = FALSE;
6078 debug_print("queueing message...\n");
6079 cm_return_val_if_fail(compose->account != NULL, -1);
6081 if (compose_check_entries(compose, perform_checks) == FALSE) {
6082 if (compose->batch) {
6083 gtk_widget_show_all(compose->window);
6088 if (!compose->to_list && !compose->newsgroup_list) {
6089 g_warning("can't get recipient list.");
6093 if (compose->to_list) {
6094 if (compose->account->protocol != A_NNTP)
6095 mailac = compose->account;
6096 else if (cur_account && cur_account->protocol != A_NNTP)
6097 mailac = cur_account;
6098 else if (!(mailac = compose_current_mail_account())) {
6099 alertpanel_error(_("No account for sending mails available!"));
6104 if (compose->newsgroup_list) {
6105 if (compose->account->protocol == A_NNTP)
6106 newsac = compose->account;
6108 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6113 /* write queue header */
6114 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6115 G_DIR_SEPARATOR, compose, (guint) rand());
6116 debug_print("queuing to %s\n", tmp);
6117 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6118 FILE_OP_ERROR(tmp, "fopen");
6123 if (change_file_mode_rw(fp, tmp) < 0) {
6124 FILE_OP_ERROR(tmp, "chmod");
6125 g_warning("can't change file mode");
6128 /* queueing variables */
6129 err |= (fprintf(fp, "AF:\n") < 0);
6130 err |= (fprintf(fp, "NF:0\n") < 0);
6131 err |= (fprintf(fp, "PS:10\n") < 0);
6132 err |= (fprintf(fp, "SRH:1\n") < 0);
6133 err |= (fprintf(fp, "SFN:\n") < 0);
6134 err |= (fprintf(fp, "DSR:\n") < 0);
6136 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6138 err |= (fprintf(fp, "MID:\n") < 0);
6139 err |= (fprintf(fp, "CFG:\n") < 0);
6140 err |= (fprintf(fp, "PT:0\n") < 0);
6141 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6142 err |= (fprintf(fp, "RQ:\n") < 0);
6144 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6146 err |= (fprintf(fp, "SSV:\n") < 0);
6148 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6150 err |= (fprintf(fp, "NSV:\n") < 0);
6151 err |= (fprintf(fp, "SSH:\n") < 0);
6152 /* write recepient list */
6153 if (compose->to_list) {
6154 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6155 for (cur = compose->to_list->next; cur != NULL;
6157 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6158 err |= (fprintf(fp, "\n") < 0);
6160 /* write newsgroup list */
6161 if (compose->newsgroup_list) {
6162 err |= (fprintf(fp, "NG:") < 0);
6163 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6164 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6165 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6166 err |= (fprintf(fp, "\n") < 0);
6168 /* Sylpheed account IDs */
6170 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6172 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6175 if (compose->privacy_system != NULL) {
6176 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6177 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6178 if (compose->use_encryption) {
6179 if (!compose_warn_encryption(compose)) {
6185 if (mailac && mailac->encrypt_to_self) {
6186 GSList *tmp_list = g_slist_copy(compose->to_list);
6187 tmp_list = g_slist_append(tmp_list, compose->account->address);
6188 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6189 g_slist_free(tmp_list);
6191 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6193 if (compose->encdata != NULL) {
6194 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6195 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6196 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6197 compose->encdata) < 0);
6198 } /* else we finally dont want to encrypt */
6200 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6201 /* and if encdata was null, it means there's been a problem in
6204 g_warning("failed to write queue message");
6213 /* Save copy folder */
6214 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6215 gchar *savefolderid;
6217 savefolderid = compose_get_save_to(compose);
6218 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6219 g_free(savefolderid);
6221 /* Save copy folder */
6222 if (compose->return_receipt) {
6223 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6225 /* Message-ID of message replying to */
6226 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6227 gchar *folderid = NULL;
6229 if (compose->replyinfo->folder)
6230 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6231 if (folderid == NULL)
6232 folderid = g_strdup("NULL");
6234 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6237 /* Message-ID of message forwarding to */
6238 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6239 gchar *folderid = NULL;
6241 if (compose->fwdinfo->folder)
6242 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6243 if (folderid == NULL)
6244 folderid = g_strdup("NULL");
6246 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6250 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6251 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6253 /* end of headers */
6254 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6256 if (compose->redirect_filename != NULL) {
6257 if (compose_redirect_write_to_file(compose, fp) < 0) {
6265 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6269 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6273 g_warning("failed to write queue message");
6279 if (fclose(fp) == EOF) {
6280 FILE_OP_ERROR(tmp, "fclose");
6286 if (item && *item) {
6289 queue = account_get_special_folder(compose->account, F_QUEUE);
6292 g_warning("can't find queue folder");
6297 folder_item_scan(queue);
6298 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6299 g_warning("can't queue the message");
6305 if (msgpath == NULL) {
6311 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6312 compose_remove_reedit_target(compose, FALSE);
6315 if ((msgnum != NULL) && (item != NULL)) {
6323 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6326 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6329 gchar *type, *subtype;
6330 GtkTreeModel *model;
6333 model = gtk_tree_view_get_model(tree_view);
6335 if (!gtk_tree_model_get_iter_first(model, &iter))
6338 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6340 if (!is_file_exist(ainfo->file)) {
6341 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6342 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6343 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6345 if (val == G_ALERTDEFAULT) {
6350 if (g_stat(ainfo->file, &statbuf) < 0)
6353 mimepart = procmime_mimeinfo_new();
6354 mimepart->content = MIMECONTENT_FILE;
6355 mimepart->data.filename = g_strdup(ainfo->file);
6356 mimepart->tmp = FALSE; /* or we destroy our attachment */
6357 mimepart->offset = 0;
6358 mimepart->length = statbuf.st_size;
6360 type = g_strdup(ainfo->content_type);
6362 if (!strchr(type, '/')) {
6364 type = g_strdup("application/octet-stream");
6367 subtype = strchr(type, '/') + 1;
6368 *(subtype - 1) = '\0';
6369 mimepart->type = procmime_get_media_type(type);
6370 mimepart->subtype = g_strdup(subtype);
6373 if (mimepart->type == MIMETYPE_MESSAGE &&
6374 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6375 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6376 } else if (mimepart->type == MIMETYPE_TEXT) {
6377 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6378 /* Text parts with no name come from multipart/alternative
6379 * forwards. Make sure the recipient won't look at the
6380 * original HTML part by mistake. */
6381 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6382 ainfo->name = g_strdup_printf(_("Original %s part"),
6386 g_hash_table_insert(mimepart->typeparameters,
6387 g_strdup("charset"), g_strdup(ainfo->charset));
6389 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6390 if (mimepart->type == MIMETYPE_APPLICATION &&
6391 !strcmp2(mimepart->subtype, "octet-stream"))
6392 g_hash_table_insert(mimepart->typeparameters,
6393 g_strdup("name"), g_strdup(ainfo->name));
6394 g_hash_table_insert(mimepart->dispositionparameters,
6395 g_strdup("filename"), g_strdup(ainfo->name));
6396 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6399 if (mimepart->type == MIMETYPE_MESSAGE
6400 || mimepart->type == MIMETYPE_MULTIPART)
6401 ainfo->encoding = ENC_BINARY;
6402 else if (compose->use_signing) {
6403 if (ainfo->encoding == ENC_7BIT)
6404 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6405 else if (ainfo->encoding == ENC_8BIT)
6406 ainfo->encoding = ENC_BASE64;
6409 procmime_encode_content(mimepart, ainfo->encoding);
6411 g_node_append(parent->node, mimepart->node);
6412 } while (gtk_tree_model_iter_next(model, &iter));
6417 static gchar *compose_quote_list_of_addresses(gchar *str)
6419 GSList *list = NULL, *item = NULL;
6420 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6422 list = address_list_append_with_comments(list, str);
6423 for (item = list; item != NULL; item = item->next) {
6424 gchar *spec = item->data;
6425 gchar *endofname = strstr(spec, " <");
6426 if (endofname != NULL) {
6429 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6430 qqname = escape_internal_quotes(qname, '"');
6432 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6433 gchar *addr = g_strdup(endofname);
6434 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6435 faddr = g_strconcat(name, addr, NULL);
6438 debug_print("new auto-quoted address: '%s'\n", faddr);
6442 result = g_strdup((faddr != NULL)? faddr: spec);
6444 result = g_strconcat(result,
6446 (faddr != NULL)? faddr: spec,
6449 if (faddr != NULL) {
6454 slist_free_strings_full(list);
6459 #define IS_IN_CUSTOM_HEADER(header) \
6460 (compose->account->add_customhdr && \
6461 custom_header_find(compose->account->customhdr_list, header) != NULL)
6463 static const gchar *compose_untranslated_header_name(gchar *header_name)
6465 /* return the untranslated header name, if header_name is a known
6466 header name, in either its translated or untranslated form, with
6467 or without trailing colon. otherwise, returns header_name. */
6468 gchar *translated_header_name;
6469 gchar *translated_header_name_wcolon;
6470 const gchar *untranslated_header_name;
6471 const gchar *untranslated_header_name_wcolon;
6474 cm_return_val_if_fail(header_name != NULL, NULL);
6476 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6477 untranslated_header_name = HEADERS[i].header_name;
6478 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6480 translated_header_name = gettext(untranslated_header_name);
6481 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6483 if (!strcmp(header_name, untranslated_header_name) ||
6484 !strcmp(header_name, translated_header_name)) {
6485 return untranslated_header_name;
6487 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6488 !strcmp(header_name, translated_header_name_wcolon)) {
6489 return untranslated_header_name_wcolon;
6493 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6497 static void compose_add_headerfield_from_headerlist(Compose *compose,
6499 const gchar *fieldname,
6500 const gchar *seperator)
6502 gchar *str, *fieldname_w_colon;
6503 gboolean add_field = FALSE;
6505 ComposeHeaderEntry *headerentry;
6506 const gchar *headerentryname;
6507 const gchar *trans_fieldname;
6510 if (IS_IN_CUSTOM_HEADER(fieldname))
6513 debug_print("Adding %s-fields\n", fieldname);
6515 fieldstr = g_string_sized_new(64);
6517 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6518 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6520 for (list = compose->header_list; list; list = list->next) {
6521 headerentry = ((ComposeHeaderEntry *)list->data);
6522 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6524 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6525 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6527 str = compose_quote_list_of_addresses(ustr);
6529 if (str != NULL && str[0] != '\0') {
6531 g_string_append(fieldstr, seperator);
6532 g_string_append(fieldstr, str);
6541 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6542 compose_convert_header
6543 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6544 strlen(fieldname) + 2, TRUE);
6545 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6549 g_free(fieldname_w_colon);
6550 g_string_free(fieldstr, TRUE);
6555 static gchar *compose_get_manual_headers_info(Compose *compose)
6557 GString *sh_header = g_string_new(" ");
6559 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6561 for (list = compose->header_list; list; list = list->next) {
6562 ComposeHeaderEntry *headerentry;
6565 gchar *headername_wcolon;
6566 const gchar *headername_trans;
6568 gboolean standard_header = FALSE;
6570 headerentry = ((ComposeHeaderEntry *)list->data);
6572 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6574 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6579 if (!strstr(tmp, ":")) {
6580 headername_wcolon = g_strconcat(tmp, ":", NULL);
6581 headername = g_strdup(tmp);
6583 headername_wcolon = g_strdup(tmp);
6584 headername = g_strdup(strtok(tmp, ":"));
6588 string = std_headers;
6589 while (*string != NULL) {
6590 headername_trans = prefs_common_translated_header_name(*string);
6591 if (!strcmp(headername_trans, headername_wcolon))
6592 standard_header = TRUE;
6595 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6596 g_string_append_printf(sh_header, "%s ", headername);
6598 g_free(headername_wcolon);
6600 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6601 return g_string_free(sh_header, FALSE);
6604 static gchar *compose_get_header(Compose *compose)
6606 gchar date[RFC822_DATE_BUFFSIZE];
6607 gchar buf[BUFFSIZE];
6608 const gchar *entry_str;
6612 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6614 gchar *from_name = NULL, *from_address = NULL;
6617 cm_return_val_if_fail(compose->account != NULL, NULL);
6618 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6620 header = g_string_sized_new(64);
6623 if (prefs_common.hide_timezone)
6624 get_rfc822_date_hide_tz(date, sizeof(date));
6626 get_rfc822_date(date, sizeof(date));
6627 g_string_append_printf(header, "Date: %s\n", date);
6631 if (compose->account->name && *compose->account->name) {
6633 QUOTE_IF_REQUIRED(buf, compose->account->name);
6634 tmp = g_strdup_printf("%s <%s>",
6635 buf, compose->account->address);
6637 tmp = g_strdup_printf("%s",
6638 compose->account->address);
6640 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6641 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6643 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6644 from_address = g_strdup(compose->account->address);
6646 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6647 /* extract name and address */
6648 if (strstr(spec, " <") && strstr(spec, ">")) {
6649 from_address = g_strdup(strrchr(spec, '<')+1);
6650 *(strrchr(from_address, '>')) = '\0';
6651 from_name = g_strdup(spec);
6652 *(strrchr(from_name, '<')) = '\0';
6655 from_address = g_strdup(spec);
6662 if (from_name && *from_name) {
6664 compose_convert_header
6665 (compose, buf, sizeof(buf), from_name,
6666 strlen("From: "), TRUE);
6667 QUOTE_IF_REQUIRED(name, buf);
6668 qname = escape_internal_quotes(name, '"');
6670 g_string_append_printf(header, "From: %s <%s>\n",
6671 qname, from_address);
6672 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6673 compose->return_receipt) {
6674 compose_convert_header(compose, buf, sizeof(buf), from_name,
6675 strlen("Disposition-Notification-To: "),
6677 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6682 g_string_append_printf(header, "From: %s\n", from_address);
6683 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6684 compose->return_receipt)
6685 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6689 g_free(from_address);
6692 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6695 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6698 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6702 * If this account is a NNTP account remove Bcc header from
6703 * message body since it otherwise will be publicly shown
6705 if (compose->account->protocol != A_NNTP)
6706 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6709 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6711 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6714 compose_convert_header(compose, buf, sizeof(buf), str,
6715 strlen("Subject: "), FALSE);
6716 g_string_append_printf(header, "Subject: %s\n", buf);
6722 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6723 g_string_append_printf(header, "Message-ID: <%s>\n",
6727 if (compose->remove_references == FALSE) {
6729 if (compose->inreplyto && compose->to_list)
6730 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6733 if (compose->references)
6734 g_string_append_printf(header, "References: %s\n", compose->references);
6738 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6741 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6744 if (compose->account->organization &&
6745 strlen(compose->account->organization) &&
6746 !IS_IN_CUSTOM_HEADER("Organization")) {
6747 compose_convert_header(compose, buf, sizeof(buf),
6748 compose->account->organization,
6749 strlen("Organization: "), FALSE);
6750 g_string_append_printf(header, "Organization: %s\n", buf);
6753 /* Program version and system info */
6754 if (compose->account->gen_xmailer &&
6755 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6756 !compose->newsgroup_list) {
6757 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6759 gtk_major_version, gtk_minor_version, gtk_micro_version,
6762 if (compose->account->gen_xmailer &&
6763 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6764 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6766 gtk_major_version, gtk_minor_version, gtk_micro_version,
6770 /* custom headers */
6771 if (compose->account->add_customhdr) {
6774 for (cur = compose->account->customhdr_list; cur != NULL;
6776 CustomHeader *chdr = (CustomHeader *)cur->data;
6778 if (custom_header_is_allowed(chdr->name)
6779 && chdr->value != NULL
6780 && *(chdr->value) != '\0') {
6781 compose_convert_header
6782 (compose, buf, sizeof(buf),
6784 strlen(chdr->name) + 2, FALSE);
6785 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6790 /* Automatic Faces and X-Faces */
6791 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6792 g_string_append_printf(header, "X-Face: %s\n", buf);
6794 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6795 g_string_append_printf(header, "X-Face: %s\n", buf);
6797 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6798 g_string_append_printf(header, "Face: %s\n", buf);
6800 else if (get_default_face (buf, sizeof(buf)) == 0) {
6801 g_string_append_printf(header, "Face: %s\n", buf);
6805 switch (compose->priority) {
6806 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6807 "X-Priority: 1 (Highest)\n");
6809 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6810 "X-Priority: 2 (High)\n");
6812 case PRIORITY_NORMAL: break;
6813 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6814 "X-Priority: 4 (Low)\n");
6816 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6817 "X-Priority: 5 (Lowest)\n");
6819 default: debug_print("compose: priority unknown : %d\n",
6823 /* get special headers */
6824 for (list = compose->header_list; list; list = list->next) {
6825 ComposeHeaderEntry *headerentry;
6828 gchar *headername_wcolon;
6829 const gchar *headername_trans;
6832 gboolean standard_header = FALSE;
6834 headerentry = ((ComposeHeaderEntry *)list->data);
6836 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6838 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6843 if (!strstr(tmp, ":")) {
6844 headername_wcolon = g_strconcat(tmp, ":", NULL);
6845 headername = g_strdup(tmp);
6847 headername_wcolon = g_strdup(tmp);
6848 headername = g_strdup(strtok(tmp, ":"));
6852 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6853 Xstrdup_a(headervalue, entry_str, return NULL);
6854 subst_char(headervalue, '\r', ' ');
6855 subst_char(headervalue, '\n', ' ');
6856 g_strstrip(headervalue);
6857 if (*headervalue != '\0') {
6858 string = std_headers;
6859 while (*string != NULL && !standard_header) {
6860 headername_trans = prefs_common_translated_header_name(*string);
6861 /* support mixed translated and untranslated headers */
6862 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6863 standard_header = TRUE;
6866 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6867 /* store untranslated header name */
6868 g_string_append_printf(header, "%s %s\n",
6869 compose_untranslated_header_name(headername_wcolon), headervalue);
6873 g_free(headername_wcolon);
6877 g_string_free(header, FALSE);
6882 #undef IS_IN_CUSTOM_HEADER
6884 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6885 gint header_len, gboolean addr_field)
6887 gchar *tmpstr = NULL;
6888 const gchar *out_codeset = NULL;
6890 cm_return_if_fail(src != NULL);
6891 cm_return_if_fail(dest != NULL);
6893 if (len < 1) return;
6895 tmpstr = g_strdup(src);
6897 subst_char(tmpstr, '\n', ' ');
6898 subst_char(tmpstr, '\r', ' ');
6901 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6902 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6903 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6908 codeconv_set_strict(TRUE);
6909 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6910 conv_get_charset_str(compose->out_encoding));
6911 codeconv_set_strict(FALSE);
6913 if (!dest || *dest == '\0') {
6914 gchar *test_conv_global_out = NULL;
6915 gchar *test_conv_reply = NULL;
6917 /* automatic mode. be automatic. */
6918 codeconv_set_strict(TRUE);
6920 out_codeset = conv_get_outgoing_charset_str();
6922 debug_print("trying to convert to %s\n", out_codeset);
6923 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6926 if (!test_conv_global_out && compose->orig_charset
6927 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6928 out_codeset = compose->orig_charset;
6929 debug_print("failure; trying to convert to %s\n", out_codeset);
6930 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6933 if (!test_conv_global_out && !test_conv_reply) {
6935 out_codeset = CS_INTERNAL;
6936 debug_print("finally using %s\n", out_codeset);
6938 g_free(test_conv_global_out);
6939 g_free(test_conv_reply);
6940 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6942 codeconv_set_strict(FALSE);
6947 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6951 cm_return_if_fail(user_data != NULL);
6953 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6954 g_strstrip(address);
6955 if (*address != '\0') {
6956 gchar *name = procheader_get_fromname(address);
6957 extract_address(address);
6958 #ifndef USE_ALT_ADDRBOOK
6959 addressbook_add_contact(name, address, NULL, NULL);
6961 debug_print("%s: %s\n", name, address);
6962 if (addressadd_selection(name, address, NULL, NULL)) {
6963 debug_print( "addressbook_add_contact - added\n" );
6970 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6972 GtkWidget *menuitem;
6975 cm_return_if_fail(menu != NULL);
6976 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6978 menuitem = gtk_separator_menu_item_new();
6979 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6980 gtk_widget_show(menuitem);
6982 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6983 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6985 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6986 g_strstrip(address);
6987 if (*address == '\0') {
6988 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6991 g_signal_connect(G_OBJECT(menuitem), "activate",
6992 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6993 gtk_widget_show(menuitem);
6996 void compose_add_extra_header(gchar *header, GtkListStore *model)
6999 if (strcmp(header, "")) {
7000 COMBOBOX_ADD(model, header, COMPOSE_TO);
7004 void compose_add_extra_header_entries(GtkListStore *model)
7008 gchar buf[BUFFSIZE];
7011 if (extra_headers == NULL) {
7012 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7013 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7014 debug_print("extra headers file not found\n");
7015 goto extra_headers_done;
7017 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7018 lastc = strlen(buf) - 1; /* remove trailing control chars */
7019 while (lastc >= 0 && buf[lastc] != ':')
7020 buf[lastc--] = '\0';
7021 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7022 buf[lastc] = '\0'; /* remove trailing : for comparison */
7023 if (custom_header_is_allowed(buf)) {
7025 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7028 g_message("disallowed extra header line: %s\n", buf);
7032 g_message("invalid extra header line: %s\n", buf);
7038 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7039 extra_headers = g_slist_reverse(extra_headers);
7041 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7044 static void compose_create_header_entry(Compose *compose)
7046 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7053 const gchar *header = NULL;
7054 ComposeHeaderEntry *headerentry;
7055 gboolean standard_header = FALSE;
7056 GtkListStore *model;
7059 headerentry = g_new0(ComposeHeaderEntry, 1);
7061 /* Combo box model */
7062 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7063 #if !GTK_CHECK_VERSION(2, 24, 0)
7064 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7066 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7068 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7070 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7072 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7073 COMPOSE_NEWSGROUPS);
7074 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7076 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7077 COMPOSE_FOLLOWUPTO);
7078 compose_add_extra_header_entries(model);
7081 #if GTK_CHECK_VERSION(2, 24, 0)
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);
7088 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7089 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7090 G_CALLBACK(compose_grab_focus_cb), compose);
7091 gtk_widget_show(combo);
7093 /* Putting only the combobox child into focus chain of its parent causes
7094 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7095 * This eliminates need to pres Tab twice in order to really get from the
7096 * combobox to next widget. */
7098 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7099 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7102 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7103 compose->header_nextrow, compose->header_nextrow+1,
7104 GTK_SHRINK, GTK_FILL, 0, 0);
7105 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7106 const gchar *last_header_entry = gtk_entry_get_text(
7107 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7109 while (*string != NULL) {
7110 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7111 standard_header = TRUE;
7114 if (standard_header)
7115 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7117 if (!compose->header_last || !standard_header) {
7118 switch(compose->account->protocol) {
7120 header = prefs_common_translated_header_name("Newsgroups:");
7123 header = prefs_common_translated_header_name("To:");
7128 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7130 gtk_editable_set_editable(
7131 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7132 prefs_common.type_any_header);
7134 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7135 G_CALLBACK(compose_grab_focus_cb), compose);
7137 /* Entry field with cleanup button */
7138 button = gtk_button_new();
7139 gtk_button_set_image(GTK_BUTTON(button),
7140 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7141 gtk_widget_show(button);
7142 CLAWS_SET_TIP(button,
7143 _("Delete entry contents"));
7144 entry = gtk_entry_new();
7145 gtk_widget_show(entry);
7146 CLAWS_SET_TIP(entry,
7147 _("Use <tab> to autocomplete from addressbook"));
7148 hbox = gtk_hbox_new (FALSE, 0);
7149 gtk_widget_show(hbox);
7150 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7151 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7152 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7153 compose->header_nextrow, compose->header_nextrow+1,
7154 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7156 g_signal_connect(G_OBJECT(entry), "key-press-event",
7157 G_CALLBACK(compose_headerentry_key_press_event_cb),
7159 g_signal_connect(G_OBJECT(entry), "changed",
7160 G_CALLBACK(compose_headerentry_changed_cb),
7162 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7163 G_CALLBACK(compose_grab_focus_cb), compose);
7165 g_signal_connect(G_OBJECT(button), "clicked",
7166 G_CALLBACK(compose_headerentry_button_clicked_cb),
7170 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7171 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7172 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7173 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7174 G_CALLBACK(compose_header_drag_received_cb),
7176 g_signal_connect(G_OBJECT(entry), "drag-drop",
7177 G_CALLBACK(compose_drag_drop),
7179 g_signal_connect(G_OBJECT(entry), "populate-popup",
7180 G_CALLBACK(compose_entry_popup_extend),
7183 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7185 headerentry->compose = compose;
7186 headerentry->combo = combo;
7187 headerentry->entry = entry;
7188 headerentry->button = button;
7189 headerentry->hbox = hbox;
7190 headerentry->headernum = compose->header_nextrow;
7191 headerentry->type = PREF_NONE;
7193 compose->header_nextrow++;
7194 compose->header_last = headerentry;
7195 compose->header_list =
7196 g_slist_append(compose->header_list,
7200 static void compose_add_header_entry(Compose *compose, const gchar *header,
7201 gchar *text, ComposePrefType pref_type)
7203 ComposeHeaderEntry *last_header = compose->header_last;
7204 gchar *tmp = g_strdup(text), *email;
7205 gboolean replyto_hdr;
7207 replyto_hdr = (!strcasecmp(header,
7208 prefs_common_translated_header_name("Reply-To:")) ||
7210 prefs_common_translated_header_name("Followup-To:")) ||
7212 prefs_common_translated_header_name("In-Reply-To:")));
7214 extract_address(tmp);
7215 email = g_utf8_strdown(tmp, -1);
7217 if (replyto_hdr == FALSE &&
7218 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7220 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7221 header, text, (gint) pref_type);
7227 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7228 gtk_entry_set_text(GTK_ENTRY(
7229 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7231 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7232 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7233 last_header->type = pref_type;
7235 if (replyto_hdr == FALSE)
7236 g_hash_table_insert(compose->email_hashtable, email,
7237 GUINT_TO_POINTER(1));
7244 static void compose_destroy_headerentry(Compose *compose,
7245 ComposeHeaderEntry *headerentry)
7247 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7250 extract_address(text);
7251 email = g_utf8_strdown(text, -1);
7252 g_hash_table_remove(compose->email_hashtable, email);
7256 gtk_widget_destroy(headerentry->combo);
7257 gtk_widget_destroy(headerentry->entry);
7258 gtk_widget_destroy(headerentry->button);
7259 gtk_widget_destroy(headerentry->hbox);
7260 g_free(headerentry);
7263 static void compose_remove_header_entries(Compose *compose)
7266 for (list = compose->header_list; list; list = list->next)
7267 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7269 compose->header_last = NULL;
7270 g_slist_free(compose->header_list);
7271 compose->header_list = NULL;
7272 compose->header_nextrow = 1;
7273 compose_create_header_entry(compose);
7276 static GtkWidget *compose_create_header(Compose *compose)
7278 GtkWidget *from_optmenu_hbox;
7279 GtkWidget *header_table_main;
7280 GtkWidget *header_scrolledwin;
7281 GtkWidget *header_table;
7283 /* parent with account selection and from header */
7284 header_table_main = gtk_table_new(2, 2, FALSE);
7285 gtk_widget_show(header_table_main);
7286 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7288 from_optmenu_hbox = compose_account_option_menu_create(compose);
7289 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7290 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7292 /* child with header labels and entries */
7293 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7294 gtk_widget_show(header_scrolledwin);
7295 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7297 header_table = gtk_table_new(2, 2, FALSE);
7298 gtk_widget_show(header_table);
7299 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7300 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7301 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7302 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7303 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7305 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7306 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7308 compose->header_table = header_table;
7309 compose->header_list = NULL;
7310 compose->header_nextrow = 0;
7312 compose_create_header_entry(compose);
7314 compose->table = NULL;
7316 return header_table_main;
7319 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7321 Compose *compose = (Compose *)data;
7322 GdkEventButton event;
7325 event.time = gtk_get_current_event_time();
7327 return attach_button_pressed(compose->attach_clist, &event, compose);
7330 static GtkWidget *compose_create_attach(Compose *compose)
7332 GtkWidget *attach_scrwin;
7333 GtkWidget *attach_clist;
7335 GtkListStore *store;
7336 GtkCellRenderer *renderer;
7337 GtkTreeViewColumn *column;
7338 GtkTreeSelection *selection;
7340 /* attachment list */
7341 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7342 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7343 GTK_POLICY_AUTOMATIC,
7344 GTK_POLICY_AUTOMATIC);
7345 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7347 store = gtk_list_store_new(N_ATTACH_COLS,
7353 G_TYPE_AUTO_POINTER,
7355 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7356 (GTK_TREE_MODEL(store)));
7357 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7358 g_object_unref(store);
7360 renderer = gtk_cell_renderer_text_new();
7361 column = gtk_tree_view_column_new_with_attributes
7362 (_("Mime type"), renderer, "text",
7363 COL_MIMETYPE, NULL);
7364 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7366 renderer = gtk_cell_renderer_text_new();
7367 column = gtk_tree_view_column_new_with_attributes
7368 (_("Size"), renderer, "text",
7370 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7372 renderer = gtk_cell_renderer_text_new();
7373 column = gtk_tree_view_column_new_with_attributes
7374 (_("Name"), renderer, "text",
7376 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7378 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7379 prefs_common.use_stripes_everywhere);
7380 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7381 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7383 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7384 G_CALLBACK(attach_selected), compose);
7385 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7386 G_CALLBACK(attach_button_pressed), compose);
7387 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7388 G_CALLBACK(popup_attach_button_pressed), compose);
7389 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7390 G_CALLBACK(attach_key_pressed), compose);
7393 gtk_drag_dest_set(attach_clist,
7394 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7395 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7396 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7397 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7398 G_CALLBACK(compose_attach_drag_received_cb),
7400 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7401 G_CALLBACK(compose_drag_drop),
7404 compose->attach_scrwin = attach_scrwin;
7405 compose->attach_clist = attach_clist;
7407 return attach_scrwin;
7410 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7411 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7413 static GtkWidget *compose_create_others(Compose *compose)
7416 GtkWidget *savemsg_checkbtn;
7417 GtkWidget *savemsg_combo;
7418 GtkWidget *savemsg_select;
7421 gchar *folderidentifier;
7423 /* Table for settings */
7424 table = gtk_table_new(3, 1, FALSE);
7425 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7426 gtk_widget_show(table);
7427 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7430 /* Save Message to folder */
7431 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7432 gtk_widget_show(savemsg_checkbtn);
7433 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7434 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7435 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7437 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7438 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7440 #if !GTK_CHECK_VERSION(2, 24, 0)
7441 savemsg_combo = gtk_combo_box_entry_new_text();
7443 savemsg_combo = gtk_combo_box_text_new_with_entry();
7445 compose->savemsg_checkbtn = savemsg_checkbtn;
7446 compose->savemsg_combo = savemsg_combo;
7447 gtk_widget_show(savemsg_combo);
7449 if (prefs_common.compose_save_to_history)
7450 #if !GTK_CHECK_VERSION(2, 24, 0)
7451 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7452 prefs_common.compose_save_to_history);
7454 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7455 prefs_common.compose_save_to_history);
7457 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7458 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7459 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7460 G_CALLBACK(compose_grab_focus_cb), compose);
7461 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7462 folderidentifier = folder_item_get_identifier(account_get_special_folder
7463 (compose->account, F_OUTBOX));
7464 compose_set_save_to(compose, folderidentifier);
7465 g_free(folderidentifier);
7468 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7469 gtk_widget_show(savemsg_select);
7470 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7471 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7472 G_CALLBACK(compose_savemsg_select_cb),
7478 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7480 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7481 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7484 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7489 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7490 _("Select folder to save message to"));
7493 path = folder_item_get_identifier(dest);
7495 compose_set_save_to(compose, path);
7499 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7500 GdkAtom clip, GtkTextIter *insert_place);
7503 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7507 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7509 if (event->button == 3) {
7511 GtkTextIter sel_start, sel_end;
7512 gboolean stuff_selected;
7514 /* move the cursor to allow GtkAspell to check the word
7515 * under the mouse */
7516 if (event->x && event->y) {
7517 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7518 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7520 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7523 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7524 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7527 stuff_selected = gtk_text_buffer_get_selection_bounds(
7529 &sel_start, &sel_end);
7531 gtk_text_buffer_place_cursor (buffer, &iter);
7532 /* reselect stuff */
7534 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7535 gtk_text_buffer_select_range(buffer,
7536 &sel_start, &sel_end);
7538 return FALSE; /* pass the event so that the right-click goes through */
7541 if (event->button == 2) {
7546 /* get the middle-click position to paste at the correct place */
7547 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7548 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7550 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7553 entry_paste_clipboard(compose, text,
7554 prefs_common.linewrap_pastes,
7555 GDK_SELECTION_PRIMARY, &iter);
7563 static void compose_spell_menu_changed(void *data)
7565 Compose *compose = (Compose *)data;
7567 GtkWidget *menuitem;
7568 GtkWidget *parent_item;
7569 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7572 if (compose->gtkaspell == NULL)
7575 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7576 "/Menu/Spelling/Options");
7578 /* setting the submenu removes /Spelling/Options from the factory
7579 * so we need to save it */
7581 if (parent_item == NULL) {
7582 parent_item = compose->aspell_options_menu;
7583 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7585 compose->aspell_options_menu = parent_item;
7587 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7589 spell_menu = g_slist_reverse(spell_menu);
7590 for (items = spell_menu;
7591 items; items = items->next) {
7592 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7593 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7594 gtk_widget_show(GTK_WIDGET(menuitem));
7596 g_slist_free(spell_menu);
7598 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7599 gtk_widget_show(parent_item);
7602 static void compose_dict_changed(void *data)
7604 Compose *compose = (Compose *) data;
7606 if(!compose->gtkaspell)
7608 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7611 gtkaspell_highlight_all(compose->gtkaspell);
7612 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7616 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7618 Compose *compose = (Compose *)data;
7619 GdkEventButton event;
7622 event.time = gtk_get_current_event_time();
7626 return text_clicked(compose->text, &event, compose);
7629 static gboolean compose_force_window_origin = TRUE;
7630 static Compose *compose_create(PrefsAccount *account,
7639 GtkWidget *handlebox;
7641 GtkWidget *notebook;
7643 GtkWidget *attach_hbox;
7644 GtkWidget *attach_lab1;
7645 GtkWidget *attach_lab2;
7650 GtkWidget *subject_hbox;
7651 GtkWidget *subject_frame;
7652 GtkWidget *subject_entry;
7656 GtkWidget *edit_vbox;
7657 GtkWidget *ruler_hbox;
7659 GtkWidget *scrolledwin;
7661 GtkTextBuffer *buffer;
7662 GtkClipboard *clipboard;
7664 UndoMain *undostruct;
7666 GtkWidget *popupmenu;
7667 GtkWidget *tmpl_menu;
7668 GtkActionGroup *action_group = NULL;
7671 GtkAspell * gtkaspell = NULL;
7674 static GdkGeometry geometry;
7676 cm_return_val_if_fail(account != NULL, NULL);
7678 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7679 &default_header_bgcolor);
7680 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7681 &default_header_color);
7683 debug_print("Creating compose window...\n");
7684 compose = g_new0(Compose, 1);
7686 compose->batch = batch;
7687 compose->account = account;
7688 compose->folder = folder;
7690 compose->mutex = cm_mutex_new();
7691 compose->set_cursor_pos = -1;
7693 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7695 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7696 gtk_widget_set_size_request(window, prefs_common.compose_width,
7697 prefs_common.compose_height);
7699 if (!geometry.max_width) {
7700 geometry.max_width = gdk_screen_width();
7701 geometry.max_height = gdk_screen_height();
7704 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7705 &geometry, GDK_HINT_MAX_SIZE);
7706 if (!geometry.min_width) {
7707 geometry.min_width = 600;
7708 geometry.min_height = 440;
7710 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7711 &geometry, GDK_HINT_MIN_SIZE);
7713 #ifndef GENERIC_UMPC
7714 if (compose_force_window_origin)
7715 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7716 prefs_common.compose_y);
7718 g_signal_connect(G_OBJECT(window), "delete_event",
7719 G_CALLBACK(compose_delete_cb), compose);
7720 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7721 gtk_widget_realize(window);
7723 gtkut_widget_set_composer_icon(window);
7725 vbox = gtk_vbox_new(FALSE, 0);
7726 gtk_container_add(GTK_CONTAINER(window), vbox);
7728 compose->ui_manager = gtk_ui_manager_new();
7729 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7730 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7731 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7732 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7733 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7734 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7735 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7736 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7737 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7738 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7762 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7767 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7768 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7796 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7857 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)
7858 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)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7864 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)
7865 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)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7870 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)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7874 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)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7880 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)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7887 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)
7888 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)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7901 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)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7919 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7920 gtk_widget_show_all(menubar);
7922 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7923 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7925 if (prefs_common.toolbar_detachable) {
7926 handlebox = gtk_handle_box_new();
7928 handlebox = gtk_hbox_new(FALSE, 0);
7930 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7932 gtk_widget_realize(handlebox);
7933 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7936 vbox2 = gtk_vbox_new(FALSE, 2);
7937 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7938 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7941 notebook = gtk_notebook_new();
7942 gtk_widget_show(notebook);
7944 /* header labels and entries */
7945 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7946 compose_create_header(compose),
7947 gtk_label_new_with_mnemonic(_("Hea_der")));
7948 /* attachment list */
7949 attach_hbox = gtk_hbox_new(FALSE, 0);
7950 gtk_widget_show(attach_hbox);
7952 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7953 gtk_widget_show(attach_lab1);
7954 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7956 attach_lab2 = gtk_label_new("");
7957 gtk_widget_show(attach_lab2);
7958 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7960 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7961 compose_create_attach(compose),
7964 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7965 compose_create_others(compose),
7966 gtk_label_new_with_mnemonic(_("Othe_rs")));
7969 subject_hbox = gtk_hbox_new(FALSE, 0);
7970 gtk_widget_show(subject_hbox);
7972 subject_frame = gtk_frame_new(NULL);
7973 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7974 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7975 gtk_widget_show(subject_frame);
7977 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7978 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7979 gtk_widget_show(subject);
7981 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7982 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7983 gtk_widget_show(label);
7986 subject_entry = claws_spell_entry_new();
7988 subject_entry = gtk_entry_new();
7990 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7991 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7992 G_CALLBACK(compose_grab_focus_cb), compose);
7993 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7994 gtk_widget_show(subject_entry);
7995 compose->subject_entry = subject_entry;
7996 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7998 edit_vbox = gtk_vbox_new(FALSE, 0);
8000 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8003 ruler_hbox = gtk_hbox_new(FALSE, 0);
8004 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8006 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8007 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8008 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8012 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8013 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8014 GTK_POLICY_AUTOMATIC,
8015 GTK_POLICY_AUTOMATIC);
8016 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8018 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8020 text = gtk_text_view_new();
8021 if (prefs_common.show_compose_margin) {
8022 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8023 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8025 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8026 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8027 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8028 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8029 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8031 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8032 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8033 G_CALLBACK(compose_edit_size_alloc),
8035 g_signal_connect(G_OBJECT(buffer), "changed",
8036 G_CALLBACK(compose_changed_cb), compose);
8037 g_signal_connect(G_OBJECT(text), "grab_focus",
8038 G_CALLBACK(compose_grab_focus_cb), compose);
8039 g_signal_connect(G_OBJECT(buffer), "insert_text",
8040 G_CALLBACK(text_inserted), compose);
8041 g_signal_connect(G_OBJECT(text), "button_press_event",
8042 G_CALLBACK(text_clicked), compose);
8043 g_signal_connect(G_OBJECT(text), "popup-menu",
8044 G_CALLBACK(compose_popup_menu), compose);
8045 g_signal_connect(G_OBJECT(subject_entry), "changed",
8046 G_CALLBACK(compose_changed_cb), compose);
8047 g_signal_connect(G_OBJECT(subject_entry), "activate",
8048 G_CALLBACK(compose_subject_entry_activated), compose);
8051 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8052 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8053 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8054 g_signal_connect(G_OBJECT(text), "drag_data_received",
8055 G_CALLBACK(compose_insert_drag_received_cb),
8057 g_signal_connect(G_OBJECT(text), "drag-drop",
8058 G_CALLBACK(compose_drag_drop),
8060 g_signal_connect(G_OBJECT(text), "key-press-event",
8061 G_CALLBACK(completion_set_focus_to_subject),
8063 gtk_widget_show_all(vbox);
8065 /* pane between attach clist and text */
8066 paned = gtk_vpaned_new();
8067 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8068 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8069 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8070 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8071 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8072 G_CALLBACK(compose_notebook_size_alloc), paned);
8074 gtk_widget_show_all(paned);
8077 if (prefs_common.textfont) {
8078 PangoFontDescription *font_desc;
8080 font_desc = pango_font_description_from_string
8081 (prefs_common.textfont);
8083 gtk_widget_modify_font(text, font_desc);
8084 pango_font_description_free(font_desc);
8088 gtk_action_group_add_actions(action_group, compose_popup_entries,
8089 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8090 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8091 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8092 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8093 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8094 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8095 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8097 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8099 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8100 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8101 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8103 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8105 undostruct = undo_init(text);
8106 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8109 address_completion_start(window);
8111 compose->window = window;
8112 compose->vbox = vbox;
8113 compose->menubar = menubar;
8114 compose->handlebox = handlebox;
8116 compose->vbox2 = vbox2;
8118 compose->paned = paned;
8120 compose->attach_label = attach_lab2;
8122 compose->notebook = notebook;
8123 compose->edit_vbox = edit_vbox;
8124 compose->ruler_hbox = ruler_hbox;
8125 compose->ruler = ruler;
8126 compose->scrolledwin = scrolledwin;
8127 compose->text = text;
8129 compose->focused_editable = NULL;
8131 compose->popupmenu = popupmenu;
8133 compose->tmpl_menu = tmpl_menu;
8135 compose->mode = mode;
8136 compose->rmode = mode;
8138 compose->targetinfo = NULL;
8139 compose->replyinfo = NULL;
8140 compose->fwdinfo = NULL;
8142 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8143 g_str_equal, (GDestroyNotify) g_free, NULL);
8145 compose->replyto = NULL;
8147 compose->bcc = NULL;
8148 compose->followup_to = NULL;
8150 compose->ml_post = NULL;
8152 compose->inreplyto = NULL;
8153 compose->references = NULL;
8154 compose->msgid = NULL;
8155 compose->boundary = NULL;
8157 compose->autowrap = prefs_common.autowrap;
8158 compose->autoindent = prefs_common.auto_indent;
8159 compose->use_signing = FALSE;
8160 compose->use_encryption = FALSE;
8161 compose->privacy_system = NULL;
8162 compose->encdata = NULL;
8164 compose->modified = FALSE;
8166 compose->return_receipt = FALSE;
8168 compose->to_list = NULL;
8169 compose->newsgroup_list = NULL;
8171 compose->undostruct = undostruct;
8173 compose->sig_str = NULL;
8175 compose->exteditor_file = NULL;
8176 compose->exteditor_pid = -1;
8177 compose->exteditor_tag = -1;
8178 compose->exteditor_socket = NULL;
8179 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8181 compose->folder_update_callback_id =
8182 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8183 compose_update_folder_hook,
8184 (gpointer) compose);
8187 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8188 if (mode != COMPOSE_REDIRECT) {
8189 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8190 strcmp(prefs_common.dictionary, "")) {
8191 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8192 prefs_common.alt_dictionary,
8193 conv_get_locale_charset_str(),
8194 prefs_common.misspelled_col,
8195 prefs_common.check_while_typing,
8196 prefs_common.recheck_when_changing_dict,
8197 prefs_common.use_alternate,
8198 prefs_common.use_both_dicts,
8199 GTK_TEXT_VIEW(text),
8200 GTK_WINDOW(compose->window),
8201 compose_dict_changed,
8202 compose_spell_menu_changed,
8205 alertpanel_error(_("Spell checker could not "
8207 gtkaspell_checkers_strerror());
8208 gtkaspell_checkers_reset_error();
8210 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8214 compose->gtkaspell = gtkaspell;
8215 compose_spell_menu_changed(compose);
8216 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8219 compose_select_account(compose, account, TRUE);
8221 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8222 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8224 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8225 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8227 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8228 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8230 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8231 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8233 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8234 if (account->protocol != A_NNTP)
8235 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8236 prefs_common_translated_header_name("To:"));
8238 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8239 prefs_common_translated_header_name("Newsgroups:"));
8241 #ifndef USE_ALT_ADDRBOOK
8242 addressbook_set_target_compose(compose);
8244 if (mode != COMPOSE_REDIRECT)
8245 compose_set_template_menu(compose);
8247 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8250 compose_list = g_list_append(compose_list, compose);
8252 if (!prefs_common.show_ruler)
8253 gtk_widget_hide(ruler_hbox);
8255 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8258 compose->priority = PRIORITY_NORMAL;
8259 compose_update_priority_menu_item(compose);
8261 compose_set_out_encoding(compose);
8264 compose_update_actions_menu(compose);
8266 /* Privacy Systems menu */
8267 compose_update_privacy_systems_menu(compose);
8269 activate_privacy_system(compose, account, TRUE);
8270 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8272 gtk_widget_realize(window);
8274 gtk_widget_show(window);
8280 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8285 GtkWidget *optmenubox;
8286 GtkWidget *fromlabel;
8289 GtkWidget *from_name = NULL;
8291 gint num = 0, def_menu = 0;
8293 accounts = account_get_list();
8294 cm_return_val_if_fail(accounts != NULL, NULL);
8296 optmenubox = gtk_event_box_new();
8297 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8298 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8300 hbox = gtk_hbox_new(FALSE, 4);
8301 from_name = gtk_entry_new();
8303 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8304 G_CALLBACK(compose_grab_focus_cb), compose);
8305 g_signal_connect_after(G_OBJECT(from_name), "activate",
8306 G_CALLBACK(from_name_activate_cb), optmenu);
8308 for (; accounts != NULL; accounts = accounts->next, num++) {
8309 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8310 gchar *name, *from = NULL;
8312 if (ac == compose->account) def_menu = num;
8314 name = g_markup_printf_escaped("<i>%s</i>",
8317 if (ac == compose->account) {
8318 if (ac->name && *ac->name) {
8320 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8321 from = g_strdup_printf("%s <%s>",
8323 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8325 from = g_strdup_printf("%s",
8327 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8329 if (cur_account != compose->account) {
8330 gtk_widget_modify_base(
8331 GTK_WIDGET(from_name),
8332 GTK_STATE_NORMAL, &default_header_bgcolor);
8333 gtk_widget_modify_text(
8334 GTK_WIDGET(from_name),
8335 GTK_STATE_NORMAL, &default_header_color);
8338 COMBOBOX_ADD(menu, name, ac->account_id);
8343 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8345 g_signal_connect(G_OBJECT(optmenu), "changed",
8346 G_CALLBACK(account_activated),
8348 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8349 G_CALLBACK(compose_entry_popup_extend),
8352 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8353 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8355 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8356 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8357 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8359 /* Putting only the GtkEntry into focus chain of parent hbox causes
8360 * the account selector combobox next to it to be unreachable when
8361 * navigating widgets in GtkTable with up/down arrow keys.
8362 * Note: gtk_widget_set_can_focus() was not enough. */
8364 l = g_list_prepend(l, from_name);
8365 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8368 CLAWS_SET_TIP(optmenubox,
8369 _("Account to use for this email"));
8370 CLAWS_SET_TIP(from_name,
8371 _("Sender address to be used"));
8373 compose->account_combo = optmenu;
8374 compose->from_name = from_name;
8379 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8381 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8382 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8383 Compose *compose = (Compose *) data;
8385 compose->priority = value;
8389 static void compose_reply_change_mode(Compose *compose,
8392 gboolean was_modified = compose->modified;
8394 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8396 cm_return_if_fail(compose->replyinfo != NULL);
8398 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8400 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8402 if (action == COMPOSE_REPLY_TO_ALL)
8404 if (action == COMPOSE_REPLY_TO_SENDER)
8406 if (action == COMPOSE_REPLY_TO_LIST)
8409 compose_remove_header_entries(compose);
8410 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8411 if (compose->account->set_autocc && compose->account->auto_cc)
8412 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8414 if (compose->account->set_autobcc && compose->account->auto_bcc)
8415 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8417 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8418 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8419 compose_show_first_last_header(compose, TRUE);
8420 compose->modified = was_modified;
8421 compose_set_title(compose);
8424 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8426 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8427 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8428 Compose *compose = (Compose *) data;
8431 compose_reply_change_mode(compose, value);
8434 static void compose_update_priority_menu_item(Compose * compose)
8436 GtkWidget *menuitem = NULL;
8437 switch (compose->priority) {
8438 case PRIORITY_HIGHEST:
8439 menuitem = gtk_ui_manager_get_widget
8440 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8443 menuitem = gtk_ui_manager_get_widget
8444 (compose->ui_manager, "/Menu/Options/Priority/High");
8446 case PRIORITY_NORMAL:
8447 menuitem = gtk_ui_manager_get_widget
8448 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8451 menuitem = gtk_ui_manager_get_widget
8452 (compose->ui_manager, "/Menu/Options/Priority/Low");
8454 case PRIORITY_LOWEST:
8455 menuitem = gtk_ui_manager_get_widget
8456 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8459 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8462 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8464 Compose *compose = (Compose *) data;
8466 gboolean can_sign = FALSE, can_encrypt = FALSE;
8468 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8470 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8473 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8474 g_free(compose->privacy_system);
8475 compose->privacy_system = NULL;
8476 g_free(compose->encdata);
8477 compose->encdata = NULL;
8478 if (systemid != NULL) {
8479 compose->privacy_system = g_strdup(systemid);
8481 can_sign = privacy_system_can_sign(systemid);
8482 can_encrypt = privacy_system_can_encrypt(systemid);
8485 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8487 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8488 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8489 if (compose->toolbar->privacy_sign_btn != NULL) {
8490 gtk_widget_set_sensitive(
8491 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8493 gtk_toggle_tool_button_set_active(
8494 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8495 can_sign ? compose->use_signing : FALSE);
8497 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8498 gtk_widget_set_sensitive(
8499 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8501 gtk_toggle_tool_button_set_active(
8502 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8503 can_encrypt ? compose->use_encryption : FALSE);
8507 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8509 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8510 GtkWidget *menuitem = NULL;
8511 GList *children, *amenu;
8512 gboolean can_sign = FALSE, can_encrypt = FALSE;
8513 gboolean found = FALSE;
8515 if (compose->privacy_system != NULL) {
8517 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8518 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8519 cm_return_if_fail(menuitem != NULL);
8521 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8524 while (amenu != NULL) {
8525 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8526 if (systemid != NULL) {
8527 if (strcmp(systemid, compose->privacy_system) == 0 &&
8528 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8529 menuitem = GTK_WIDGET(amenu->data);
8531 can_sign = privacy_system_can_sign(systemid);
8532 can_encrypt = privacy_system_can_encrypt(systemid);
8536 } else if (strlen(compose->privacy_system) == 0 &&
8537 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8538 menuitem = GTK_WIDGET(amenu->data);
8541 can_encrypt = FALSE;
8546 amenu = amenu->next;
8548 g_list_free(children);
8549 if (menuitem != NULL)
8550 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8552 if (warn && !found && strlen(compose->privacy_system)) {
8553 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8554 "will not be able to sign or encrypt this message."),
8555 compose->privacy_system);
8559 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8560 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8561 if (compose->toolbar->privacy_sign_btn != NULL) {
8562 gtk_widget_set_sensitive(
8563 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8566 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8567 gtk_widget_set_sensitive(
8568 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8573 static void compose_set_out_encoding(Compose *compose)
8575 CharSet out_encoding;
8576 const gchar *branch = NULL;
8577 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8579 switch(out_encoding) {
8580 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8581 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8582 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8583 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8584 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8585 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8586 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8587 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8588 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8589 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8590 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8591 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8592 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8593 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8594 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8595 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8596 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8597 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8598 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8599 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8600 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8601 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8602 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8603 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8604 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8605 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8606 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8607 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8608 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8609 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8610 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8611 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8612 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8613 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8615 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8618 static void compose_set_template_menu(Compose *compose)
8620 GSList *tmpl_list, *cur;
8624 tmpl_list = template_get_config();
8626 menu = gtk_menu_new();
8628 gtk_menu_set_accel_group (GTK_MENU (menu),
8629 gtk_ui_manager_get_accel_group(compose->ui_manager));
8630 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8631 Template *tmpl = (Template *)cur->data;
8632 gchar *accel_path = NULL;
8633 item = gtk_menu_item_new_with_label(tmpl->name);
8634 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8635 g_signal_connect(G_OBJECT(item), "activate",
8636 G_CALLBACK(compose_template_activate_cb),
8638 g_object_set_data(G_OBJECT(item), "template", tmpl);
8639 gtk_widget_show(item);
8640 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8641 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8645 gtk_widget_show(menu);
8646 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8649 void compose_update_actions_menu(Compose *compose)
8651 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8654 static void compose_update_privacy_systems_menu(Compose *compose)
8656 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8657 GSList *systems, *cur;
8659 GtkWidget *system_none;
8661 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8662 GtkWidget *privacy_menu = gtk_menu_new();
8664 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8665 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8667 g_signal_connect(G_OBJECT(system_none), "activate",
8668 G_CALLBACK(compose_set_privacy_system_cb), compose);
8670 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8671 gtk_widget_show(system_none);
8673 systems = privacy_get_system_ids();
8674 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8675 gchar *systemid = cur->data;
8677 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8678 widget = gtk_radio_menu_item_new_with_label(group,
8679 privacy_system_get_name(systemid));
8680 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8681 g_strdup(systemid), g_free);
8682 g_signal_connect(G_OBJECT(widget), "activate",
8683 G_CALLBACK(compose_set_privacy_system_cb), compose);
8685 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8686 gtk_widget_show(widget);
8689 g_slist_free(systems);
8690 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8691 gtk_widget_show_all(privacy_menu);
8692 gtk_widget_show_all(privacy_menuitem);
8695 void compose_reflect_prefs_all(void)
8700 for (cur = compose_list; cur != NULL; cur = cur->next) {
8701 compose = (Compose *)cur->data;
8702 compose_set_template_menu(compose);
8706 void compose_reflect_prefs_pixmap_theme(void)
8711 for (cur = compose_list; cur != NULL; cur = cur->next) {
8712 compose = (Compose *)cur->data;
8713 toolbar_update(TOOLBAR_COMPOSE, compose);
8717 static const gchar *compose_quote_char_from_context(Compose *compose)
8719 const gchar *qmark = NULL;
8721 cm_return_val_if_fail(compose != NULL, NULL);
8723 switch (compose->mode) {
8724 /* use forward-specific quote char */
8725 case COMPOSE_FORWARD:
8726 case COMPOSE_FORWARD_AS_ATTACH:
8727 case COMPOSE_FORWARD_INLINE:
8728 if (compose->folder && compose->folder->prefs &&
8729 compose->folder->prefs->forward_with_format)
8730 qmark = compose->folder->prefs->forward_quotemark;
8731 else if (compose->account->forward_with_format)
8732 qmark = compose->account->forward_quotemark;
8734 qmark = prefs_common.fw_quotemark;
8737 /* use reply-specific quote char in all other modes */
8739 if (compose->folder && compose->folder->prefs &&
8740 compose->folder->prefs->reply_with_format)
8741 qmark = compose->folder->prefs->reply_quotemark;
8742 else if (compose->account->reply_with_format)
8743 qmark = compose->account->reply_quotemark;
8745 qmark = prefs_common.quotemark;
8749 if (qmark == NULL || *qmark == '\0')
8755 static void compose_template_apply(Compose *compose, Template *tmpl,
8759 GtkTextBuffer *buffer;
8763 gchar *parsed_str = NULL;
8764 gint cursor_pos = 0;
8765 const gchar *err_msg = _("The body of the template has an error at line %d.");
8768 /* process the body */
8770 text = GTK_TEXT_VIEW(compose->text);
8771 buffer = gtk_text_view_get_buffer(text);
8774 qmark = compose_quote_char_from_context(compose);
8776 if (compose->replyinfo != NULL) {
8779 gtk_text_buffer_set_text(buffer, "", -1);
8780 mark = gtk_text_buffer_get_insert(buffer);
8781 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8783 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8784 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8786 } else if (compose->fwdinfo != NULL) {
8789 gtk_text_buffer_set_text(buffer, "", -1);
8790 mark = gtk_text_buffer_get_insert(buffer);
8791 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8793 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8794 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8797 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8799 GtkTextIter start, end;
8802 gtk_text_buffer_get_start_iter(buffer, &start);
8803 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8804 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8806 /* clear the buffer now */
8808 gtk_text_buffer_set_text(buffer, "", -1);
8810 parsed_str = compose_quote_fmt(compose, dummyinfo,
8811 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8812 procmsg_msginfo_free( &dummyinfo );
8818 gtk_text_buffer_set_text(buffer, "", -1);
8819 mark = gtk_text_buffer_get_insert(buffer);
8820 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8823 if (replace && parsed_str && compose->account->auto_sig)
8824 compose_insert_sig(compose, FALSE);
8826 if (replace && parsed_str) {
8827 gtk_text_buffer_get_start_iter(buffer, &iter);
8828 gtk_text_buffer_place_cursor(buffer, &iter);
8832 cursor_pos = quote_fmt_get_cursor_pos();
8833 compose->set_cursor_pos = cursor_pos;
8834 if (cursor_pos == -1)
8836 gtk_text_buffer_get_start_iter(buffer, &iter);
8837 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8838 gtk_text_buffer_place_cursor(buffer, &iter);
8841 /* process the other fields */
8843 compose_template_apply_fields(compose, tmpl);
8844 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8845 quote_fmt_reset_vartable();
8846 quote_fmtlex_destroy();
8848 compose_changed_cb(NULL, compose);
8851 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8852 gtkaspell_highlight_all(compose->gtkaspell);
8856 static void compose_template_apply_fields_error(const gchar *header)
8861 tr = g_strdup(C_("'%s' stands for a header name",
8862 "Template '%s' format error."));
8863 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8864 alertpanel_error("%s", text);
8870 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8872 MsgInfo* dummyinfo = NULL;
8873 MsgInfo *msginfo = NULL;
8876 if (compose->replyinfo != NULL)
8877 msginfo = compose->replyinfo;
8878 else if (compose->fwdinfo != NULL)
8879 msginfo = compose->fwdinfo;
8881 dummyinfo = compose_msginfo_new_from_compose(compose);
8882 msginfo = dummyinfo;
8885 if (tmpl->from && *tmpl->from != '\0') {
8887 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8888 compose->gtkaspell);
8890 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8892 quote_fmt_scan_string(tmpl->from);
8895 buf = quote_fmt_get_buffer();
8897 compose_template_apply_fields_error("From");
8899 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8902 quote_fmt_reset_vartable();
8903 quote_fmtlex_destroy();
8906 if (tmpl->to && *tmpl->to != '\0') {
8908 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8909 compose->gtkaspell);
8911 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8913 quote_fmt_scan_string(tmpl->to);
8916 buf = quote_fmt_get_buffer();
8918 compose_template_apply_fields_error("To");
8920 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8923 quote_fmt_reset_vartable();
8924 quote_fmtlex_destroy();
8927 if (tmpl->cc && *tmpl->cc != '\0') {
8929 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8930 compose->gtkaspell);
8932 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8934 quote_fmt_scan_string(tmpl->cc);
8937 buf = quote_fmt_get_buffer();
8939 compose_template_apply_fields_error("Cc");
8941 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8944 quote_fmt_reset_vartable();
8945 quote_fmtlex_destroy();
8948 if (tmpl->bcc && *tmpl->bcc != '\0') {
8950 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8951 compose->gtkaspell);
8953 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8955 quote_fmt_scan_string(tmpl->bcc);
8958 buf = quote_fmt_get_buffer();
8960 compose_template_apply_fields_error("Bcc");
8962 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8965 quote_fmt_reset_vartable();
8966 quote_fmtlex_destroy();
8969 if (tmpl->replyto && *tmpl->replyto != '\0') {
8971 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8972 compose->gtkaspell);
8974 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8976 quote_fmt_scan_string(tmpl->replyto);
8979 buf = quote_fmt_get_buffer();
8981 compose_template_apply_fields_error("Reply-To");
8983 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8986 quote_fmt_reset_vartable();
8987 quote_fmtlex_destroy();
8990 /* process the subject */
8991 if (tmpl->subject && *tmpl->subject != '\0') {
8993 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8994 compose->gtkaspell);
8996 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8998 quote_fmt_scan_string(tmpl->subject);
9001 buf = quote_fmt_get_buffer();
9003 compose_template_apply_fields_error("Subject");
9005 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9008 quote_fmt_reset_vartable();
9009 quote_fmtlex_destroy();
9012 procmsg_msginfo_free( &dummyinfo );
9015 static void compose_destroy(Compose *compose)
9017 GtkAllocation allocation;
9018 GtkTextBuffer *buffer;
9019 GtkClipboard *clipboard;
9021 compose_list = g_list_remove(compose_list, compose);
9023 if (compose->updating) {
9024 debug_print("danger, not destroying anything now\n");
9025 compose->deferred_destroy = TRUE;
9029 /* NOTE: address_completion_end() does nothing with the window
9030 * however this may change. */
9031 address_completion_end(compose->window);
9033 slist_free_strings_full(compose->to_list);
9034 slist_free_strings_full(compose->newsgroup_list);
9035 slist_free_strings_full(compose->header_list);
9037 slist_free_strings_full(extra_headers);
9038 extra_headers = NULL;
9040 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9042 g_hash_table_destroy(compose->email_hashtable);
9044 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9045 compose->folder_update_callback_id);
9047 procmsg_msginfo_free(&(compose->targetinfo));
9048 procmsg_msginfo_free(&(compose->replyinfo));
9049 procmsg_msginfo_free(&(compose->fwdinfo));
9051 g_free(compose->replyto);
9052 g_free(compose->cc);
9053 g_free(compose->bcc);
9054 g_free(compose->newsgroups);
9055 g_free(compose->followup_to);
9057 g_free(compose->ml_post);
9059 g_free(compose->inreplyto);
9060 g_free(compose->references);
9061 g_free(compose->msgid);
9062 g_free(compose->boundary);
9064 g_free(compose->redirect_filename);
9065 if (compose->undostruct)
9066 undo_destroy(compose->undostruct);
9068 g_free(compose->sig_str);
9070 g_free(compose->exteditor_file);
9072 g_free(compose->orig_charset);
9074 g_free(compose->privacy_system);
9075 g_free(compose->encdata);
9077 #ifndef USE_ALT_ADDRBOOK
9078 if (addressbook_get_target_compose() == compose)
9079 addressbook_set_target_compose(NULL);
9082 if (compose->gtkaspell) {
9083 gtkaspell_delete(compose->gtkaspell);
9084 compose->gtkaspell = NULL;
9088 if (!compose->batch) {
9089 gtk_widget_get_allocation(compose->window, &allocation);
9090 prefs_common.compose_width = allocation.width;
9091 prefs_common.compose_height = allocation.height;
9094 if (!gtk_widget_get_parent(compose->paned))
9095 gtk_widget_destroy(compose->paned);
9096 gtk_widget_destroy(compose->popupmenu);
9098 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9099 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9100 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9102 gtk_widget_destroy(compose->window);
9103 toolbar_destroy(compose->toolbar);
9104 g_free(compose->toolbar);
9105 cm_mutex_free(compose->mutex);
9109 static void compose_attach_info_free(AttachInfo *ainfo)
9111 g_free(ainfo->file);
9112 g_free(ainfo->content_type);
9113 g_free(ainfo->name);
9114 g_free(ainfo->charset);
9118 static void compose_attach_update_label(Compose *compose)
9123 GtkTreeModel *model;
9127 if (compose == NULL)
9130 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9131 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9132 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9136 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9137 total_size = ainfo->size;
9138 while(gtk_tree_model_iter_next(model, &iter)) {
9139 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9140 total_size += ainfo->size;
9143 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9144 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9148 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9150 Compose *compose = (Compose *)data;
9151 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9152 GtkTreeSelection *selection;
9154 GtkTreeModel *model;
9156 selection = gtk_tree_view_get_selection(tree_view);
9157 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9162 for (cur = sel; cur != NULL; cur = cur->next) {
9163 GtkTreePath *path = cur->data;
9164 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9167 gtk_tree_path_free(path);
9170 for (cur = sel; cur != NULL; cur = cur->next) {
9171 GtkTreeRowReference *ref = cur->data;
9172 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9175 if (gtk_tree_model_get_iter(model, &iter, path))
9176 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9178 gtk_tree_path_free(path);
9179 gtk_tree_row_reference_free(ref);
9183 compose_attach_update_label(compose);
9186 static struct _AttachProperty
9189 GtkWidget *mimetype_entry;
9190 GtkWidget *encoding_optmenu;
9191 GtkWidget *path_entry;
9192 GtkWidget *filename_entry;
9194 GtkWidget *cancel_btn;
9197 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9199 gtk_tree_path_free((GtkTreePath *)ptr);
9202 static void compose_attach_property(GtkAction *action, gpointer data)
9204 Compose *compose = (Compose *)data;
9205 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9207 GtkComboBox *optmenu;
9208 GtkTreeSelection *selection;
9210 GtkTreeModel *model;
9213 static gboolean cancelled;
9215 /* only if one selected */
9216 selection = gtk_tree_view_get_selection(tree_view);
9217 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9220 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9224 path = (GtkTreePath *) sel->data;
9225 gtk_tree_model_get_iter(model, &iter, path);
9226 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9229 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9235 if (!attach_prop.window)
9236 compose_attach_property_create(&cancelled);
9237 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9238 gtk_widget_grab_focus(attach_prop.ok_btn);
9239 gtk_widget_show(attach_prop.window);
9240 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9241 GTK_WINDOW(compose->window));
9243 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9244 if (ainfo->encoding == ENC_UNKNOWN)
9245 combobox_select_by_data(optmenu, ENC_BASE64);
9247 combobox_select_by_data(optmenu, ainfo->encoding);
9249 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9250 ainfo->content_type ? ainfo->content_type : "");
9251 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9252 ainfo->file ? ainfo->file : "");
9253 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9254 ainfo->name ? ainfo->name : "");
9257 const gchar *entry_text;
9259 gchar *cnttype = NULL;
9266 gtk_widget_hide(attach_prop.window);
9267 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9272 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9273 if (*entry_text != '\0') {
9276 text = g_strstrip(g_strdup(entry_text));
9277 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9278 cnttype = g_strdup(text);
9281 alertpanel_error(_("Invalid MIME type."));
9287 ainfo->encoding = combobox_get_active_data(optmenu);
9289 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9290 if (*entry_text != '\0') {
9291 if (is_file_exist(entry_text) &&
9292 (size = get_file_size(entry_text)) > 0)
9293 file = g_strdup(entry_text);
9296 (_("File doesn't exist or is empty."));
9302 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9303 if (*entry_text != '\0') {
9304 g_free(ainfo->name);
9305 ainfo->name = g_strdup(entry_text);
9309 g_free(ainfo->content_type);
9310 ainfo->content_type = cnttype;
9313 g_free(ainfo->file);
9317 ainfo->size = (goffset)size;
9319 /* update tree store */
9320 text = to_human_readable(ainfo->size);
9321 gtk_tree_model_get_iter(model, &iter, path);
9322 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9323 COL_MIMETYPE, ainfo->content_type,
9325 COL_NAME, ainfo->name,
9326 COL_CHARSET, ainfo->charset,
9332 gtk_tree_path_free(path);
9335 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9337 label = gtk_label_new(str); \
9338 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9339 GTK_FILL, 0, 0, 0); \
9340 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9342 entry = gtk_entry_new(); \
9343 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9344 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9347 static void compose_attach_property_create(gboolean *cancelled)
9353 GtkWidget *mimetype_entry;
9356 GtkListStore *optmenu_menu;
9357 GtkWidget *path_entry;
9358 GtkWidget *filename_entry;
9361 GtkWidget *cancel_btn;
9362 GList *mime_type_list, *strlist;
9365 debug_print("Creating attach_property window...\n");
9367 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9368 gtk_widget_set_size_request(window, 480, -1);
9369 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9370 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9371 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9372 g_signal_connect(G_OBJECT(window), "delete_event",
9373 G_CALLBACK(attach_property_delete_event),
9375 g_signal_connect(G_OBJECT(window), "key_press_event",
9376 G_CALLBACK(attach_property_key_pressed),
9379 vbox = gtk_vbox_new(FALSE, 8);
9380 gtk_container_add(GTK_CONTAINER(window), vbox);
9382 table = gtk_table_new(4, 2, FALSE);
9383 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9384 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9385 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9387 label = gtk_label_new(_("MIME type"));
9388 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9390 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9391 #if !GTK_CHECK_VERSION(2, 24, 0)
9392 mimetype_entry = gtk_combo_box_entry_new_text();
9394 mimetype_entry = gtk_combo_box_text_new_with_entry();
9396 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9397 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9399 /* stuff with list */
9400 mime_type_list = procmime_get_mime_type_list();
9402 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9403 MimeType *type = (MimeType *) mime_type_list->data;
9406 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9408 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9411 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9412 (GCompareFunc)strcmp2);
9415 for (mime_type_list = strlist; mime_type_list != NULL;
9416 mime_type_list = mime_type_list->next) {
9417 #if !GTK_CHECK_VERSION(2, 24, 0)
9418 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9420 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9422 g_free(mime_type_list->data);
9424 g_list_free(strlist);
9425 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9426 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9428 label = gtk_label_new(_("Encoding"));
9429 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9431 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9433 hbox = gtk_hbox_new(FALSE, 0);
9434 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9435 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9437 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9438 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9440 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9441 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9442 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9443 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9444 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9446 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9448 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9449 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9451 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9452 &ok_btn, GTK_STOCK_OK,
9454 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9455 gtk_widget_grab_default(ok_btn);
9457 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9458 G_CALLBACK(attach_property_ok),
9460 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9461 G_CALLBACK(attach_property_cancel),
9464 gtk_widget_show_all(vbox);
9466 attach_prop.window = window;
9467 attach_prop.mimetype_entry = mimetype_entry;
9468 attach_prop.encoding_optmenu = optmenu;
9469 attach_prop.path_entry = path_entry;
9470 attach_prop.filename_entry = filename_entry;
9471 attach_prop.ok_btn = ok_btn;
9472 attach_prop.cancel_btn = cancel_btn;
9475 #undef SET_LABEL_AND_ENTRY
9477 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9483 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9489 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9490 gboolean *cancelled)
9498 static gboolean attach_property_key_pressed(GtkWidget *widget,
9500 gboolean *cancelled)
9502 if (event && event->keyval == GDK_KEY_Escape) {
9506 if (event && event->keyval == GDK_KEY_Return) {
9514 static void compose_exec_ext_editor(Compose *compose)
9519 GdkNativeWindow socket_wid = 0;
9523 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9524 G_DIR_SEPARATOR, compose);
9526 if (compose_get_ext_editor_uses_socket()) {
9527 /* Only allow one socket */
9528 if (compose->exteditor_socket != NULL) {
9529 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9530 /* Move the focus off of the socket */
9531 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9536 /* Create the receiving GtkSocket */
9537 socket = gtk_socket_new ();
9538 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9539 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9541 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9542 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9543 /* Realize the socket so that we can use its ID */
9544 gtk_widget_realize(socket);
9545 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9546 compose->exteditor_socket = socket;
9549 if (pipe(pipe_fds) < 0) {
9555 if ((pid = fork()) < 0) {
9562 /* close the write side of the pipe */
9565 compose->exteditor_file = g_strdup(tmp);
9566 compose->exteditor_pid = pid;
9568 compose_set_ext_editor_sensitive(compose, FALSE);
9571 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9573 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9575 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9579 } else { /* process-monitoring process */
9585 /* close the read side of the pipe */
9588 if (compose_write_body_to_file(compose, tmp) < 0) {
9589 fd_write_all(pipe_fds[1], "2\n", 2);
9593 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9595 fd_write_all(pipe_fds[1], "1\n", 2);
9599 /* wait until editor is terminated */
9600 waitpid(pid_ed, NULL, 0);
9602 fd_write_all(pipe_fds[1], "0\n", 2);
9609 #endif /* G_OS_UNIX */
9612 static gboolean compose_can_autosave(Compose *compose)
9614 if (compose->privacy_system && compose->use_encryption)
9615 return prefs_common.autosave && prefs_common.autosave_encrypted;
9617 return prefs_common.autosave;
9621 static gboolean compose_get_ext_editor_cmd_valid()
9623 gboolean has_s = FALSE;
9624 gboolean has_w = FALSE;
9625 const gchar *p = prefs_common_get_ext_editor_cmd();
9628 while ((p = strchr(p, '%'))) {
9634 } else if (*p == 'w') {
9645 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9652 cm_return_val_if_fail(file != NULL, -1);
9654 if ((pid = fork()) < 0) {
9659 if (pid != 0) return pid;
9661 /* grandchild process */
9663 if (setpgid(0, getppid()))
9666 if (compose_get_ext_editor_cmd_valid()) {
9667 if (compose_get_ext_editor_uses_socket()) {
9668 p = g_strdup(prefs_common_get_ext_editor_cmd());
9669 s = strstr(p, "%w");
9671 if (strstr(p, "%s") < s)
9672 buf = g_strdup_printf(p, file, socket_wid);
9674 buf = g_strdup_printf(p, socket_wid, file);
9677 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9680 if (prefs_common_get_ext_editor_cmd())
9681 g_warning("External editor command-line is invalid: '%s'",
9682 prefs_common_get_ext_editor_cmd());
9683 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9686 cmdline = strsplit_with_quote(buf, " ", 0);
9688 execvp(cmdline[0], cmdline);
9691 g_strfreev(cmdline);
9696 static gboolean compose_ext_editor_kill(Compose *compose)
9698 pid_t pgid = compose->exteditor_pid * -1;
9701 ret = kill(pgid, 0);
9703 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9707 msg = g_strdup_printf
9708 (_("The external editor is still working.\n"
9709 "Force terminating the process?\n"
9710 "process group id: %d"), -pgid);
9711 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9712 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9716 if (val == G_ALERTALTERNATE) {
9717 g_source_remove(compose->exteditor_tag);
9718 g_io_channel_shutdown(compose->exteditor_ch,
9720 g_io_channel_unref(compose->exteditor_ch);
9722 if (kill(pgid, SIGTERM) < 0) perror("kill");
9723 waitpid(compose->exteditor_pid, NULL, 0);
9725 g_warning("Terminated process group id: %d. "
9726 "Temporary file: %s", -pgid, compose->exteditor_file);
9728 compose_set_ext_editor_sensitive(compose, TRUE);
9730 g_free(compose->exteditor_file);
9731 compose->exteditor_file = NULL;
9732 compose->exteditor_pid = -1;
9733 compose->exteditor_ch = NULL;
9734 compose->exteditor_tag = -1;
9742 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9746 Compose *compose = (Compose *)data;
9749 debug_print("Compose: input from monitoring process\n");
9751 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9756 g_io_channel_shutdown(source, FALSE, NULL);
9757 g_io_channel_unref(source);
9759 waitpid(compose->exteditor_pid, NULL, 0);
9761 if (buf[0] == '0') { /* success */
9762 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9763 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9764 GtkTextIter start, end;
9767 gtk_text_buffer_set_text(buffer, "", -1);
9768 compose_insert_file(compose, compose->exteditor_file);
9769 compose_changed_cb(NULL, compose);
9771 /* Check if we should save the draft or not */
9772 if (compose_can_autosave(compose))
9773 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9775 if (claws_unlink(compose->exteditor_file) < 0)
9776 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9778 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9779 gtk_text_buffer_get_start_iter(buffer, &start);
9780 gtk_text_buffer_get_end_iter(buffer, &end);
9781 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9782 if (chars && strlen(chars) > 0)
9783 compose->modified = TRUE;
9785 } else if (buf[0] == '1') { /* failed */
9786 g_warning("Couldn't exec external editor");
9787 if (claws_unlink(compose->exteditor_file) < 0)
9788 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9789 } else if (buf[0] == '2') {
9790 g_warning("Couldn't write to file");
9791 } else if (buf[0] == '3') {
9792 g_warning("Pipe read failed");
9795 compose_set_ext_editor_sensitive(compose, TRUE);
9797 g_free(compose->exteditor_file);
9798 compose->exteditor_file = NULL;
9799 compose->exteditor_pid = -1;
9800 compose->exteditor_ch = NULL;
9801 compose->exteditor_tag = -1;
9802 if (compose->exteditor_socket) {
9803 gtk_widget_destroy(compose->exteditor_socket);
9804 compose->exteditor_socket = NULL;
9811 static char *ext_editor_menu_entries[] = {
9812 "Menu/Message/Send",
9813 "Menu/Message/SendLater",
9814 "Menu/Message/InsertFile",
9815 "Menu/Message/InsertSig",
9816 "Menu/Message/ReplaceSig",
9817 "Menu/Message/Save",
9818 "Menu/Message/Print",
9823 "Menu/Tools/ShowRuler",
9824 "Menu/Tools/Actions",
9829 static void compose_set_ext_editor_sensitive(Compose *compose,
9834 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9835 cm_menu_set_sensitive_full(compose->ui_manager,
9836 ext_editor_menu_entries[i], sensitive);
9839 if (compose_get_ext_editor_uses_socket()) {
9841 if (compose->exteditor_socket)
9842 gtk_widget_hide(compose->exteditor_socket);
9843 gtk_widget_show(compose->scrolledwin);
9844 if (prefs_common.show_ruler)
9845 gtk_widget_show(compose->ruler_hbox);
9846 /* Fix the focus, as it doesn't go anywhere when the
9847 * socket is hidden or destroyed */
9848 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9850 g_assert (compose->exteditor_socket != NULL);
9851 /* Fix the focus, as it doesn't go anywhere when the
9852 * edit box is hidden */
9853 if (gtk_widget_is_focus(compose->text))
9854 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9855 gtk_widget_hide(compose->scrolledwin);
9856 gtk_widget_hide(compose->ruler_hbox);
9857 gtk_widget_show(compose->exteditor_socket);
9860 gtk_widget_set_sensitive(compose->text, sensitive);
9862 if (compose->toolbar->send_btn)
9863 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9864 if (compose->toolbar->sendl_btn)
9865 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9866 if (compose->toolbar->draft_btn)
9867 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9868 if (compose->toolbar->insert_btn)
9869 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9870 if (compose->toolbar->sig_btn)
9871 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9872 if (compose->toolbar->exteditor_btn)
9873 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9874 if (compose->toolbar->linewrap_current_btn)
9875 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9876 if (compose->toolbar->linewrap_all_btn)
9877 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9880 static gboolean compose_get_ext_editor_uses_socket()
9882 return (prefs_common_get_ext_editor_cmd() &&
9883 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9886 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9888 compose->exteditor_socket = NULL;
9889 /* returning FALSE allows destruction of the socket */
9892 #endif /* G_OS_UNIX */
9895 * compose_undo_state_changed:
9897 * Change the sensivity of the menuentries undo and redo
9899 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9900 gint redo_state, gpointer data)
9902 Compose *compose = (Compose *)data;
9904 switch (undo_state) {
9905 case UNDO_STATE_TRUE:
9906 if (!undostruct->undo_state) {
9907 undostruct->undo_state = TRUE;
9908 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9911 case UNDO_STATE_FALSE:
9912 if (undostruct->undo_state) {
9913 undostruct->undo_state = FALSE;
9914 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9917 case UNDO_STATE_UNCHANGED:
9919 case UNDO_STATE_REFRESH:
9920 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9923 g_warning("Undo state not recognized");
9927 switch (redo_state) {
9928 case UNDO_STATE_TRUE:
9929 if (!undostruct->redo_state) {
9930 undostruct->redo_state = TRUE;
9931 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9934 case UNDO_STATE_FALSE:
9935 if (undostruct->redo_state) {
9936 undostruct->redo_state = FALSE;
9937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9940 case UNDO_STATE_UNCHANGED:
9942 case UNDO_STATE_REFRESH:
9943 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9946 g_warning("Redo state not recognized");
9951 /* callback functions */
9953 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9954 GtkAllocation *allocation,
9957 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9960 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9961 * includes "non-client" (windows-izm) in calculation, so this calculation
9962 * may not be accurate.
9964 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9965 GtkAllocation *allocation,
9966 GtkSHRuler *shruler)
9968 if (prefs_common.show_ruler) {
9969 gint char_width = 0, char_height = 0;
9970 gint line_width_in_chars;
9972 gtkut_get_font_size(GTK_WIDGET(widget),
9973 &char_width, &char_height);
9974 line_width_in_chars =
9975 (allocation->width - allocation->x) / char_width;
9977 /* got the maximum */
9978 gtk_shruler_set_range(GTK_SHRULER(shruler),
9979 0.0, line_width_in_chars, 0);
9988 ComposePrefType type;
9989 gboolean entry_marked;
9992 static void account_activated(GtkComboBox *optmenu, gpointer data)
9994 Compose *compose = (Compose *)data;
9997 gchar *folderidentifier;
9998 gint account_id = 0;
10001 GSList *list, *saved_list = NULL;
10002 HeaderEntryState *state;
10004 /* Get ID of active account in the combo box */
10005 menu = gtk_combo_box_get_model(optmenu);
10006 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10007 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10009 ac = account_find_from_id(account_id);
10010 cm_return_if_fail(ac != NULL);
10012 if (ac != compose->account) {
10013 compose_select_account(compose, ac, FALSE);
10015 for (list = compose->header_list; list; list = list->next) {
10016 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10018 if (hentry->type == PREF_ACCOUNT || !list->next) {
10019 compose_destroy_headerentry(compose, hentry);
10022 state = g_malloc0(sizeof(HeaderEntryState));
10023 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10024 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10025 state->entry = gtk_editable_get_chars(
10026 GTK_EDITABLE(hentry->entry), 0, -1);
10027 state->type = hentry->type;
10029 saved_list = g_slist_append(saved_list, state);
10030 compose_destroy_headerentry(compose, hentry);
10033 compose->header_last = NULL;
10034 g_slist_free(compose->header_list);
10035 compose->header_list = NULL;
10036 compose->header_nextrow = 1;
10037 compose_create_header_entry(compose);
10039 if (ac->set_autocc && ac->auto_cc)
10040 compose_entry_append(compose, ac->auto_cc,
10041 COMPOSE_CC, PREF_ACCOUNT);
10042 if (ac->set_autobcc && ac->auto_bcc)
10043 compose_entry_append(compose, ac->auto_bcc,
10044 COMPOSE_BCC, PREF_ACCOUNT);
10045 if (ac->set_autoreplyto && ac->auto_replyto)
10046 compose_entry_append(compose, ac->auto_replyto,
10047 COMPOSE_REPLYTO, PREF_ACCOUNT);
10049 for (list = saved_list; list; list = list->next) {
10050 state = (HeaderEntryState *) list->data;
10052 compose_add_header_entry(compose, state->header,
10053 state->entry, state->type);
10055 g_free(state->header);
10056 g_free(state->entry);
10059 g_slist_free(saved_list);
10061 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10062 (ac->protocol == A_NNTP) ?
10063 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10066 /* Set message save folder */
10067 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10068 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10070 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10071 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10073 compose_set_save_to(compose, NULL);
10074 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10075 folderidentifier = folder_item_get_identifier(account_get_special_folder
10076 (compose->account, F_OUTBOX));
10077 compose_set_save_to(compose, folderidentifier);
10078 g_free(folderidentifier);
10082 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10083 GtkTreeViewColumn *column, Compose *compose)
10085 compose_attach_property(NULL, compose);
10088 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10091 Compose *compose = (Compose *)data;
10092 GtkTreeSelection *attach_selection;
10093 gint attach_nr_selected;
10096 if (!event) return FALSE;
10098 if (event->button == 3) {
10099 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10100 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10102 /* If no rows, or just one row is selected, right-click should
10103 * open menu relevant to the row being right-clicked on. We
10104 * achieve that by selecting the clicked row first. If more
10105 * than one row is selected, we shouldn't modify the selection,
10106 * as user may want to remove selected rows (attachments). */
10107 if (attach_nr_selected < 2) {
10108 gtk_tree_selection_unselect_all(attach_selection);
10109 attach_nr_selected = 0;
10110 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10111 event->x, event->y, &path, NULL, NULL, NULL);
10112 if (path != NULL) {
10113 gtk_tree_selection_select_path(attach_selection, path);
10114 gtk_tree_path_free(path);
10115 attach_nr_selected++;
10119 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10120 /* Properties menu item makes no sense with more than one row
10121 * selected, the properties dialog can only edit one attachment. */
10122 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10124 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10125 NULL, NULL, event->button, event->time);
10132 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10135 Compose *compose = (Compose *)data;
10137 if (!event) return FALSE;
10139 switch (event->keyval) {
10140 case GDK_KEY_Delete:
10141 compose_attach_remove_selected(NULL, compose);
10147 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10149 toolbar_comp_set_sensitive(compose, allow);
10150 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10151 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10153 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10155 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10156 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10157 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10159 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10163 static void compose_send_cb(GtkAction *action, gpointer data)
10165 Compose *compose = (Compose *)data;
10168 if (compose->exteditor_tag != -1) {
10169 debug_print("ignoring send: external editor still open\n");
10173 if (prefs_common.work_offline &&
10174 !inc_offline_should_override(TRUE,
10175 _("Claws Mail needs network access in order "
10176 "to send this email.")))
10179 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10180 g_source_remove(compose->draft_timeout_tag);
10181 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10184 compose_send(compose);
10187 static void compose_send_later_cb(GtkAction *action, gpointer data)
10189 Compose *compose = (Compose *)data;
10193 compose_allow_user_actions(compose, FALSE);
10194 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10195 compose_allow_user_actions(compose, TRUE);
10199 compose_close(compose);
10200 } else if (val == -1) {
10201 alertpanel_error(_("Could not queue message."));
10202 } else if (val == -2) {
10203 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10204 } else if (val == -3) {
10205 if (privacy_peek_error())
10206 alertpanel_error(_("Could not queue message for sending:\n\n"
10207 "Signature failed: %s"), privacy_get_error());
10208 } else if (val == -4) {
10209 alertpanel_error(_("Could not queue message for sending:\n\n"
10210 "Charset conversion failed."));
10211 } else if (val == -5) {
10212 alertpanel_error(_("Could not queue message for sending:\n\n"
10213 "Couldn't get recipient encryption key."));
10214 } else if (val == -6) {
10217 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10220 #define DRAFTED_AT_EXIT "drafted_at_exit"
10221 static void compose_register_draft(MsgInfo *info)
10223 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10224 DRAFTED_AT_EXIT, NULL);
10225 FILE *fp = g_fopen(filepath, "ab");
10228 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10236 gboolean compose_draft (gpointer data, guint action)
10238 Compose *compose = (Compose *)data;
10243 MsgFlags flag = {0, 0};
10244 static gboolean lock = FALSE;
10245 MsgInfo *newmsginfo;
10247 gboolean target_locked = FALSE;
10248 gboolean err = FALSE;
10250 if (lock) return FALSE;
10252 if (compose->sending)
10255 draft = account_get_special_folder(compose->account, F_DRAFT);
10256 cm_return_val_if_fail(draft != NULL, FALSE);
10258 if (!g_mutex_trylock(compose->mutex)) {
10259 /* we don't want to lock the mutex once it's available,
10260 * because as the only other part of compose.c locking
10261 * it is compose_close - which means once unlocked,
10262 * the compose struct will be freed */
10263 debug_print("couldn't lock mutex, probably sending\n");
10269 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10270 G_DIR_SEPARATOR, compose);
10271 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10272 FILE_OP_ERROR(tmp, "fopen");
10276 /* chmod for security */
10277 if (change_file_mode_rw(fp, tmp) < 0) {
10278 FILE_OP_ERROR(tmp, "chmod");
10279 g_warning("can't change file mode");
10282 /* Save draft infos */
10283 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10284 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10286 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10287 gchar *savefolderid;
10289 savefolderid = compose_get_save_to(compose);
10290 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10291 g_free(savefolderid);
10293 if (compose->return_receipt) {
10294 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10296 if (compose->privacy_system) {
10297 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10298 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10299 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10302 /* Message-ID of message replying to */
10303 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10304 gchar *folderid = NULL;
10306 if (compose->replyinfo->folder)
10307 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10308 if (folderid == NULL)
10309 folderid = g_strdup("NULL");
10311 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10314 /* Message-ID of message forwarding to */
10315 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10316 gchar *folderid = NULL;
10318 if (compose->fwdinfo->folder)
10319 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10320 if (folderid == NULL)
10321 folderid = g_strdup("NULL");
10323 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10327 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10328 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10330 sheaders = compose_get_manual_headers_info(compose);
10331 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10334 /* end of headers */
10335 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10342 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10346 if (fclose(fp) == EOF) {
10350 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10351 if (compose->targetinfo) {
10352 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10354 flag.perm_flags |= MSG_LOCKED;
10356 flag.tmp_flags = MSG_DRAFT;
10358 folder_item_scan(draft);
10359 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10360 MsgInfo *tmpinfo = NULL;
10361 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10362 if (compose->msgid) {
10363 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10366 msgnum = tmpinfo->msgnum;
10367 procmsg_msginfo_free(&tmpinfo);
10368 debug_print("got draft msgnum %d from scanning\n", msgnum);
10370 debug_print("didn't get draft msgnum after scanning\n");
10373 debug_print("got draft msgnum %d from adding\n", msgnum);
10379 if (action != COMPOSE_AUTO_SAVE) {
10380 if (action != COMPOSE_DRAFT_FOR_EXIT)
10381 alertpanel_error(_("Could not save draft."));
10384 gtkut_window_popup(compose->window);
10385 val = alertpanel_full(_("Could not save draft"),
10386 _("Could not save draft.\n"
10387 "Do you want to cancel exit or discard this email?"),
10388 _("_Cancel exit"), _("_Discard email"), NULL,
10389 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10390 if (val == G_ALERTALTERNATE) {
10392 g_mutex_unlock(compose->mutex); /* must be done before closing */
10393 compose_close(compose);
10397 g_mutex_unlock(compose->mutex); /* must be done before closing */
10406 if (compose->mode == COMPOSE_REEDIT) {
10407 compose_remove_reedit_target(compose, TRUE);
10410 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10413 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10415 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10417 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10418 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10419 procmsg_msginfo_set_flags(newmsginfo, 0,
10420 MSG_HAS_ATTACHMENT);
10422 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10423 compose_register_draft(newmsginfo);
10425 procmsg_msginfo_free(&newmsginfo);
10428 folder_item_scan(draft);
10430 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10432 g_mutex_unlock(compose->mutex); /* must be done before closing */
10433 compose_close(compose);
10439 path = folder_item_fetch_msg(draft, msgnum);
10440 if (path == NULL) {
10441 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10444 if (g_stat(path, &s) < 0) {
10445 FILE_OP_ERROR(path, "stat");
10451 procmsg_msginfo_free(&(compose->targetinfo));
10452 compose->targetinfo = procmsg_msginfo_new();
10453 compose->targetinfo->msgnum = msgnum;
10454 compose->targetinfo->size = (goffset)s.st_size;
10455 compose->targetinfo->mtime = s.st_mtime;
10456 compose->targetinfo->folder = draft;
10458 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10459 compose->mode = COMPOSE_REEDIT;
10461 if (action == COMPOSE_AUTO_SAVE) {
10462 compose->autosaved_draft = compose->targetinfo;
10464 compose->modified = FALSE;
10465 compose_set_title(compose);
10469 g_mutex_unlock(compose->mutex);
10473 void compose_clear_exit_drafts(void)
10475 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10476 DRAFTED_AT_EXIT, NULL);
10477 if (is_file_exist(filepath))
10478 claws_unlink(filepath);
10483 void compose_reopen_exit_drafts(void)
10485 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10486 DRAFTED_AT_EXIT, NULL);
10487 FILE *fp = g_fopen(filepath, "rb");
10491 while (fgets(buf, sizeof(buf), fp)) {
10492 gchar **parts = g_strsplit(buf, "\t", 2);
10493 const gchar *folder = parts[0];
10494 int msgnum = parts[1] ? atoi(parts[1]):-1;
10496 if (folder && *folder && msgnum > -1) {
10497 FolderItem *item = folder_find_item_from_identifier(folder);
10498 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10500 compose_reedit(info, FALSE);
10507 compose_clear_exit_drafts();
10510 static void compose_save_cb(GtkAction *action, gpointer data)
10512 Compose *compose = (Compose *)data;
10513 compose_draft(compose, COMPOSE_KEEP_EDITING);
10514 compose->rmode = COMPOSE_REEDIT;
10517 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10519 if (compose && file_list) {
10522 for ( tmp = file_list; tmp; tmp = tmp->next) {
10523 gchar *file = (gchar *) tmp->data;
10524 gchar *utf8_filename = conv_filename_to_utf8(file);
10525 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10526 compose_changed_cb(NULL, compose);
10531 g_free(utf8_filename);
10536 static void compose_attach_cb(GtkAction *action, gpointer data)
10538 Compose *compose = (Compose *)data;
10541 if (compose->redirect_filename != NULL)
10544 /* Set focus_window properly, in case we were called via popup menu,
10545 * which unsets it (via focus_out_event callback on compose window). */
10546 manage_window_focus_in(compose->window, NULL, NULL);
10548 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10551 compose_attach_from_list(compose, file_list, TRUE);
10552 g_list_free(file_list);
10556 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10558 Compose *compose = (Compose *)data;
10560 gint files_inserted = 0;
10562 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10567 for ( tmp = file_list; tmp; tmp = tmp->next) {
10568 gchar *file = (gchar *) tmp->data;
10569 gchar *filedup = g_strdup(file);
10570 gchar *shortfile = g_path_get_basename(filedup);
10571 ComposeInsertResult res;
10572 /* insert the file if the file is short or if the user confirmed that
10573 he/she wants to insert the large file */
10574 res = compose_insert_file(compose, file);
10575 if (res == COMPOSE_INSERT_READ_ERROR) {
10576 alertpanel_error(_("File '%s' could not be read."), shortfile);
10577 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10578 alertpanel_error(_("File '%s' contained invalid characters\n"
10579 "for the current encoding, insertion may be incorrect."),
10581 } else if (res == COMPOSE_INSERT_SUCCESS)
10588 g_list_free(file_list);
10592 if (files_inserted > 0 && compose->gtkaspell &&
10593 compose->gtkaspell->check_while_typing)
10594 gtkaspell_highlight_all(compose->gtkaspell);
10598 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10600 Compose *compose = (Compose *)data;
10602 compose_insert_sig(compose, FALSE);
10605 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10607 Compose *compose = (Compose *)data;
10609 compose_insert_sig(compose, TRUE);
10612 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10616 Compose *compose = (Compose *)data;
10618 gtkut_widget_get_uposition(widget, &x, &y);
10619 if (!compose->batch) {
10620 prefs_common.compose_x = x;
10621 prefs_common.compose_y = y;
10623 if (compose->sending || compose->updating)
10625 compose_close_cb(NULL, compose);
10629 void compose_close_toolbar(Compose *compose)
10631 compose_close_cb(NULL, compose);
10634 static void compose_close_cb(GtkAction *action, gpointer data)
10636 Compose *compose = (Compose *)data;
10640 if (compose->exteditor_tag != -1) {
10641 if (!compose_ext_editor_kill(compose))
10646 if (compose->modified) {
10647 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10648 if (!g_mutex_trylock(compose->mutex)) {
10649 /* we don't want to lock the mutex once it's available,
10650 * because as the only other part of compose.c locking
10651 * it is compose_close - which means once unlocked,
10652 * the compose struct will be freed */
10653 debug_print("couldn't lock mutex, probably sending\n");
10657 val = alertpanel(_("Discard message"),
10658 _("This message has been modified. Discard it?"),
10659 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10661 val = alertpanel(_("Save changes"),
10662 _("This message has been modified. Save the latest changes?"),
10663 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10666 g_mutex_unlock(compose->mutex);
10668 case G_ALERTDEFAULT:
10669 if (compose_can_autosave(compose) && !reedit)
10670 compose_remove_draft(compose);
10672 case G_ALERTALTERNATE:
10673 compose_draft(data, COMPOSE_QUIT_EDITING);
10680 compose_close(compose);
10683 static void compose_print_cb(GtkAction *action, gpointer data)
10685 Compose *compose = (Compose *) data;
10687 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10688 if (compose->targetinfo)
10689 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10692 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10694 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10695 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10696 Compose *compose = (Compose *) data;
10699 compose->out_encoding = (CharSet)value;
10702 static void compose_address_cb(GtkAction *action, gpointer data)
10704 Compose *compose = (Compose *)data;
10706 #ifndef USE_ALT_ADDRBOOK
10707 addressbook_open(compose);
10709 GError* error = NULL;
10710 addressbook_connect_signals(compose);
10711 addressbook_dbus_open(TRUE, &error);
10713 g_warning("%s", error->message);
10714 g_error_free(error);
10719 static void about_show_cb(GtkAction *action, gpointer data)
10724 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10726 Compose *compose = (Compose *)data;
10731 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10732 cm_return_if_fail(tmpl != NULL);
10734 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10736 val = alertpanel(_("Apply template"), msg,
10737 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10740 if (val == G_ALERTDEFAULT)
10741 compose_template_apply(compose, tmpl, TRUE);
10742 else if (val == G_ALERTALTERNATE)
10743 compose_template_apply(compose, tmpl, FALSE);
10746 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10748 Compose *compose = (Compose *)data;
10751 if (compose->exteditor_tag != -1) {
10752 debug_print("ignoring open external editor: external editor still open\n");
10756 compose_exec_ext_editor(compose);
10759 static void compose_undo_cb(GtkAction *action, gpointer data)
10761 Compose *compose = (Compose *)data;
10762 gboolean prev_autowrap = compose->autowrap;
10764 compose->autowrap = FALSE;
10765 undo_undo(compose->undostruct);
10766 compose->autowrap = prev_autowrap;
10769 static void compose_redo_cb(GtkAction *action, gpointer data)
10771 Compose *compose = (Compose *)data;
10772 gboolean prev_autowrap = compose->autowrap;
10774 compose->autowrap = FALSE;
10775 undo_redo(compose->undostruct);
10776 compose->autowrap = prev_autowrap;
10779 static void entry_cut_clipboard(GtkWidget *entry)
10781 if (GTK_IS_EDITABLE(entry))
10782 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10783 else if (GTK_IS_TEXT_VIEW(entry))
10784 gtk_text_buffer_cut_clipboard(
10785 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10786 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10790 static void entry_copy_clipboard(GtkWidget *entry)
10792 if (GTK_IS_EDITABLE(entry))
10793 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10794 else if (GTK_IS_TEXT_VIEW(entry))
10795 gtk_text_buffer_copy_clipboard(
10796 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10797 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10800 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10801 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10803 if (GTK_IS_TEXT_VIEW(entry)) {
10804 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10805 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10806 GtkTextIter start_iter, end_iter;
10808 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10810 if (contents == NULL)
10813 /* we shouldn't delete the selection when middle-click-pasting, or we
10814 * can't mid-click-paste our own selection */
10815 if (clip != GDK_SELECTION_PRIMARY) {
10816 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10817 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10820 if (insert_place == NULL) {
10821 /* if insert_place isn't specified, insert at the cursor.
10822 * used for Ctrl-V pasting */
10823 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10824 start = gtk_text_iter_get_offset(&start_iter);
10825 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10827 /* if insert_place is specified, paste here.
10828 * used for mid-click-pasting */
10829 start = gtk_text_iter_get_offset(insert_place);
10830 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10831 if (prefs_common.primary_paste_unselects)
10832 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10836 /* paste unwrapped: mark the paste so it's not wrapped later */
10837 end = start + strlen(contents);
10838 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10839 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10840 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10841 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10842 /* rewrap paragraph now (after a mid-click-paste) */
10843 mark_start = gtk_text_buffer_get_insert(buffer);
10844 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10845 gtk_text_iter_backward_char(&start_iter);
10846 compose_beautify_paragraph(compose, &start_iter, TRUE);
10848 } else if (GTK_IS_EDITABLE(entry))
10849 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10851 compose->modified = TRUE;
10854 static void entry_allsel(GtkWidget *entry)
10856 if (GTK_IS_EDITABLE(entry))
10857 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10858 else if (GTK_IS_TEXT_VIEW(entry)) {
10859 GtkTextIter startiter, enditer;
10860 GtkTextBuffer *textbuf;
10862 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10863 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10864 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10866 gtk_text_buffer_move_mark_by_name(textbuf,
10867 "selection_bound", &startiter);
10868 gtk_text_buffer_move_mark_by_name(textbuf,
10869 "insert", &enditer);
10873 static void compose_cut_cb(GtkAction *action, gpointer data)
10875 Compose *compose = (Compose *)data;
10876 if (compose->focused_editable
10877 #ifndef GENERIC_UMPC
10878 && gtk_widget_has_focus(compose->focused_editable)
10881 entry_cut_clipboard(compose->focused_editable);
10884 static void compose_copy_cb(GtkAction *action, gpointer data)
10886 Compose *compose = (Compose *)data;
10887 if (compose->focused_editable
10888 #ifndef GENERIC_UMPC
10889 && gtk_widget_has_focus(compose->focused_editable)
10892 entry_copy_clipboard(compose->focused_editable);
10895 static void compose_paste_cb(GtkAction *action, gpointer data)
10897 Compose *compose = (Compose *)data;
10898 gint prev_autowrap;
10899 GtkTextBuffer *buffer;
10901 if (compose->focused_editable &&
10902 #ifndef GENERIC_UMPC
10903 gtk_widget_has_focus(compose->focused_editable)
10906 entry_paste_clipboard(compose, compose->focused_editable,
10907 prefs_common.linewrap_pastes,
10908 GDK_SELECTION_CLIPBOARD, NULL);
10913 #ifndef GENERIC_UMPC
10914 gtk_widget_has_focus(compose->text) &&
10916 compose->gtkaspell &&
10917 compose->gtkaspell->check_while_typing)
10918 gtkaspell_highlight_all(compose->gtkaspell);
10922 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10924 Compose *compose = (Compose *)data;
10925 gint wrap_quote = prefs_common.linewrap_quote;
10926 if (compose->focused_editable
10927 #ifndef GENERIC_UMPC
10928 && gtk_widget_has_focus(compose->focused_editable)
10931 /* let text_insert() (called directly or at a later time
10932 * after the gtk_editable_paste_clipboard) know that
10933 * text is to be inserted as a quotation. implemented
10934 * by using a simple refcount... */
10935 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10936 G_OBJECT(compose->focused_editable),
10937 "paste_as_quotation"));
10938 g_object_set_data(G_OBJECT(compose->focused_editable),
10939 "paste_as_quotation",
10940 GINT_TO_POINTER(paste_as_quotation + 1));
10941 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10942 entry_paste_clipboard(compose, compose->focused_editable,
10943 prefs_common.linewrap_pastes,
10944 GDK_SELECTION_CLIPBOARD, NULL);
10945 prefs_common.linewrap_quote = wrap_quote;
10949 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10951 Compose *compose = (Compose *)data;
10952 gint prev_autowrap;
10953 GtkTextBuffer *buffer;
10955 if (compose->focused_editable
10956 #ifndef GENERIC_UMPC
10957 && gtk_widget_has_focus(compose->focused_editable)
10960 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10961 GDK_SELECTION_CLIPBOARD, NULL);
10966 #ifndef GENERIC_UMPC
10967 gtk_widget_has_focus(compose->text) &&
10969 compose->gtkaspell &&
10970 compose->gtkaspell->check_while_typing)
10971 gtkaspell_highlight_all(compose->gtkaspell);
10975 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10977 Compose *compose = (Compose *)data;
10978 gint prev_autowrap;
10979 GtkTextBuffer *buffer;
10981 if (compose->focused_editable
10982 #ifndef GENERIC_UMPC
10983 && gtk_widget_has_focus(compose->focused_editable)
10986 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10987 GDK_SELECTION_CLIPBOARD, NULL);
10992 #ifndef GENERIC_UMPC
10993 gtk_widget_has_focus(compose->text) &&
10995 compose->gtkaspell &&
10996 compose->gtkaspell->check_while_typing)
10997 gtkaspell_highlight_all(compose->gtkaspell);
11001 static void compose_allsel_cb(GtkAction *action, gpointer data)
11003 Compose *compose = (Compose *)data;
11004 if (compose->focused_editable
11005 #ifndef GENERIC_UMPC
11006 && gtk_widget_has_focus(compose->focused_editable)
11009 entry_allsel(compose->focused_editable);
11012 static void textview_move_beginning_of_line (GtkTextView *text)
11014 GtkTextBuffer *buffer;
11018 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11020 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11021 mark = gtk_text_buffer_get_insert(buffer);
11022 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11023 gtk_text_iter_set_line_offset(&ins, 0);
11024 gtk_text_buffer_place_cursor(buffer, &ins);
11027 static void textview_move_forward_character (GtkTextView *text)
11029 GtkTextBuffer *buffer;
11033 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11035 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11036 mark = gtk_text_buffer_get_insert(buffer);
11037 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11038 if (gtk_text_iter_forward_cursor_position(&ins))
11039 gtk_text_buffer_place_cursor(buffer, &ins);
11042 static void textview_move_backward_character (GtkTextView *text)
11044 GtkTextBuffer *buffer;
11048 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11050 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11051 mark = gtk_text_buffer_get_insert(buffer);
11052 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11053 if (gtk_text_iter_backward_cursor_position(&ins))
11054 gtk_text_buffer_place_cursor(buffer, &ins);
11057 static void textview_move_forward_word (GtkTextView *text)
11059 GtkTextBuffer *buffer;
11064 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11066 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11067 mark = gtk_text_buffer_get_insert(buffer);
11068 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11069 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11070 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11071 gtk_text_iter_backward_word_start(&ins);
11072 gtk_text_buffer_place_cursor(buffer, &ins);
11076 static void textview_move_backward_word (GtkTextView *text)
11078 GtkTextBuffer *buffer;
11082 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11084 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11085 mark = gtk_text_buffer_get_insert(buffer);
11086 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11087 if (gtk_text_iter_backward_word_starts(&ins, 1))
11088 gtk_text_buffer_place_cursor(buffer, &ins);
11091 static void textview_move_end_of_line (GtkTextView *text)
11093 GtkTextBuffer *buffer;
11097 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11099 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11100 mark = gtk_text_buffer_get_insert(buffer);
11101 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11102 if (gtk_text_iter_forward_to_line_end(&ins))
11103 gtk_text_buffer_place_cursor(buffer, &ins);
11106 static void textview_move_next_line (GtkTextView *text)
11108 GtkTextBuffer *buffer;
11113 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11115 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11116 mark = gtk_text_buffer_get_insert(buffer);
11117 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11118 offset = gtk_text_iter_get_line_offset(&ins);
11119 if (gtk_text_iter_forward_line(&ins)) {
11120 gtk_text_iter_set_line_offset(&ins, offset);
11121 gtk_text_buffer_place_cursor(buffer, &ins);
11125 static void textview_move_previous_line (GtkTextView *text)
11127 GtkTextBuffer *buffer;
11132 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11134 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11135 mark = gtk_text_buffer_get_insert(buffer);
11136 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11137 offset = gtk_text_iter_get_line_offset(&ins);
11138 if (gtk_text_iter_backward_line(&ins)) {
11139 gtk_text_iter_set_line_offset(&ins, offset);
11140 gtk_text_buffer_place_cursor(buffer, &ins);
11144 static void textview_delete_forward_character (GtkTextView *text)
11146 GtkTextBuffer *buffer;
11148 GtkTextIter ins, end_iter;
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);
11156 if (gtk_text_iter_forward_char(&end_iter)) {
11157 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11161 static void textview_delete_backward_character (GtkTextView *text)
11163 GtkTextBuffer *buffer;
11165 GtkTextIter ins, end_iter;
11167 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11169 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11170 mark = gtk_text_buffer_get_insert(buffer);
11171 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11173 if (gtk_text_iter_backward_char(&end_iter)) {
11174 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11178 static void textview_delete_forward_word (GtkTextView *text)
11180 GtkTextBuffer *buffer;
11182 GtkTextIter ins, end_iter;
11184 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11186 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11187 mark = gtk_text_buffer_get_insert(buffer);
11188 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11190 if (gtk_text_iter_forward_word_end(&end_iter)) {
11191 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11195 static void textview_delete_backward_word (GtkTextView *text)
11197 GtkTextBuffer *buffer;
11199 GtkTextIter ins, end_iter;
11201 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11203 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11204 mark = gtk_text_buffer_get_insert(buffer);
11205 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11207 if (gtk_text_iter_backward_word_start(&end_iter)) {
11208 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11212 static void textview_delete_line (GtkTextView *text)
11214 GtkTextBuffer *buffer;
11216 GtkTextIter ins, start_iter, end_iter;
11218 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11220 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11221 mark = gtk_text_buffer_get_insert(buffer);
11222 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11225 gtk_text_iter_set_line_offset(&start_iter, 0);
11228 if (gtk_text_iter_ends_line(&end_iter)){
11229 if (!gtk_text_iter_forward_char(&end_iter))
11230 gtk_text_iter_backward_char(&start_iter);
11233 gtk_text_iter_forward_to_line_end(&end_iter);
11234 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11237 static void textview_delete_to_line_end (GtkTextView *text)
11239 GtkTextBuffer *buffer;
11241 GtkTextIter ins, end_iter;
11243 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11245 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11246 mark = gtk_text_buffer_get_insert(buffer);
11247 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11249 if (gtk_text_iter_ends_line(&end_iter))
11250 gtk_text_iter_forward_char(&end_iter);
11252 gtk_text_iter_forward_to_line_end(&end_iter);
11253 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11256 #define DO_ACTION(name, act) { \
11257 if(!strcmp(name, a_name)) { \
11261 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11263 const gchar *a_name = gtk_action_get_name(action);
11264 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11265 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11266 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11267 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11268 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11269 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11270 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11271 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11272 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11273 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11274 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11275 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11276 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11277 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11278 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11281 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11283 Compose *compose = (Compose *)data;
11284 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11285 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11287 action = compose_call_advanced_action_from_path(gaction);
11290 void (*do_action) (GtkTextView *text);
11291 } action_table[] = {
11292 {textview_move_beginning_of_line},
11293 {textview_move_forward_character},
11294 {textview_move_backward_character},
11295 {textview_move_forward_word},
11296 {textview_move_backward_word},
11297 {textview_move_end_of_line},
11298 {textview_move_next_line},
11299 {textview_move_previous_line},
11300 {textview_delete_forward_character},
11301 {textview_delete_backward_character},
11302 {textview_delete_forward_word},
11303 {textview_delete_backward_word},
11304 {textview_delete_line},
11305 {textview_delete_to_line_end}
11308 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11310 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11311 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11312 if (action_table[action].do_action)
11313 action_table[action].do_action(text);
11315 g_warning("Not implemented yet.");
11319 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11321 GtkAllocation allocation;
11325 if (GTK_IS_EDITABLE(widget)) {
11326 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11327 gtk_editable_set_position(GTK_EDITABLE(widget),
11330 if ((parent = gtk_widget_get_parent(widget))
11331 && (parent = gtk_widget_get_parent(parent))
11332 && (parent = gtk_widget_get_parent(parent))) {
11333 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11334 gtk_widget_get_allocation(widget, &allocation);
11335 gint y = allocation.y;
11336 gint height = allocation.height;
11337 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11338 (GTK_SCROLLED_WINDOW(parent));
11340 gfloat value = gtk_adjustment_get_value(shown);
11341 gfloat upper = gtk_adjustment_get_upper(shown);
11342 gfloat page_size = gtk_adjustment_get_page_size(shown);
11343 if (y < (int)value) {
11344 gtk_adjustment_set_value(shown, y - 1);
11346 if ((y + height) > ((int)value + (int)page_size)) {
11347 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11348 gtk_adjustment_set_value(shown,
11349 y + height - (int)page_size - 1);
11351 gtk_adjustment_set_value(shown,
11352 (int)upper - (int)page_size - 1);
11359 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11360 compose->focused_editable = widget;
11362 #ifdef GENERIC_UMPC
11363 if (GTK_IS_TEXT_VIEW(widget)
11364 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11365 g_object_ref(compose->notebook);
11366 g_object_ref(compose->edit_vbox);
11367 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11368 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11369 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11370 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11371 g_object_unref(compose->notebook);
11372 g_object_unref(compose->edit_vbox);
11373 g_signal_handlers_block_by_func(G_OBJECT(widget),
11374 G_CALLBACK(compose_grab_focus_cb),
11376 gtk_widget_grab_focus(widget);
11377 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11378 G_CALLBACK(compose_grab_focus_cb),
11380 } else if (!GTK_IS_TEXT_VIEW(widget)
11381 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11382 g_object_ref(compose->notebook);
11383 g_object_ref(compose->edit_vbox);
11384 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11385 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11386 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11387 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11388 g_object_unref(compose->notebook);
11389 g_object_unref(compose->edit_vbox);
11390 g_signal_handlers_block_by_func(G_OBJECT(widget),
11391 G_CALLBACK(compose_grab_focus_cb),
11393 gtk_widget_grab_focus(widget);
11394 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11395 G_CALLBACK(compose_grab_focus_cb),
11401 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11403 compose->modified = TRUE;
11404 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11405 #ifndef GENERIC_UMPC
11406 compose_set_title(compose);
11410 static void compose_wrap_cb(GtkAction *action, gpointer data)
11412 Compose *compose = (Compose *)data;
11413 compose_beautify_paragraph(compose, NULL, TRUE);
11416 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11418 Compose *compose = (Compose *)data;
11419 compose_wrap_all_full(compose, TRUE);
11422 static void compose_find_cb(GtkAction *action, gpointer data)
11424 Compose *compose = (Compose *)data;
11426 message_search_compose(compose);
11429 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11432 Compose *compose = (Compose *)data;
11433 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11434 if (compose->autowrap)
11435 compose_wrap_all_full(compose, TRUE);
11436 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11439 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11442 Compose *compose = (Compose *)data;
11443 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11446 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11448 Compose *compose = (Compose *)data;
11450 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11451 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11454 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11456 Compose *compose = (Compose *)data;
11458 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11459 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11462 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11464 g_free(compose->privacy_system);
11465 g_free(compose->encdata);
11467 compose->privacy_system = g_strdup(account->default_privacy_system);
11468 compose_update_privacy_system_menu_item(compose, warn);
11471 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11473 Compose *compose = (Compose *)data;
11475 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11476 gtk_widget_show(compose->ruler_hbox);
11477 prefs_common.show_ruler = TRUE;
11479 gtk_widget_hide(compose->ruler_hbox);
11480 gtk_widget_queue_resize(compose->edit_vbox);
11481 prefs_common.show_ruler = FALSE;
11485 static void compose_attach_drag_received_cb (GtkWidget *widget,
11486 GdkDragContext *context,
11489 GtkSelectionData *data,
11492 gpointer user_data)
11494 Compose *compose = (Compose *)user_data;
11498 type = gtk_selection_data_get_data_type(data);
11499 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11500 && gtk_drag_get_source_widget(context) !=
11501 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11502 list = uri_list_extract_filenames(
11503 (const gchar *)gtk_selection_data_get_data(data));
11504 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11505 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11506 compose_attach_append
11507 (compose, (const gchar *)tmp->data,
11508 utf8_filename, NULL, NULL);
11509 g_free(utf8_filename);
11511 if (list) compose_changed_cb(NULL, compose);
11512 list_free_strings(list);
11514 } else if (gtk_drag_get_source_widget(context)
11515 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11516 /* comes from our summaryview */
11517 SummaryView * summaryview = NULL;
11518 GSList * list = NULL, *cur = NULL;
11520 if (mainwindow_get_mainwindow())
11521 summaryview = mainwindow_get_mainwindow()->summaryview;
11524 list = summary_get_selected_msg_list(summaryview);
11526 for (cur = list; cur; cur = cur->next) {
11527 MsgInfo *msginfo = (MsgInfo *)cur->data;
11528 gchar *file = NULL;
11530 file = procmsg_get_message_file_full(msginfo,
11533 compose_attach_append(compose, (const gchar *)file,
11534 (const gchar *)file, "message/rfc822", NULL);
11538 g_slist_free(list);
11542 static gboolean compose_drag_drop(GtkWidget *widget,
11543 GdkDragContext *drag_context,
11545 guint time, gpointer user_data)
11547 /* not handling this signal makes compose_insert_drag_received_cb
11552 static gboolean completion_set_focus_to_subject
11553 (GtkWidget *widget,
11554 GdkEventKey *event,
11557 cm_return_val_if_fail(compose != NULL, FALSE);
11559 /* make backtab move to subject field */
11560 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11561 gtk_widget_grab_focus(compose->subject_entry);
11567 static void compose_insert_drag_received_cb (GtkWidget *widget,
11568 GdkDragContext *drag_context,
11571 GtkSelectionData *data,
11574 gpointer user_data)
11576 Compose *compose = (Compose *)user_data;
11582 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11584 type = gtk_selection_data_get_data_type(data);
11585 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11586 AlertValue val = G_ALERTDEFAULT;
11587 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11589 list = uri_list_extract_filenames(ddata);
11590 num_files = g_list_length(list);
11591 if (list == NULL && strstr(ddata, "://")) {
11592 /* Assume a list of no files, and data has ://, is a remote link */
11593 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11594 gchar *tmpfile = get_tmp_file();
11595 str_write_to_file(tmpdata, tmpfile);
11597 compose_insert_file(compose, tmpfile);
11598 claws_unlink(tmpfile);
11600 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11601 compose_beautify_paragraph(compose, NULL, TRUE);
11604 switch (prefs_common.compose_dnd_mode) {
11605 case COMPOSE_DND_ASK:
11606 msg = g_strdup_printf(
11608 "Do you want to insert the contents of the file "
11609 "into the message body, or attach it to the email?",
11610 "Do you want to insert the contents of the %d files "
11611 "into the message body, or attach them to the email?",
11614 val = alertpanel_full(_("Insert or attach?"), msg,
11615 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11616 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11619 case COMPOSE_DND_INSERT:
11620 val = G_ALERTALTERNATE;
11622 case COMPOSE_DND_ATTACH:
11623 val = G_ALERTOTHER;
11626 /* unexpected case */
11627 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11630 if (val & G_ALERTDISABLE) {
11631 val &= ~G_ALERTDISABLE;
11632 /* remember what action to perform by default, only if we don't click Cancel */
11633 if (val == G_ALERTALTERNATE)
11634 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11635 else if (val == G_ALERTOTHER)
11636 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11639 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11640 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11641 list_free_strings(list);
11644 } else if (val == G_ALERTOTHER) {
11645 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11646 list_free_strings(list);
11651 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11652 compose_insert_file(compose, (const gchar *)tmp->data);
11654 list_free_strings(list);
11656 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11661 static void compose_header_drag_received_cb (GtkWidget *widget,
11662 GdkDragContext *drag_context,
11665 GtkSelectionData *data,
11668 gpointer user_data)
11670 GtkEditable *entry = (GtkEditable *)user_data;
11671 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11673 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11676 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11677 gchar *decoded=g_new(gchar, strlen(email));
11680 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11681 gtk_editable_delete_text(entry, 0, -1);
11682 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11683 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11687 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11690 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11692 Compose *compose = (Compose *)data;
11694 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11695 compose->return_receipt = TRUE;
11697 compose->return_receipt = FALSE;
11700 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11702 Compose *compose = (Compose *)data;
11704 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11705 compose->remove_references = TRUE;
11707 compose->remove_references = FALSE;
11710 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11711 ComposeHeaderEntry *headerentry)
11713 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11714 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11715 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11719 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11720 GdkEventKey *event,
11721 ComposeHeaderEntry *headerentry)
11723 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11724 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11725 !(event->state & GDK_MODIFIER_MASK) &&
11726 (event->keyval == GDK_KEY_BackSpace) &&
11727 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11728 gtk_container_remove
11729 (GTK_CONTAINER(headerentry->compose->header_table),
11730 headerentry->combo);
11731 gtk_container_remove
11732 (GTK_CONTAINER(headerentry->compose->header_table),
11733 headerentry->entry);
11734 headerentry->compose->header_list =
11735 g_slist_remove(headerentry->compose->header_list,
11737 g_free(headerentry);
11738 } else if (event->keyval == GDK_KEY_Tab) {
11739 if (headerentry->compose->header_last == headerentry) {
11740 /* Override default next focus, and give it to subject_entry
11741 * instead of notebook tabs
11743 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11744 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11751 static gboolean scroll_postpone(gpointer data)
11753 Compose *compose = (Compose *)data;
11755 if (compose->batch)
11758 GTK_EVENTS_FLUSH();
11759 compose_show_first_last_header(compose, FALSE);
11763 static void compose_headerentry_changed_cb(GtkWidget *entry,
11764 ComposeHeaderEntry *headerentry)
11766 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11767 compose_create_header_entry(headerentry->compose);
11768 g_signal_handlers_disconnect_matched
11769 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11770 0, 0, NULL, NULL, headerentry);
11772 if (!headerentry->compose->batch)
11773 g_timeout_add(0, scroll_postpone, headerentry->compose);
11777 static gboolean compose_defer_auto_save_draft(Compose *compose)
11779 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11780 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11784 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11786 GtkAdjustment *vadj;
11788 cm_return_if_fail(compose);
11793 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11794 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11795 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11796 gtk_widget_get_parent(compose->header_table)));
11797 gtk_adjustment_set_value(vadj, (show_first ?
11798 gtk_adjustment_get_lower(vadj) :
11799 (gtk_adjustment_get_upper(vadj) -
11800 gtk_adjustment_get_page_size(vadj))));
11801 gtk_adjustment_changed(vadj);
11804 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11805 const gchar *text, gint len, Compose *compose)
11807 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11808 (G_OBJECT(compose->text), "paste_as_quotation"));
11811 cm_return_if_fail(text != NULL);
11813 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11814 G_CALLBACK(text_inserted),
11816 if (paste_as_quotation) {
11818 const gchar *qmark;
11820 GtkTextIter start_iter;
11823 len = strlen(text);
11825 new_text = g_strndup(text, len);
11827 qmark = compose_quote_char_from_context(compose);
11829 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11830 gtk_text_buffer_place_cursor(buffer, iter);
11832 pos = gtk_text_iter_get_offset(iter);
11834 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11835 _("Quote format error at line %d."));
11836 quote_fmt_reset_vartable();
11838 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11839 GINT_TO_POINTER(paste_as_quotation - 1));
11841 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11842 gtk_text_buffer_place_cursor(buffer, iter);
11843 gtk_text_buffer_delete_mark(buffer, mark);
11845 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11846 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11847 compose_beautify_paragraph(compose, &start_iter, FALSE);
11848 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11849 gtk_text_buffer_delete_mark(buffer, mark);
11851 if (strcmp(text, "\n") || compose->automatic_break
11852 || gtk_text_iter_starts_line(iter)) {
11853 GtkTextIter before_ins;
11854 gtk_text_buffer_insert(buffer, iter, text, len);
11855 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11856 before_ins = *iter;
11857 gtk_text_iter_backward_chars(&before_ins, len);
11858 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11861 /* check if the preceding is just whitespace or quote */
11862 GtkTextIter start_line;
11863 gchar *tmp = NULL, *quote = NULL;
11864 gint quote_len = 0, is_normal = 0;
11865 start_line = *iter;
11866 gtk_text_iter_set_line_offset(&start_line, 0);
11867 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11870 if (*tmp == '\0') {
11873 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11881 gtk_text_buffer_insert(buffer, iter, text, len);
11883 gtk_text_buffer_insert_with_tags_by_name(buffer,
11884 iter, text, len, "no_join", NULL);
11889 if (!paste_as_quotation) {
11890 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11891 compose_beautify_paragraph(compose, iter, FALSE);
11892 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11893 gtk_text_buffer_delete_mark(buffer, mark);
11896 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11897 G_CALLBACK(text_inserted),
11899 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11901 if (compose_can_autosave(compose) &&
11902 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11903 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11904 compose->draft_timeout_tag = g_timeout_add
11905 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11909 static void compose_check_all(GtkAction *action, gpointer data)
11911 Compose *compose = (Compose *)data;
11912 if (!compose->gtkaspell)
11915 if (gtk_widget_has_focus(compose->subject_entry))
11916 claws_spell_entry_check_all(
11917 CLAWS_SPELL_ENTRY(compose->subject_entry));
11919 gtkaspell_check_all(compose->gtkaspell);
11922 static void compose_highlight_all(GtkAction *action, gpointer data)
11924 Compose *compose = (Compose *)data;
11925 if (compose->gtkaspell) {
11926 claws_spell_entry_recheck_all(
11927 CLAWS_SPELL_ENTRY(compose->subject_entry));
11928 gtkaspell_highlight_all(compose->gtkaspell);
11932 static void compose_check_backwards(GtkAction *action, gpointer data)
11934 Compose *compose = (Compose *)data;
11935 if (!compose->gtkaspell) {
11936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11940 if (gtk_widget_has_focus(compose->subject_entry))
11941 claws_spell_entry_check_backwards(
11942 CLAWS_SPELL_ENTRY(compose->subject_entry));
11944 gtkaspell_check_backwards(compose->gtkaspell);
11947 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11949 Compose *compose = (Compose *)data;
11950 if (!compose->gtkaspell) {
11951 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11955 if (gtk_widget_has_focus(compose->subject_entry))
11956 claws_spell_entry_check_forwards_go(
11957 CLAWS_SPELL_ENTRY(compose->subject_entry));
11959 gtkaspell_check_forwards_go(compose->gtkaspell);
11964 *\brief Guess originating forward account from MsgInfo and several
11965 * "common preference" settings. Return NULL if no guess.
11967 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11969 PrefsAccount *account = NULL;
11971 cm_return_val_if_fail(msginfo, NULL);
11972 cm_return_val_if_fail(msginfo->folder, NULL);
11973 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11975 if (msginfo->folder->prefs->enable_default_account)
11976 account = account_find_from_id(msginfo->folder->prefs->default_account);
11978 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11980 Xstrdup_a(to, msginfo->to, return NULL);
11981 extract_address(to);
11982 account = account_find_from_address(to, FALSE);
11985 if (!account && prefs_common.forward_account_autosel) {
11987 if (!procheader_get_header_from_msginfo
11988 (msginfo, &cc, "Cc:")) {
11989 gchar *buf = cc + strlen("Cc:");
11990 extract_address(buf);
11991 account = account_find_from_address(buf, FALSE);
11996 if (!account && prefs_common.forward_account_autosel) {
11997 gchar *deliveredto = NULL;
11998 if (!procheader_get_header_from_msginfo
11999 (msginfo, &deliveredto, "Delivered-To:")) {
12000 gchar *buf = deliveredto + strlen("Delivered-To:");
12001 extract_address(buf);
12002 account = account_find_from_address(buf, FALSE);
12003 g_free(deliveredto);
12008 account = msginfo->folder->folder->account;
12013 gboolean compose_close(Compose *compose)
12017 cm_return_val_if_fail(compose, FALSE);
12019 if (!g_mutex_trylock(compose->mutex)) {
12020 /* we have to wait for the (possibly deferred by auto-save)
12021 * drafting to be done, before destroying the compose under
12023 debug_print("waiting for drafting to finish...\n");
12024 compose_allow_user_actions(compose, FALSE);
12025 if (compose->close_timeout_tag == 0) {
12026 compose->close_timeout_tag =
12027 g_timeout_add (500, (GSourceFunc) compose_close,
12033 if (compose->draft_timeout_tag >= 0) {
12034 g_source_remove(compose->draft_timeout_tag);
12035 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12038 gtkut_widget_get_uposition(compose->window, &x, &y);
12039 if (!compose->batch) {
12040 prefs_common.compose_x = x;
12041 prefs_common.compose_y = y;
12043 g_mutex_unlock(compose->mutex);
12044 compose_destroy(compose);
12049 * Add entry field for each address in list.
12050 * \param compose E-Mail composition object.
12051 * \param listAddress List of (formatted) E-Mail addresses.
12053 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12056 node = listAddress;
12058 addr = ( gchar * ) node->data;
12059 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12060 node = g_list_next( node );
12064 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12065 guint action, gboolean opening_multiple)
12067 gchar *body = NULL;
12068 GSList *new_msglist = NULL;
12069 MsgInfo *tmp_msginfo = NULL;
12070 gboolean originally_enc = FALSE;
12071 gboolean originally_sig = FALSE;
12072 Compose *compose = NULL;
12073 gchar *s_system = NULL;
12075 cm_return_if_fail(msgview != NULL);
12077 cm_return_if_fail(msginfo_list != NULL);
12079 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12080 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12081 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12083 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12084 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12085 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12086 orig_msginfo, mimeinfo);
12087 if (tmp_msginfo != NULL) {
12088 new_msglist = g_slist_append(NULL, tmp_msginfo);
12090 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12091 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12092 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12094 tmp_msginfo->folder = orig_msginfo->folder;
12095 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12096 if (orig_msginfo->tags) {
12097 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12098 tmp_msginfo->folder->tags_dirty = TRUE;
12104 if (!opening_multiple)
12105 body = messageview_get_selection(msgview);
12108 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12109 procmsg_msginfo_free(&tmp_msginfo);
12110 g_slist_free(new_msglist);
12112 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12114 if (compose && originally_enc) {
12115 compose_force_encryption(compose, compose->account, FALSE, s_system);
12118 if (compose && originally_sig && compose->account->default_sign_reply) {
12119 compose_force_signing(compose, compose->account, s_system);
12123 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12126 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12129 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12130 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12131 GSList *cur = msginfo_list;
12132 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12133 "messages. Opening the windows "
12134 "could take some time. Do you "
12135 "want to continue?"),
12136 g_slist_length(msginfo_list));
12137 if (g_slist_length(msginfo_list) > 9
12138 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12139 != G_ALERTALTERNATE) {
12144 /* We'll open multiple compose windows */
12145 /* let the WM place the next windows */
12146 compose_force_window_origin = FALSE;
12147 for (; cur; cur = cur->next) {
12149 tmplist.data = cur->data;
12150 tmplist.next = NULL;
12151 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12153 compose_force_window_origin = TRUE;
12155 /* forwarding multiple mails as attachments is done via a
12156 * single compose window */
12157 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12161 void compose_check_for_email_account(Compose *compose)
12163 PrefsAccount *ac = NULL, *curr = NULL;
12169 if (compose->account && compose->account->protocol == A_NNTP) {
12170 ac = account_get_cur_account();
12171 if (ac->protocol == A_NNTP) {
12172 list = account_get_list();
12174 for( ; list != NULL ; list = g_list_next(list)) {
12175 curr = (PrefsAccount *) list->data;
12176 if (curr->protocol != A_NNTP) {
12182 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12187 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12188 const gchar *address)
12190 GSList *msginfo_list = NULL;
12191 gchar *body = messageview_get_selection(msgview);
12194 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12196 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12197 compose_check_for_email_account(compose);
12198 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12199 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12200 compose_reply_set_subject(compose, msginfo);
12203 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12206 void compose_set_position(Compose *compose, gint pos)
12208 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12210 gtkut_text_view_set_position(text, pos);
12213 gboolean compose_search_string(Compose *compose,
12214 const gchar *str, gboolean case_sens)
12216 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12218 return gtkut_text_view_search_string(text, str, case_sens);
12221 gboolean compose_search_string_backward(Compose *compose,
12222 const gchar *str, gboolean case_sens)
12224 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12226 return gtkut_text_view_search_string_backward(text, str, case_sens);
12229 /* allocate a msginfo structure and populate its data from a compose data structure */
12230 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12232 MsgInfo *newmsginfo;
12234 gchar date[RFC822_DATE_BUFFSIZE];
12236 cm_return_val_if_fail( compose != NULL, NULL );
12238 newmsginfo = procmsg_msginfo_new();
12241 get_rfc822_date(date, sizeof(date));
12242 newmsginfo->date = g_strdup(date);
12245 if (compose->from_name) {
12246 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12247 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12251 if (compose->subject_entry)
12252 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12254 /* to, cc, reply-to, newsgroups */
12255 for (list = compose->header_list; list; list = list->next) {
12256 gchar *header = gtk_editable_get_chars(
12258 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12259 gchar *entry = gtk_editable_get_chars(
12260 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12262 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12263 if ( newmsginfo->to == NULL ) {
12264 newmsginfo->to = g_strdup(entry);
12265 } else if (entry && *entry) {
12266 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12267 g_free(newmsginfo->to);
12268 newmsginfo->to = tmp;
12271 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12272 if ( newmsginfo->cc == NULL ) {
12273 newmsginfo->cc = g_strdup(entry);
12274 } else if (entry && *entry) {
12275 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12276 g_free(newmsginfo->cc);
12277 newmsginfo->cc = tmp;
12280 if ( strcasecmp(header,
12281 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12282 if ( newmsginfo->newsgroups == NULL ) {
12283 newmsginfo->newsgroups = g_strdup(entry);
12284 } else if (entry && *entry) {
12285 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12286 g_free(newmsginfo->newsgroups);
12287 newmsginfo->newsgroups = tmp;
12295 /* other data is unset */
12301 /* update compose's dictionaries from folder dict settings */
12302 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12303 FolderItem *folder_item)
12305 cm_return_if_fail(compose != NULL);
12307 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12308 FolderItemPrefs *prefs = folder_item->prefs;
12310 if (prefs->enable_default_dictionary)
12311 gtkaspell_change_dict(compose->gtkaspell,
12312 prefs->default_dictionary, FALSE);
12313 if (folder_item->prefs->enable_default_alt_dictionary)
12314 gtkaspell_change_alt_dict(compose->gtkaspell,
12315 prefs->default_alt_dictionary);
12316 if (prefs->enable_default_dictionary
12317 || prefs->enable_default_alt_dictionary)
12318 compose_spell_menu_changed(compose);
12323 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12325 Compose *compose = (Compose *)data;
12327 cm_return_if_fail(compose != NULL);
12329 gtk_widget_grab_focus(compose->text);
12332 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12334 gtk_combo_box_popup(GTK_COMBO_BOX(data));