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;
3639 GError *error = NULL;
3645 GString *file_contents = NULL;
3646 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3648 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3650 /* get the size of the file we are about to insert */
3652 f = g_file_new_for_path(file);
3653 fi = g_file_query_info(f, "standard::size",
3654 G_FILE_QUERY_INFO_NONE, NULL, &error);
3656 if (error != NULL) {
3657 g_warning(error->message);
3659 g_error_free(error);
3663 ret = g_stat(file, &file_stat);
3666 gchar *shortfile = g_path_get_basename(file);
3667 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3669 return COMPOSE_INSERT_NO_FILE;
3670 } else if (prefs_common.warn_large_insert == TRUE) {
3672 size = g_file_info_get_size(fi);
3676 size = file_stat.st_size;
3679 /* ask user for confirmation if the file is large */
3680 if (prefs_common.warn_large_insert_size < 0 ||
3681 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3685 msg = g_strdup_printf(_("You are about to insert a file of %s "
3686 "in the message body. Are you sure you want to do that?"),
3687 to_human_readable(size));
3688 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3689 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3692 /* do we ask for confirmation next time? */
3693 if (aval & G_ALERTDISABLE) {
3694 /* no confirmation next time, disable feature in preferences */
3695 aval &= ~G_ALERTDISABLE;
3696 prefs_common.warn_large_insert = FALSE;
3699 /* abort file insertion if user canceled action */
3700 if (aval != G_ALERTALTERNATE) {
3701 return COMPOSE_INSERT_NO_FILE;
3707 if ((fp = g_fopen(file, "rb")) == NULL) {
3708 FILE_OP_ERROR(file, "fopen");
3709 return COMPOSE_INSERT_READ_ERROR;
3712 prev_autowrap = compose->autowrap;
3713 compose->autowrap = FALSE;
3715 text = GTK_TEXT_VIEW(compose->text);
3716 buffer = gtk_text_view_get_buffer(text);
3717 mark = gtk_text_buffer_get_insert(buffer);
3718 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3720 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3721 G_CALLBACK(text_inserted),
3724 cur_encoding = conv_get_locale_charset_str_no_utf8();
3726 file_contents = g_string_new("");
3727 while (fgets(buf, sizeof(buf), fp) != NULL) {
3730 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3731 str = g_strdup(buf);
3733 codeconv_set_strict(TRUE);
3734 str = conv_codeset_strdup
3735 (buf, cur_encoding, CS_INTERNAL);
3736 codeconv_set_strict(FALSE);
3739 result = COMPOSE_INSERT_INVALID_CHARACTER;
3745 /* strip <CR> if DOS/Windows file,
3746 replace <CR> with <LF> if Macintosh file. */
3749 if (len > 0 && str[len - 1] != '\n') {
3751 if (str[len] == '\r') str[len] = '\n';
3754 file_contents = g_string_append(file_contents, str);
3758 if (result == COMPOSE_INSERT_SUCCESS) {
3759 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3761 compose_changed_cb(NULL, compose);
3762 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3763 G_CALLBACK(text_inserted),
3765 compose->autowrap = prev_autowrap;
3766 if (compose->autowrap)
3767 compose_wrap_all(compose);
3770 g_string_free(file_contents, TRUE);
3776 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3777 const gchar *filename,
3778 const gchar *content_type,
3779 const gchar *charset)
3787 GtkListStore *store;
3789 gboolean has_binary = FALSE;
3791 if (!is_file_exist(file)) {
3792 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3793 gboolean result = FALSE;
3794 if (file_from_uri && is_file_exist(file_from_uri)) {
3795 result = compose_attach_append(
3796 compose, file_from_uri,
3797 filename, content_type,
3800 g_free(file_from_uri);
3803 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3806 if ((size = get_file_size(file)) < 0) {
3807 alertpanel_error("Can't get file size of %s\n", filename);
3811 /* In batch mode, we allow 0-length files to be attached no questions asked */
3812 if (size == 0 && !compose->batch) {
3813 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3814 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3815 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3816 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3819 if (aval != G_ALERTALTERNATE) {
3823 if ((fp = g_fopen(file, "rb")) == NULL) {
3824 alertpanel_error(_("Can't read %s."), filename);
3829 ainfo = g_new0(AttachInfo, 1);
3830 auto_ainfo = g_auto_pointer_new_with_free
3831 (ainfo, (GFreeFunc) compose_attach_info_free);
3832 ainfo->file = g_strdup(file);
3835 ainfo->content_type = g_strdup(content_type);
3836 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3838 MsgFlags flags = {0, 0};
3840 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3841 ainfo->encoding = ENC_7BIT;
3843 ainfo->encoding = ENC_8BIT;
3845 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3846 if (msginfo && msginfo->subject)
3847 name = g_strdup(msginfo->subject);
3849 name = g_path_get_basename(filename ? filename : file);
3851 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3853 procmsg_msginfo_free(&msginfo);
3855 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3856 ainfo->charset = g_strdup(charset);
3857 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3859 ainfo->encoding = ENC_BASE64;
3861 name = g_path_get_basename(filename ? filename : file);
3862 ainfo->name = g_strdup(name);
3866 ainfo->content_type = procmime_get_mime_type(file);
3867 if (!ainfo->content_type) {
3868 ainfo->content_type =
3869 g_strdup("application/octet-stream");
3870 ainfo->encoding = ENC_BASE64;
3871 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3873 procmime_get_encoding_for_text_file(file, &has_binary);
3875 ainfo->encoding = ENC_BASE64;
3876 name = g_path_get_basename(filename ? filename : file);
3877 ainfo->name = g_strdup(name);
3881 if (ainfo->name != NULL
3882 && !strcmp(ainfo->name, ".")) {
3883 g_free(ainfo->name);
3887 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3888 g_free(ainfo->content_type);
3889 ainfo->content_type = g_strdup("application/octet-stream");
3890 g_free(ainfo->charset);
3891 ainfo->charset = NULL;
3894 ainfo->size = (goffset)size;
3895 size_text = to_human_readable((goffset)size);
3897 store = GTK_LIST_STORE(gtk_tree_view_get_model
3898 (GTK_TREE_VIEW(compose->attach_clist)));
3900 gtk_list_store_append(store, &iter);
3901 gtk_list_store_set(store, &iter,
3902 COL_MIMETYPE, ainfo->content_type,
3903 COL_SIZE, size_text,
3904 COL_NAME, ainfo->name,
3905 COL_CHARSET, ainfo->charset,
3907 COL_AUTODATA, auto_ainfo,
3910 g_auto_pointer_free(auto_ainfo);
3911 compose_attach_update_label(compose);
3915 void compose_use_signing(Compose *compose, gboolean use_signing)
3917 compose->use_signing = use_signing;
3918 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3921 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3923 compose->use_encryption = use_encryption;
3924 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3927 #define NEXT_PART_NOT_CHILD(info) \
3929 node = info->node; \
3930 while (node->children) \
3931 node = g_node_last_child(node); \
3932 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3935 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3939 MimeInfo *firsttext = NULL;
3940 MimeInfo *encrypted = NULL;
3943 const gchar *partname = NULL;
3945 mimeinfo = procmime_scan_message(msginfo);
3946 if (!mimeinfo) return;
3948 if (mimeinfo->node->children == NULL) {
3949 procmime_mimeinfo_free_all(&mimeinfo);
3953 /* find first content part */
3954 child = (MimeInfo *) mimeinfo->node->children->data;
3955 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3956 child = (MimeInfo *)child->node->children->data;
3959 if (child->type == MIMETYPE_TEXT) {
3961 debug_print("First text part found\n");
3962 } else if (compose->mode == COMPOSE_REEDIT &&
3963 child->type == MIMETYPE_APPLICATION &&
3964 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3965 encrypted = (MimeInfo *)child->node->parent->data;
3968 child = (MimeInfo *) mimeinfo->node->children->data;
3969 while (child != NULL) {
3972 if (child == encrypted) {
3973 /* skip this part of tree */
3974 NEXT_PART_NOT_CHILD(child);
3978 if (child->type == MIMETYPE_MULTIPART) {
3979 /* get the actual content */
3980 child = procmime_mimeinfo_next(child);
3984 if (child == firsttext) {
3985 child = procmime_mimeinfo_next(child);
3989 outfile = procmime_get_tmp_file_name(child);
3990 if ((err = procmime_get_part(outfile, child)) < 0)
3991 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3993 gchar *content_type;
3995 content_type = procmime_get_content_type_str(child->type, child->subtype);
3997 /* if we meet a pgp signature, we don't attach it, but
3998 * we force signing. */
3999 if ((strcmp(content_type, "application/pgp-signature") &&
4000 strcmp(content_type, "application/pkcs7-signature") &&
4001 strcmp(content_type, "application/x-pkcs7-signature"))
4002 || compose->mode == COMPOSE_REDIRECT) {
4003 partname = procmime_mimeinfo_get_parameter(child, "filename");
4004 if (partname == NULL)
4005 partname = procmime_mimeinfo_get_parameter(child, "name");
4006 if (partname == NULL)
4008 compose_attach_append(compose, outfile,
4009 partname, content_type,
4010 procmime_mimeinfo_get_parameter(child, "charset"));
4012 compose_force_signing(compose, compose->account, NULL);
4014 g_free(content_type);
4017 NEXT_PART_NOT_CHILD(child);
4019 procmime_mimeinfo_free_all(&mimeinfo);
4022 #undef NEXT_PART_NOT_CHILD
4027 WAIT_FOR_INDENT_CHAR,
4028 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4031 /* return indent length, we allow:
4032 indent characters followed by indent characters or spaces/tabs,
4033 alphabets and numbers immediately followed by indent characters,
4034 and the repeating sequences of the above
4035 If quote ends with multiple spaces, only the first one is included. */
4036 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4037 const GtkTextIter *start, gint *len)
4039 GtkTextIter iter = *start;
4043 IndentState state = WAIT_FOR_INDENT_CHAR;
4046 gint alnum_count = 0;
4047 gint space_count = 0;
4050 if (prefs_common.quote_chars == NULL) {
4054 while (!gtk_text_iter_ends_line(&iter)) {
4055 wc = gtk_text_iter_get_char(&iter);
4056 if (g_unichar_iswide(wc))
4058 clen = g_unichar_to_utf8(wc, ch);
4062 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4063 is_space = g_unichar_isspace(wc);
4065 if (state == WAIT_FOR_INDENT_CHAR) {
4066 if (!is_indent && !g_unichar_isalnum(wc))
4069 quote_len += alnum_count + space_count + 1;
4070 alnum_count = space_count = 0;
4071 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4074 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4075 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4079 else if (is_indent) {
4080 quote_len += alnum_count + space_count + 1;
4081 alnum_count = space_count = 0;
4084 state = WAIT_FOR_INDENT_CHAR;
4088 gtk_text_iter_forward_char(&iter);
4091 if (quote_len > 0 && space_count > 0)
4097 if (quote_len > 0) {
4099 gtk_text_iter_forward_chars(&iter, quote_len);
4100 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4106 /* return >0 if the line is itemized */
4107 static int compose_itemized_length(GtkTextBuffer *buffer,
4108 const GtkTextIter *start)
4110 GtkTextIter iter = *start;
4115 if (gtk_text_iter_ends_line(&iter))
4120 wc = gtk_text_iter_get_char(&iter);
4121 if (!g_unichar_isspace(wc))
4123 gtk_text_iter_forward_char(&iter);
4124 if (gtk_text_iter_ends_line(&iter))
4128 clen = g_unichar_to_utf8(wc, ch);
4129 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4131 wc == 0x2022 || /* BULLET */
4132 wc == 0x2023 || /* TRIANGULAR BULLET */
4133 wc == 0x2043 || /* HYPHEN BULLET */
4134 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4135 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4136 wc == 0x2219 || /* BULLET OPERATOR */
4137 wc == 0x25d8 || /* INVERSE BULLET */
4138 wc == 0x25e6 || /* WHITE BULLET */
4139 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4140 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4141 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4142 wc == 0x29be || /* CIRCLED WHITE BULLET */
4143 wc == 0x29bf /* CIRCLED BULLET */
4147 gtk_text_iter_forward_char(&iter);
4148 if (gtk_text_iter_ends_line(&iter))
4150 wc = gtk_text_iter_get_char(&iter);
4151 if (g_unichar_isspace(wc)) {
4157 /* return the string at the start of the itemization */
4158 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4159 const GtkTextIter *start)
4161 GtkTextIter iter = *start;
4164 GString *item_chars = g_string_new("");
4167 if (gtk_text_iter_ends_line(&iter))
4172 wc = gtk_text_iter_get_char(&iter);
4173 if (!g_unichar_isspace(wc))
4175 gtk_text_iter_forward_char(&iter);
4176 if (gtk_text_iter_ends_line(&iter))
4178 g_string_append_unichar(item_chars, wc);
4181 str = item_chars->str;
4182 g_string_free(item_chars, FALSE);
4186 /* return the number of spaces at a line's start */
4187 static int compose_left_offset_length(GtkTextBuffer *buffer,
4188 const GtkTextIter *start)
4190 GtkTextIter iter = *start;
4193 if (gtk_text_iter_ends_line(&iter))
4197 wc = gtk_text_iter_get_char(&iter);
4198 if (!g_unichar_isspace(wc))
4201 gtk_text_iter_forward_char(&iter);
4202 if (gtk_text_iter_ends_line(&iter))
4206 gtk_text_iter_forward_char(&iter);
4207 if (gtk_text_iter_ends_line(&iter))
4212 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4213 const GtkTextIter *start,
4214 GtkTextIter *break_pos,
4218 GtkTextIter iter = *start, line_end = *start;
4219 PangoLogAttr *attrs;
4226 gboolean can_break = FALSE;
4227 gboolean do_break = FALSE;
4228 gboolean was_white = FALSE;
4229 gboolean prev_dont_break = FALSE;
4231 gtk_text_iter_forward_to_line_end(&line_end);
4232 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4233 len = g_utf8_strlen(str, -1);
4237 g_warning("compose_get_line_break_pos: len = 0!");
4241 /* g_print("breaking line: %d: %s (len = %d)\n",
4242 gtk_text_iter_get_line(&iter), str, len); */
4244 attrs = g_new(PangoLogAttr, len + 1);
4246 pango_default_break(str, -1, NULL, attrs, len + 1);
4250 /* skip quote and leading spaces */
4251 for (i = 0; *p != '\0' && i < len; i++) {
4254 wc = g_utf8_get_char(p);
4255 if (i >= quote_len && !g_unichar_isspace(wc))
4257 if (g_unichar_iswide(wc))
4259 else if (*p == '\t')
4263 p = g_utf8_next_char(p);
4266 for (; *p != '\0' && i < len; i++) {
4267 PangoLogAttr *attr = attrs + i;
4271 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4274 was_white = attr->is_white;
4276 /* don't wrap URI */
4277 if ((uri_len = get_uri_len(p)) > 0) {
4279 if (pos > 0 && col > max_col) {
4289 wc = g_utf8_get_char(p);
4290 if (g_unichar_iswide(wc)) {
4292 if (prev_dont_break && can_break && attr->is_line_break)
4294 } else if (*p == '\t')
4298 if (pos > 0 && col > max_col) {
4303 if (*p == '-' || *p == '/')
4304 prev_dont_break = TRUE;
4306 prev_dont_break = FALSE;
4308 p = g_utf8_next_char(p);
4312 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4317 *break_pos = *start;
4318 gtk_text_iter_set_line_offset(break_pos, pos);
4323 static gboolean compose_join_next_line(Compose *compose,
4324 GtkTextBuffer *buffer,
4326 const gchar *quote_str)
4328 GtkTextIter iter_ = *iter, cur, prev, next, end;
4329 PangoLogAttr attrs[3];
4331 gchar *next_quote_str;
4334 gboolean keep_cursor = FALSE;
4336 if (!gtk_text_iter_forward_line(&iter_) ||
4337 gtk_text_iter_ends_line(&iter_)) {
4340 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4342 if ((quote_str || next_quote_str) &&
4343 strcmp2(quote_str, next_quote_str) != 0) {
4344 g_free(next_quote_str);
4347 g_free(next_quote_str);
4350 if (quote_len > 0) {
4351 gtk_text_iter_forward_chars(&end, quote_len);
4352 if (gtk_text_iter_ends_line(&end)) {
4357 /* don't join itemized lines */
4358 if (compose_itemized_length(buffer, &end) > 0) {
4362 /* don't join signature separator */
4363 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4366 /* delete quote str */
4368 gtk_text_buffer_delete(buffer, &iter_, &end);
4370 /* don't join line breaks put by the user */
4372 gtk_text_iter_backward_char(&cur);
4373 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4374 gtk_text_iter_forward_char(&cur);
4378 gtk_text_iter_forward_char(&cur);
4379 /* delete linebreak and extra spaces */
4380 while (gtk_text_iter_backward_char(&cur)) {
4381 wc1 = gtk_text_iter_get_char(&cur);
4382 if (!g_unichar_isspace(wc1))
4387 while (!gtk_text_iter_ends_line(&cur)) {
4388 wc1 = gtk_text_iter_get_char(&cur);
4389 if (!g_unichar_isspace(wc1))
4391 gtk_text_iter_forward_char(&cur);
4394 if (!gtk_text_iter_equal(&prev, &next)) {
4397 mark = gtk_text_buffer_get_insert(buffer);
4398 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4399 if (gtk_text_iter_equal(&prev, &cur))
4401 gtk_text_buffer_delete(buffer, &prev, &next);
4405 /* insert space if required */
4406 gtk_text_iter_backward_char(&prev);
4407 wc1 = gtk_text_iter_get_char(&prev);
4408 wc2 = gtk_text_iter_get_char(&next);
4409 gtk_text_iter_forward_char(&next);
4410 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4411 pango_default_break(str, -1, NULL, attrs, 3);
4412 if (!attrs[1].is_line_break ||
4413 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4414 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4416 gtk_text_iter_backward_char(&iter_);
4417 gtk_text_buffer_place_cursor(buffer, &iter_);
4426 #define ADD_TXT_POS(bp_, ep_, pti_) \
4427 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4428 last = last->next; \
4429 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4430 last->next = NULL; \
4432 g_warning("alloc error scanning URIs"); \
4435 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4437 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4438 GtkTextBuffer *buffer;
4439 GtkTextIter iter, break_pos, end_of_line;
4440 gchar *quote_str = NULL;
4442 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4443 gboolean prev_autowrap = compose->autowrap;
4444 gint startq_offset = -1, noq_offset = -1;
4445 gint uri_start = -1, uri_stop = -1;
4446 gint nouri_start = -1, nouri_stop = -1;
4447 gint num_blocks = 0;
4448 gint quotelevel = -1;
4449 gboolean modified = force;
4450 gboolean removed = FALSE;
4451 gboolean modified_before_remove = FALSE;
4453 gboolean start = TRUE;
4454 gint itemized_len = 0, rem_item_len = 0;
4455 gchar *itemized_chars = NULL;
4456 gboolean item_continuation = FALSE;
4461 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4465 compose->autowrap = FALSE;
4467 buffer = gtk_text_view_get_buffer(text);
4468 undo_wrapping(compose->undostruct, TRUE);
4473 mark = gtk_text_buffer_get_insert(buffer);
4474 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4478 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4479 if (gtk_text_iter_ends_line(&iter)) {
4480 while (gtk_text_iter_ends_line(&iter) &&
4481 gtk_text_iter_forward_line(&iter))
4484 while (gtk_text_iter_backward_line(&iter)) {
4485 if (gtk_text_iter_ends_line(&iter)) {
4486 gtk_text_iter_forward_line(&iter);
4492 /* move to line start */
4493 gtk_text_iter_set_line_offset(&iter, 0);
4496 itemized_len = compose_itemized_length(buffer, &iter);
4498 if (!itemized_len) {
4499 itemized_len = compose_left_offset_length(buffer, &iter);
4500 item_continuation = TRUE;
4504 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4506 /* go until paragraph end (empty line) */
4507 while (start || !gtk_text_iter_ends_line(&iter)) {
4508 gchar *scanpos = NULL;
4509 /* parse table - in order of priority */
4511 const gchar *needle; /* token */
4513 /* token search function */
4514 gchar *(*search) (const gchar *haystack,
4515 const gchar *needle);
4516 /* part parsing function */
4517 gboolean (*parse) (const gchar *start,
4518 const gchar *scanpos,
4522 /* part to URI function */
4523 gchar *(*build_uri) (const gchar *bp,
4527 static struct table parser[] = {
4528 {"http://", strcasestr, get_uri_part, make_uri_string},
4529 {"https://", strcasestr, get_uri_part, make_uri_string},
4530 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4531 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4532 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4533 {"www.", strcasestr, get_uri_part, make_http_string},
4534 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4535 {"@", strcasestr, get_email_part, make_email_string}
4537 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4538 gint last_index = PARSE_ELEMS;
4540 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4544 if (!prev_autowrap && num_blocks == 0) {
4546 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4547 G_CALLBACK(text_inserted),
4550 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4553 uri_start = uri_stop = -1;
4555 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4558 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4559 if (startq_offset == -1)
4560 startq_offset = gtk_text_iter_get_offset(&iter);
4561 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4562 if (quotelevel > 2) {
4563 /* recycle colors */
4564 if (prefs_common.recycle_quote_colors)
4573 if (startq_offset == -1)
4574 noq_offset = gtk_text_iter_get_offset(&iter);
4578 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4581 if (gtk_text_iter_ends_line(&iter)) {
4583 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4584 prefs_common.linewrap_len,
4586 GtkTextIter prev, next, cur;
4587 if (prev_autowrap != FALSE || force) {
4588 compose->automatic_break = TRUE;
4590 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4591 compose->automatic_break = FALSE;
4592 if (itemized_len && compose->autoindent) {
4593 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4594 if (!item_continuation)
4595 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4597 } else if (quote_str && wrap_quote) {
4598 compose->automatic_break = TRUE;
4600 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4601 compose->automatic_break = FALSE;
4602 if (itemized_len && compose->autoindent) {
4603 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4604 if (!item_continuation)
4605 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4609 /* remove trailing spaces */
4611 rem_item_len = itemized_len;
4612 while (compose->autoindent && rem_item_len-- > 0)
4613 gtk_text_iter_backward_char(&cur);
4614 gtk_text_iter_backward_char(&cur);
4617 while (!gtk_text_iter_starts_line(&cur)) {
4620 gtk_text_iter_backward_char(&cur);
4621 wc = gtk_text_iter_get_char(&cur);
4622 if (!g_unichar_isspace(wc))
4626 if (!gtk_text_iter_equal(&prev, &next)) {
4627 gtk_text_buffer_delete(buffer, &prev, &next);
4629 gtk_text_iter_forward_char(&break_pos);
4633 gtk_text_buffer_insert(buffer, &break_pos,
4637 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4639 /* move iter to current line start */
4640 gtk_text_iter_set_line_offset(&iter, 0);
4647 /* move iter to next line start */
4653 if (!prev_autowrap && num_blocks > 0) {
4655 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4656 G_CALLBACK(text_inserted),
4660 while (!gtk_text_iter_ends_line(&end_of_line)) {
4661 gtk_text_iter_forward_char(&end_of_line);
4663 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4665 nouri_start = gtk_text_iter_get_offset(&iter);
4666 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4668 walk_pos = gtk_text_iter_get_offset(&iter);
4669 /* FIXME: this looks phony. scanning for anything in the parse table */
4670 for (n = 0; n < PARSE_ELEMS; n++) {
4673 tmp = parser[n].search(walk, parser[n].needle);
4675 if (scanpos == NULL || tmp < scanpos) {
4684 /* check if URI can be parsed */
4685 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4686 (const gchar **)&ep, FALSE)
4687 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4691 strlen(parser[last_index].needle);
4694 uri_start = walk_pos + (bp - o_walk);
4695 uri_stop = walk_pos + (ep - o_walk);
4699 gtk_text_iter_forward_line(&iter);
4702 if (startq_offset != -1) {
4703 GtkTextIter startquote, endquote;
4704 gtk_text_buffer_get_iter_at_offset(
4705 buffer, &startquote, startq_offset);
4708 switch (quotelevel) {
4710 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4711 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4712 gtk_text_buffer_apply_tag_by_name(
4713 buffer, "quote0", &startquote, &endquote);
4714 gtk_text_buffer_remove_tag_by_name(
4715 buffer, "quote1", &startquote, &endquote);
4716 gtk_text_buffer_remove_tag_by_name(
4717 buffer, "quote2", &startquote, &endquote);
4722 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4723 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4724 gtk_text_buffer_apply_tag_by_name(
4725 buffer, "quote1", &startquote, &endquote);
4726 gtk_text_buffer_remove_tag_by_name(
4727 buffer, "quote0", &startquote, &endquote);
4728 gtk_text_buffer_remove_tag_by_name(
4729 buffer, "quote2", &startquote, &endquote);
4734 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4735 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4736 gtk_text_buffer_apply_tag_by_name(
4737 buffer, "quote2", &startquote, &endquote);
4738 gtk_text_buffer_remove_tag_by_name(
4739 buffer, "quote0", &startquote, &endquote);
4740 gtk_text_buffer_remove_tag_by_name(
4741 buffer, "quote1", &startquote, &endquote);
4747 } else if (noq_offset != -1) {
4748 GtkTextIter startnoquote, endnoquote;
4749 gtk_text_buffer_get_iter_at_offset(
4750 buffer, &startnoquote, noq_offset);
4753 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4754 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4755 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4756 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4757 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4758 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4759 gtk_text_buffer_remove_tag_by_name(
4760 buffer, "quote0", &startnoquote, &endnoquote);
4761 gtk_text_buffer_remove_tag_by_name(
4762 buffer, "quote1", &startnoquote, &endnoquote);
4763 gtk_text_buffer_remove_tag_by_name(
4764 buffer, "quote2", &startnoquote, &endnoquote);
4770 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4771 GtkTextIter nouri_start_iter, nouri_end_iter;
4772 gtk_text_buffer_get_iter_at_offset(
4773 buffer, &nouri_start_iter, nouri_start);
4774 gtk_text_buffer_get_iter_at_offset(
4775 buffer, &nouri_end_iter, nouri_stop);
4776 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4777 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4778 gtk_text_buffer_remove_tag_by_name(
4779 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4780 modified_before_remove = modified;
4785 if (uri_start >= 0 && uri_stop > 0) {
4786 GtkTextIter uri_start_iter, uri_end_iter, back;
4787 gtk_text_buffer_get_iter_at_offset(
4788 buffer, &uri_start_iter, uri_start);
4789 gtk_text_buffer_get_iter_at_offset(
4790 buffer, &uri_end_iter, uri_stop);
4791 back = uri_end_iter;
4792 gtk_text_iter_backward_char(&back);
4793 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4794 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4795 gtk_text_buffer_apply_tag_by_name(
4796 buffer, "link", &uri_start_iter, &uri_end_iter);
4798 if (removed && !modified_before_remove) {
4804 /* debug_print("not modified, out after %d lines\n", lines); */
4808 /* debug_print("modified, out after %d lines\n", lines); */
4810 g_free(itemized_chars);
4813 undo_wrapping(compose->undostruct, FALSE);
4814 compose->autowrap = prev_autowrap;
4819 void compose_action_cb(void *data)
4821 Compose *compose = (Compose *)data;
4822 compose_wrap_all(compose);
4825 static void compose_wrap_all(Compose *compose)
4827 compose_wrap_all_full(compose, FALSE);
4830 static void compose_wrap_all_full(Compose *compose, gboolean force)
4832 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4833 GtkTextBuffer *buffer;
4835 gboolean modified = TRUE;
4837 buffer = gtk_text_view_get_buffer(text);
4839 gtk_text_buffer_get_start_iter(buffer, &iter);
4841 undo_wrapping(compose->undostruct, TRUE);
4843 while (!gtk_text_iter_is_end(&iter) && modified)
4844 modified = compose_beautify_paragraph(compose, &iter, force);
4846 undo_wrapping(compose->undostruct, FALSE);
4850 static void compose_set_title(Compose *compose)
4856 edited = compose->modified ? _(" [Edited]") : "";
4858 subject = gtk_editable_get_chars(
4859 GTK_EDITABLE(compose->subject_entry), 0, -1);
4861 #ifndef GENERIC_UMPC
4862 if (subject && strlen(subject))
4863 str = g_strdup_printf(_("%s - Compose message%s"),
4866 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4868 str = g_strdup(_("Compose message"));
4871 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4877 * compose_current_mail_account:
4879 * Find a current mail account (the currently selected account, or the
4880 * default account, if a news account is currently selected). If a
4881 * mail account cannot be found, display an error message.
4883 * Return value: Mail account, or NULL if not found.
4885 static PrefsAccount *
4886 compose_current_mail_account(void)
4890 if (cur_account && cur_account->protocol != A_NNTP)
4893 ac = account_get_default();
4894 if (!ac || ac->protocol == A_NNTP) {
4895 alertpanel_error(_("Account for sending mail is not specified.\n"
4896 "Please select a mail account before sending."));
4903 #define QUOTE_IF_REQUIRED(out, str) \
4905 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4909 len = strlen(str) + 3; \
4910 if ((__tmp = alloca(len)) == NULL) { \
4911 g_warning("can't allocate memory"); \
4912 g_string_free(header, TRUE); \
4915 g_snprintf(__tmp, len, "\"%s\"", str); \
4920 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4921 g_warning("can't allocate memory"); \
4922 g_string_free(header, TRUE); \
4925 strcpy(__tmp, str); \
4931 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4933 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4937 len = strlen(str) + 3; \
4938 if ((__tmp = alloca(len)) == NULL) { \
4939 g_warning("can't allocate memory"); \
4942 g_snprintf(__tmp, len, "\"%s\"", str); \
4947 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4948 g_warning("can't allocate memory"); \
4951 strcpy(__tmp, str); \
4957 static void compose_select_account(Compose *compose, PrefsAccount *account,
4960 gchar *from = NULL, *header = NULL;
4961 ComposeHeaderEntry *header_entry;
4962 #if GTK_CHECK_VERSION(2, 24, 0)
4966 cm_return_if_fail(account != NULL);
4968 compose->account = account;
4969 if (account->name && *account->name) {
4971 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4972 qbuf = escape_internal_quotes(buf, '"');
4973 from = g_strdup_printf("%s <%s>",
4974 qbuf, account->address);
4977 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4979 from = g_strdup_printf("<%s>",
4981 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4986 compose_set_title(compose);
4988 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4989 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4991 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4992 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4993 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4995 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4997 activate_privacy_system(compose, account, FALSE);
4999 if (!init && compose->mode != COMPOSE_REDIRECT) {
5000 undo_block(compose->undostruct);
5001 compose_insert_sig(compose, TRUE);
5002 undo_unblock(compose->undostruct);
5005 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5006 #if !GTK_CHECK_VERSION(2, 24, 0)
5007 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
5009 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5010 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5011 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5014 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5015 if (account->protocol == A_NNTP) {
5016 if (!strcmp(header, _("To:")))
5017 combobox_select_by_text(
5018 GTK_COMBO_BOX(header_entry->combo),
5021 if (!strcmp(header, _("Newsgroups:")))
5022 combobox_select_by_text(
5023 GTK_COMBO_BOX(header_entry->combo),
5031 /* use account's dict info if set */
5032 if (compose->gtkaspell) {
5033 if (account->enable_default_dictionary)
5034 gtkaspell_change_dict(compose->gtkaspell,
5035 account->default_dictionary, FALSE);
5036 if (account->enable_default_alt_dictionary)
5037 gtkaspell_change_alt_dict(compose->gtkaspell,
5038 account->default_alt_dictionary);
5039 if (account->enable_default_dictionary
5040 || account->enable_default_alt_dictionary)
5041 compose_spell_menu_changed(compose);
5046 gboolean compose_check_for_valid_recipient(Compose *compose) {
5047 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5048 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5049 gboolean recipient_found = FALSE;
5053 /* free to and newsgroup list */
5054 slist_free_strings_full(compose->to_list);
5055 compose->to_list = NULL;
5057 slist_free_strings_full(compose->newsgroup_list);
5058 compose->newsgroup_list = NULL;
5060 /* search header entries for to and newsgroup entries */
5061 for (list = compose->header_list; list; list = list->next) {
5064 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5065 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5068 if (entry[0] != '\0') {
5069 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5070 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5071 compose->to_list = address_list_append(compose->to_list, entry);
5072 recipient_found = TRUE;
5075 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5076 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5077 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5078 recipient_found = TRUE;
5085 return recipient_found;
5088 static gboolean compose_check_for_set_recipients(Compose *compose)
5090 if (compose->account->set_autocc && compose->account->auto_cc) {
5091 gboolean found_other = FALSE;
5093 /* search header entries for to and newsgroup entries */
5094 for (list = compose->header_list; list; list = list->next) {
5097 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5098 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5101 if (strcmp(entry, compose->account->auto_cc)
5102 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5113 if (compose->batch) {
5114 gtk_widget_show_all(compose->window);
5116 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5117 prefs_common_translated_header_name("Cc"));
5118 aval = alertpanel(_("Send"),
5120 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5122 if (aval != G_ALERTALTERNATE)
5126 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5127 gboolean found_other = FALSE;
5129 /* search header entries for to and newsgroup entries */
5130 for (list = compose->header_list; list; list = list->next) {
5133 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5134 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5137 if (strcmp(entry, compose->account->auto_bcc)
5138 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5150 if (compose->batch) {
5151 gtk_widget_show_all(compose->window);
5153 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5154 prefs_common_translated_header_name("Bcc"));
5155 aval = alertpanel(_("Send"),
5157 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5159 if (aval != G_ALERTALTERNATE)
5166 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5170 if (compose_check_for_valid_recipient(compose) == FALSE) {
5171 if (compose->batch) {
5172 gtk_widget_show_all(compose->window);
5174 alertpanel_error(_("Recipient is not specified."));
5178 if (compose_check_for_set_recipients(compose) == FALSE) {
5182 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5183 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5184 if (*str == '\0' && check_everything == TRUE &&
5185 compose->mode != COMPOSE_REDIRECT) {
5187 gchar *button_label;
5190 if (compose->sending)
5191 button_label = g_strconcat("+", _("_Send"), NULL);
5193 button_label = g_strconcat("+", _("_Queue"), NULL);
5194 message = g_strdup_printf(_("Subject is empty. %s"),
5195 compose->sending?_("Send it anyway?"):
5196 _("Queue it anyway?"));
5198 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5199 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5200 ALERT_QUESTION, G_ALERTDEFAULT);
5202 g_free(button_label);
5203 if (aval & G_ALERTDISABLE) {
5204 aval &= ~G_ALERTDISABLE;
5205 prefs_common.warn_empty_subj = FALSE;
5207 if (aval != G_ALERTALTERNATE)
5212 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5213 && check_everything == TRUE) {
5217 /* count To and Cc recipients */
5218 for (list = compose->header_list; list; list = list->next) {
5222 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5223 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5226 if ((entry[0] != '\0') &&
5227 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5228 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5234 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5236 gchar *button_label;
5239 if (compose->sending)
5240 button_label = g_strconcat("+", _("_Send"), NULL);
5242 button_label = g_strconcat("+", _("_Queue"), NULL);
5243 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5244 compose->sending?_("Send it anyway?"):
5245 _("Queue it anyway?"));
5247 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5248 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5249 ALERT_QUESTION, G_ALERTDEFAULT);
5251 if (aval & G_ALERTDISABLE) {
5252 aval &= ~G_ALERTDISABLE;
5253 prefs_common.warn_sending_many_recipients_num = 0;
5255 if (aval != G_ALERTALTERNATE)
5260 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5266 gint compose_send(Compose *compose)
5269 FolderItem *folder = NULL;
5271 gchar *msgpath = NULL;
5272 gboolean discard_window = FALSE;
5273 gchar *errstr = NULL;
5274 gchar *tmsgid = NULL;
5275 MainWindow *mainwin = mainwindow_get_mainwindow();
5276 gboolean queued_removed = FALSE;
5278 if (prefs_common.send_dialog_invisible
5279 || compose->batch == TRUE)
5280 discard_window = TRUE;
5282 compose_allow_user_actions (compose, FALSE);
5283 compose->sending = TRUE;
5285 if (compose_check_entries(compose, TRUE) == FALSE) {
5286 if (compose->batch) {
5287 gtk_widget_show_all(compose->window);
5293 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5296 if (compose->batch) {
5297 gtk_widget_show_all(compose->window);
5300 alertpanel_error(_("Could not queue message for sending:\n\n"
5301 "Charset conversion failed."));
5302 } else if (val == -5) {
5303 alertpanel_error(_("Could not queue message for sending:\n\n"
5304 "Couldn't get recipient encryption key."));
5305 } else if (val == -6) {
5307 } else if (val == -3) {
5308 if (privacy_peek_error())
5309 alertpanel_error(_("Could not queue message for sending:\n\n"
5310 "Signature failed: %s"), privacy_get_error());
5311 } else if (val == -2 && errno != 0) {
5312 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5314 alertpanel_error(_("Could not queue message for sending."));
5319 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5320 if (discard_window) {
5321 compose->sending = FALSE;
5322 compose_close(compose);
5323 /* No more compose access in the normal codepath
5324 * after this point! */
5329 alertpanel_error(_("The message was queued but could not be "
5330 "sent.\nUse \"Send queued messages\" from "
5331 "the main window to retry."));
5332 if (!discard_window) {
5339 if (msgpath == NULL) {
5340 msgpath = folder_item_fetch_msg(folder, msgnum);
5341 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5344 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5345 claws_unlink(msgpath);
5348 if (!discard_window) {
5350 if (!queued_removed)
5351 folder_item_remove_msg(folder, msgnum);
5352 folder_item_scan(folder);
5354 /* make sure we delete that */
5355 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5357 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5358 folder_item_remove_msg(folder, tmp->msgnum);
5359 procmsg_msginfo_free(&tmp);
5366 if (!queued_removed)
5367 folder_item_remove_msg(folder, msgnum);
5368 folder_item_scan(folder);
5370 /* make sure we delete that */
5371 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5373 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5374 folder_item_remove_msg(folder, tmp->msgnum);
5375 procmsg_msginfo_free(&tmp);
5378 if (!discard_window) {
5379 compose->sending = FALSE;
5380 compose_allow_user_actions (compose, TRUE);
5381 compose_close(compose);
5385 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5386 "the main window to retry."), errstr);
5389 alertpanel_error_log(_("The message was queued but could not be "
5390 "sent.\nUse \"Send queued messages\" from "
5391 "the main window to retry."));
5393 if (!discard_window) {
5402 toolbar_main_set_sensitive(mainwin);
5403 main_window_set_menu_sensitive(mainwin);
5409 compose_allow_user_actions (compose, TRUE);
5410 compose->sending = FALSE;
5411 compose->modified = TRUE;
5412 toolbar_main_set_sensitive(mainwin);
5413 main_window_set_menu_sensitive(mainwin);
5418 static gboolean compose_use_attach(Compose *compose)
5420 GtkTreeModel *model = gtk_tree_view_get_model
5421 (GTK_TREE_VIEW(compose->attach_clist));
5422 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5425 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5428 gchar buf[BUFFSIZE];
5430 gboolean first_to_address;
5431 gboolean first_cc_address;
5433 ComposeHeaderEntry *headerentry;
5434 const gchar *headerentryname;
5435 const gchar *cc_hdr;
5436 const gchar *to_hdr;
5437 gboolean err = FALSE;
5439 debug_print("Writing redirect header\n");
5441 cc_hdr = prefs_common_translated_header_name("Cc:");
5442 to_hdr = prefs_common_translated_header_name("To:");
5444 first_to_address = TRUE;
5445 for (list = compose->header_list; list; list = list->next) {
5446 headerentry = ((ComposeHeaderEntry *)list->data);
5447 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5449 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5450 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5451 Xstrdup_a(str, entstr, return -1);
5453 if (str[0] != '\0') {
5454 compose_convert_header
5455 (compose, buf, sizeof(buf), str,
5456 strlen("Resent-To") + 2, TRUE);
5458 if (first_to_address) {
5459 err |= (fprintf(fp, "Resent-To: ") < 0);
5460 first_to_address = FALSE;
5462 err |= (fprintf(fp, ",") < 0);
5464 err |= (fprintf(fp, "%s", buf) < 0);
5468 if (!first_to_address) {
5469 err |= (fprintf(fp, "\n") < 0);
5472 first_cc_address = TRUE;
5473 for (list = compose->header_list; list; list = list->next) {
5474 headerentry = ((ComposeHeaderEntry *)list->data);
5475 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5477 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5478 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5479 Xstrdup_a(str, strg, return -1);
5481 if (str[0] != '\0') {
5482 compose_convert_header
5483 (compose, buf, sizeof(buf), str,
5484 strlen("Resent-Cc") + 2, TRUE);
5486 if (first_cc_address) {
5487 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5488 first_cc_address = FALSE;
5490 err |= (fprintf(fp, ",") < 0);
5492 err |= (fprintf(fp, "%s", buf) < 0);
5496 if (!first_cc_address) {
5497 err |= (fprintf(fp, "\n") < 0);
5500 return (err ? -1:0);
5503 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5505 gchar date[RFC822_DATE_BUFFSIZE];
5506 gchar buf[BUFFSIZE];
5508 const gchar *entstr;
5509 /* struct utsname utsbuf; */
5510 gboolean err = FALSE;
5512 cm_return_val_if_fail(fp != NULL, -1);
5513 cm_return_val_if_fail(compose->account != NULL, -1);
5514 cm_return_val_if_fail(compose->account->address != NULL, -1);
5517 if (prefs_common.hide_timezone)
5518 get_rfc822_date_hide_tz(date, sizeof(date));
5520 get_rfc822_date(date, sizeof(date));
5521 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5524 if (compose->account->name && *compose->account->name) {
5525 compose_convert_header
5526 (compose, buf, sizeof(buf), compose->account->name,
5527 strlen("From: "), TRUE);
5528 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5529 buf, compose->account->address) < 0);
5531 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5534 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5535 if (*entstr != '\0') {
5536 Xstrdup_a(str, entstr, return -1);
5539 compose_convert_header(compose, buf, sizeof(buf), str,
5540 strlen("Subject: "), FALSE);
5541 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5545 /* Resent-Message-ID */
5546 if (compose->account->gen_msgid) {
5547 gchar *addr = prefs_account_generate_msgid(compose->account);
5548 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5550 g_free(compose->msgid);
5551 compose->msgid = addr;
5553 compose->msgid = NULL;
5556 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5559 /* separator between header and body */
5560 err |= (fputs("\n", fp) == EOF);
5562 return (err ? -1:0);
5565 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5570 gchar rewrite_buf[BUFFSIZE];
5572 gboolean skip = FALSE;
5573 gboolean err = FALSE;
5574 gchar *not_included[]={
5575 "Return-Path:", "Delivered-To:", "Received:",
5576 "Subject:", "X-UIDL:", "AF:",
5577 "NF:", "PS:", "SRH:",
5578 "SFN:", "DSR:", "MID:",
5579 "CFG:", "PT:", "S:",
5580 "RQ:", "SSV:", "NSV:",
5581 "SSH:", "R:", "MAID:",
5582 "NAID:", "RMID:", "FMID:",
5583 "SCF:", "RRCPT:", "NG:",
5584 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5585 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5586 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5587 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5588 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5593 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5594 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5598 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5600 for (i = 0; not_included[i] != NULL; i++) {
5601 if (g_ascii_strncasecmp(buf, not_included[i],
5602 strlen(not_included[i])) == 0) {
5612 if (fputs(buf, fdest) == -1) {
5618 if (!prefs_common.redirect_keep_from) {
5619 if (g_ascii_strncasecmp(buf, "From:",
5620 strlen("From:")) == 0) {
5621 err |= (fputs(" (by way of ", fdest) == EOF);
5622 if (compose->account->name
5623 && *compose->account->name) {
5624 gchar buffer[BUFFSIZE];
5626 compose_convert_header
5627 (compose, buffer, sizeof(buffer),
5628 compose->account->name,
5631 err |= (fprintf(fdest, "%s <%s>",
5633 compose->account->address) < 0);
5635 err |= (fprintf(fdest, "%s",
5636 compose->account->address) < 0);
5637 err |= (fputs(")", fdest) == EOF);
5643 if (fputs("\n", fdest) == -1)
5650 if (compose_redirect_write_headers(compose, fdest))
5653 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5654 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5668 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5670 GtkTextBuffer *buffer;
5671 GtkTextIter start, end, tmp;
5672 gchar *chars, *tmp_enc_file, *content;
5674 const gchar *out_codeset;
5675 EncodingType encoding = ENC_UNKNOWN;
5676 MimeInfo *mimemsg, *mimetext;
5678 const gchar *src_codeset = CS_INTERNAL;
5679 gchar *from_addr = NULL;
5680 gchar *from_name = NULL;
5683 if (action == COMPOSE_WRITE_FOR_SEND) {
5684 attach_parts = TRUE;
5686 /* We're sending the message, generate a Message-ID
5688 if (compose->msgid == NULL &&
5689 compose->account->gen_msgid) {
5690 compose->msgid = prefs_account_generate_msgid(compose->account);
5694 /* create message MimeInfo */
5695 mimemsg = procmime_mimeinfo_new();
5696 mimemsg->type = MIMETYPE_MESSAGE;
5697 mimemsg->subtype = g_strdup("rfc822");
5698 mimemsg->content = MIMECONTENT_MEM;
5699 mimemsg->tmp = TRUE; /* must free content later */
5700 mimemsg->data.mem = compose_get_header(compose);
5702 /* Create text part MimeInfo */
5703 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5704 gtk_text_buffer_get_end_iter(buffer, &end);
5707 /* We make sure that there is a newline at the end. */
5708 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5709 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5710 if (*chars != '\n') {
5711 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5716 /* get all composed text */
5717 gtk_text_buffer_get_start_iter(buffer, &start);
5718 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5720 out_codeset = conv_get_charset_str(compose->out_encoding);
5722 if (!out_codeset && is_ascii_str(chars)) {
5723 out_codeset = CS_US_ASCII;
5724 } else if (prefs_common.outgoing_fallback_to_ascii &&
5725 is_ascii_str(chars)) {
5726 out_codeset = CS_US_ASCII;
5727 encoding = ENC_7BIT;
5731 gchar *test_conv_global_out = NULL;
5732 gchar *test_conv_reply = NULL;
5734 /* automatic mode. be automatic. */
5735 codeconv_set_strict(TRUE);
5737 out_codeset = conv_get_outgoing_charset_str();
5739 debug_print("trying to convert to %s\n", out_codeset);
5740 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5743 if (!test_conv_global_out && compose->orig_charset
5744 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5745 out_codeset = compose->orig_charset;
5746 debug_print("failure; trying to convert to %s\n", out_codeset);
5747 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5750 if (!test_conv_global_out && !test_conv_reply) {
5752 out_codeset = CS_INTERNAL;
5753 debug_print("failure; finally using %s\n", out_codeset);
5755 g_free(test_conv_global_out);
5756 g_free(test_conv_reply);
5757 codeconv_set_strict(FALSE);
5760 if (encoding == ENC_UNKNOWN) {
5761 if (prefs_common.encoding_method == CTE_BASE64)
5762 encoding = ENC_BASE64;
5763 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5764 encoding = ENC_QUOTED_PRINTABLE;
5765 else if (prefs_common.encoding_method == CTE_8BIT)
5766 encoding = ENC_8BIT;
5768 encoding = procmime_get_encoding_for_charset(out_codeset);
5771 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5772 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5774 if (action == COMPOSE_WRITE_FOR_SEND) {
5775 codeconv_set_strict(TRUE);
5776 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5777 codeconv_set_strict(FALSE);
5782 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5783 "to the specified %s charset.\n"
5784 "Send it as %s?"), out_codeset, src_codeset);
5785 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5786 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5787 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5790 if (aval != G_ALERTALTERNATE) {
5795 out_codeset = src_codeset;
5801 out_codeset = src_codeset;
5806 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5807 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5808 strstr(buf, "\nFrom ") != NULL) {
5809 encoding = ENC_QUOTED_PRINTABLE;
5813 mimetext = procmime_mimeinfo_new();
5814 mimetext->content = MIMECONTENT_MEM;
5815 mimetext->tmp = TRUE; /* must free content later */
5816 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5817 * and free the data, which we need later. */
5818 mimetext->data.mem = g_strdup(buf);
5819 mimetext->type = MIMETYPE_TEXT;
5820 mimetext->subtype = g_strdup("plain");
5821 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5822 g_strdup(out_codeset));
5824 /* protect trailing spaces when signing message */
5825 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5826 privacy_system_can_sign(compose->privacy_system)) {
5827 encoding = ENC_QUOTED_PRINTABLE;
5831 debug_print("main text: %Id bytes encoded as %s in %d\n",
5833 debug_print("main text: %zd bytes encoded as %s in %d\n",
5835 strlen(buf), out_codeset, encoding);
5837 /* check for line length limit */
5838 if (action == COMPOSE_WRITE_FOR_SEND &&
5839 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5840 check_line_length(buf, 1000, &line) < 0) {
5843 msg = g_strdup_printf
5844 (_("Line %d exceeds the line length limit (998 bytes).\n"
5845 "The contents of the message might be broken on the way to the delivery.\n"
5847 "Send it anyway?"), line + 1);
5848 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5850 if (aval != G_ALERTALTERNATE) {
5856 if (encoding != ENC_UNKNOWN)
5857 procmime_encode_content(mimetext, encoding);
5859 /* append attachment parts */
5860 if (compose_use_attach(compose) && attach_parts) {
5861 MimeInfo *mimempart;
5862 gchar *boundary = NULL;
5863 mimempart = procmime_mimeinfo_new();
5864 mimempart->content = MIMECONTENT_EMPTY;
5865 mimempart->type = MIMETYPE_MULTIPART;
5866 mimempart->subtype = g_strdup("mixed");
5870 boundary = generate_mime_boundary(NULL);
5871 } while (strstr(buf, boundary) != NULL);
5873 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5876 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5878 g_node_append(mimempart->node, mimetext->node);
5879 g_node_append(mimemsg->node, mimempart->node);
5881 if (compose_add_attachments(compose, mimempart) < 0)
5884 g_node_append(mimemsg->node, mimetext->node);
5888 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5889 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5890 /* extract name and address */
5891 if (strstr(spec, " <") && strstr(spec, ">")) {
5892 from_addr = g_strdup(strrchr(spec, '<')+1);
5893 *(strrchr(from_addr, '>')) = '\0';
5894 from_name = g_strdup(spec);
5895 *(strrchr(from_name, '<')) = '\0';
5902 /* sign message if sending */
5903 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5904 privacy_system_can_sign(compose->privacy_system))
5905 if (!privacy_sign(compose->privacy_system, mimemsg,
5906 compose->account, from_addr)) {
5914 if (compose->use_encryption) {
5915 if (compose->encdata != NULL &&
5916 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5918 /* First, write an unencrypted copy and save it to outbox, if
5919 * user wants that. */
5920 if (compose->account->save_encrypted_as_clear_text) {
5921 debug_print("saving sent message unencrypted...\n");
5922 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5926 /* fp now points to a file with headers written,
5927 * let's make a copy. */
5929 content = file_read_stream_to_str(fp);
5931 str_write_to_file(content, tmp_enc_file);
5934 /* Now write the unencrypted body. */
5935 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5936 procmime_write_mimeinfo(mimemsg, tmpfp);
5939 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5941 outbox = folder_get_default_outbox();
5943 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5944 claws_unlink(tmp_enc_file);
5946 g_warning("Can't open file '%s'", tmp_enc_file);
5949 g_warning("couldn't get tempfile");
5952 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5953 debug_print("Couldn't encrypt mime structure: %s.\n",
5954 privacy_get_error());
5955 alertpanel_error(_("Couldn't encrypt the email: %s"),
5956 privacy_get_error());
5961 procmime_write_mimeinfo(mimemsg, fp);
5963 procmime_mimeinfo_free_all(&mimemsg);
5968 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5970 GtkTextBuffer *buffer;
5971 GtkTextIter start, end;
5976 if ((fp = g_fopen(file, "wb")) == NULL) {
5977 FILE_OP_ERROR(file, "fopen");
5981 /* chmod for security */
5982 if (change_file_mode_rw(fp, file) < 0) {
5983 FILE_OP_ERROR(file, "chmod");
5984 g_warning("can't change file mode");
5987 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5988 gtk_text_buffer_get_start_iter(buffer, &start);
5989 gtk_text_buffer_get_end_iter(buffer, &end);
5990 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5992 chars = conv_codeset_strdup
5993 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6002 len = strlen(chars);
6003 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
6004 FILE_OP_ERROR(file, "fwrite");
6013 if (fclose(fp) == EOF) {
6014 FILE_OP_ERROR(file, "fclose");
6021 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6024 MsgInfo *msginfo = compose->targetinfo;
6026 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6027 if (!msginfo) return -1;
6029 if (!force && MSG_IS_LOCKED(msginfo->flags))
6032 item = msginfo->folder;
6033 cm_return_val_if_fail(item != NULL, -1);
6035 if (procmsg_msg_exist(msginfo) &&
6036 (folder_has_parent_of_type(item, F_QUEUE) ||
6037 folder_has_parent_of_type(item, F_DRAFT)
6038 || msginfo == compose->autosaved_draft)) {
6039 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6040 g_warning("can't remove the old message");
6043 debug_print("removed reedit target %d\n", msginfo->msgnum);
6050 static void compose_remove_draft(Compose *compose)
6053 MsgInfo *msginfo = compose->targetinfo;
6054 drafts = account_get_special_folder(compose->account, F_DRAFT);
6056 if (procmsg_msg_exist(msginfo)) {
6057 folder_item_remove_msg(drafts, msginfo->msgnum);
6062 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6063 gboolean remove_reedit_target)
6065 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6068 static gboolean compose_warn_encryption(Compose *compose)
6070 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6071 AlertValue val = G_ALERTALTERNATE;
6073 if (warning == NULL)
6076 val = alertpanel_full(_("Encryption warning"), warning,
6077 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6078 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6079 if (val & G_ALERTDISABLE) {
6080 val &= ~G_ALERTDISABLE;
6081 if (val == G_ALERTALTERNATE)
6082 privacy_inhibit_encrypt_warning(compose->privacy_system,
6086 if (val == G_ALERTALTERNATE) {
6093 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6094 gchar **msgpath, gboolean perform_checks,
6095 gboolean remove_reedit_target)
6102 PrefsAccount *mailac = NULL, *newsac = NULL;
6103 gboolean err = FALSE;
6105 debug_print("queueing message...\n");
6106 cm_return_val_if_fail(compose->account != NULL, -1);
6108 if (compose_check_entries(compose, perform_checks) == FALSE) {
6109 if (compose->batch) {
6110 gtk_widget_show_all(compose->window);
6115 if (!compose->to_list && !compose->newsgroup_list) {
6116 g_warning("can't get recipient list.");
6120 if (compose->to_list) {
6121 if (compose->account->protocol != A_NNTP)
6122 mailac = compose->account;
6123 else if (cur_account && cur_account->protocol != A_NNTP)
6124 mailac = cur_account;
6125 else if (!(mailac = compose_current_mail_account())) {
6126 alertpanel_error(_("No account for sending mails available!"));
6131 if (compose->newsgroup_list) {
6132 if (compose->account->protocol == A_NNTP)
6133 newsac = compose->account;
6135 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6140 /* write queue header */
6141 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6142 G_DIR_SEPARATOR, compose, (guint) rand());
6143 debug_print("queuing to %s\n", tmp);
6144 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6145 FILE_OP_ERROR(tmp, "fopen");
6150 if (change_file_mode_rw(fp, tmp) < 0) {
6151 FILE_OP_ERROR(tmp, "chmod");
6152 g_warning("can't change file mode");
6155 /* queueing variables */
6156 err |= (fprintf(fp, "AF:\n") < 0);
6157 err |= (fprintf(fp, "NF:0\n") < 0);
6158 err |= (fprintf(fp, "PS:10\n") < 0);
6159 err |= (fprintf(fp, "SRH:1\n") < 0);
6160 err |= (fprintf(fp, "SFN:\n") < 0);
6161 err |= (fprintf(fp, "DSR:\n") < 0);
6163 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6165 err |= (fprintf(fp, "MID:\n") < 0);
6166 err |= (fprintf(fp, "CFG:\n") < 0);
6167 err |= (fprintf(fp, "PT:0\n") < 0);
6168 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6169 err |= (fprintf(fp, "RQ:\n") < 0);
6171 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6173 err |= (fprintf(fp, "SSV:\n") < 0);
6175 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6177 err |= (fprintf(fp, "NSV:\n") < 0);
6178 err |= (fprintf(fp, "SSH:\n") < 0);
6179 /* write recepient list */
6180 if (compose->to_list) {
6181 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6182 for (cur = compose->to_list->next; cur != NULL;
6184 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6185 err |= (fprintf(fp, "\n") < 0);
6187 /* write newsgroup list */
6188 if (compose->newsgroup_list) {
6189 err |= (fprintf(fp, "NG:") < 0);
6190 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6191 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6192 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6193 err |= (fprintf(fp, "\n") < 0);
6195 /* Sylpheed account IDs */
6197 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6199 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6202 if (compose->privacy_system != NULL) {
6203 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6204 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6205 if (compose->use_encryption) {
6206 if (!compose_warn_encryption(compose)) {
6212 if (mailac && mailac->encrypt_to_self) {
6213 GSList *tmp_list = g_slist_copy(compose->to_list);
6214 tmp_list = g_slist_append(tmp_list, compose->account->address);
6215 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6216 g_slist_free(tmp_list);
6218 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6220 if (compose->encdata != NULL) {
6221 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6222 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6223 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6224 compose->encdata) < 0);
6225 } /* else we finally dont want to encrypt */
6227 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6228 /* and if encdata was null, it means there's been a problem in
6231 g_warning("failed to write queue message");
6240 /* Save copy folder */
6241 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6242 gchar *savefolderid;
6244 savefolderid = compose_get_save_to(compose);
6245 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6246 g_free(savefolderid);
6248 /* Save copy folder */
6249 if (compose->return_receipt) {
6250 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6252 /* Message-ID of message replying to */
6253 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6254 gchar *folderid = NULL;
6256 if (compose->replyinfo->folder)
6257 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6258 if (folderid == NULL)
6259 folderid = g_strdup("NULL");
6261 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6264 /* Message-ID of message forwarding to */
6265 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6266 gchar *folderid = NULL;
6268 if (compose->fwdinfo->folder)
6269 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6270 if (folderid == NULL)
6271 folderid = g_strdup("NULL");
6273 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6277 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6278 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6280 /* end of headers */
6281 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6283 if (compose->redirect_filename != NULL) {
6284 if (compose_redirect_write_to_file(compose, fp) < 0) {
6292 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6296 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6300 g_warning("failed to write queue message");
6306 if (fclose(fp) == EOF) {
6307 FILE_OP_ERROR(tmp, "fclose");
6313 if (item && *item) {
6316 queue = account_get_special_folder(compose->account, F_QUEUE);
6319 g_warning("can't find queue folder");
6324 folder_item_scan(queue);
6325 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6326 g_warning("can't queue the message");
6332 if (msgpath == NULL) {
6338 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6339 compose_remove_reedit_target(compose, FALSE);
6342 if ((msgnum != NULL) && (item != NULL)) {
6350 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6353 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6358 GError *error = NULL;
6363 gchar *type, *subtype;
6364 GtkTreeModel *model;
6367 model = gtk_tree_view_get_model(tree_view);
6369 if (!gtk_tree_model_get_iter_first(model, &iter))
6372 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6374 if (!is_file_exist(ainfo->file)) {
6375 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6376 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6377 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6379 if (val == G_ALERTDEFAULT) {
6385 f = g_file_new_for_path(ainfo->file);
6386 fi = g_file_query_info(f, "standard::size",
6387 G_FILE_QUERY_INFO_NONE, NULL, &error);
6388 if (error != NULL) {
6389 g_warning(error->message);
6390 g_error_free(error);
6394 size = g_file_info_get_size(fi);
6398 if (g_stat(ainfo->file, &statbuf) < 0)
6400 size = statbuf.st_size;
6403 mimepart = procmime_mimeinfo_new();
6404 mimepart->content = MIMECONTENT_FILE;
6405 mimepart->data.filename = g_strdup(ainfo->file);
6406 mimepart->tmp = FALSE; /* or we destroy our attachment */
6407 mimepart->offset = 0;
6408 mimepart->length = size;
6410 type = g_strdup(ainfo->content_type);
6412 if (!strchr(type, '/')) {
6414 type = g_strdup("application/octet-stream");
6417 subtype = strchr(type, '/') + 1;
6418 *(subtype - 1) = '\0';
6419 mimepart->type = procmime_get_media_type(type);
6420 mimepart->subtype = g_strdup(subtype);
6423 if (mimepart->type == MIMETYPE_MESSAGE &&
6424 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6425 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6426 } else if (mimepart->type == MIMETYPE_TEXT) {
6427 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6428 /* Text parts with no name come from multipart/alternative
6429 * forwards. Make sure the recipient won't look at the
6430 * original HTML part by mistake. */
6431 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6432 ainfo->name = g_strdup_printf(_("Original %s part"),
6436 g_hash_table_insert(mimepart->typeparameters,
6437 g_strdup("charset"), g_strdup(ainfo->charset));
6439 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6440 if (mimepart->type == MIMETYPE_APPLICATION &&
6441 !strcmp2(mimepart->subtype, "octet-stream"))
6442 g_hash_table_insert(mimepart->typeparameters,
6443 g_strdup("name"), g_strdup(ainfo->name));
6444 g_hash_table_insert(mimepart->dispositionparameters,
6445 g_strdup("filename"), g_strdup(ainfo->name));
6446 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6449 if (mimepart->type == MIMETYPE_MESSAGE
6450 || mimepart->type == MIMETYPE_MULTIPART)
6451 ainfo->encoding = ENC_BINARY;
6452 else if (compose->use_signing) {
6453 if (ainfo->encoding == ENC_7BIT)
6454 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6455 else if (ainfo->encoding == ENC_8BIT)
6456 ainfo->encoding = ENC_BASE64;
6459 procmime_encode_content(mimepart, ainfo->encoding);
6461 g_node_append(parent->node, mimepart->node);
6462 } while (gtk_tree_model_iter_next(model, &iter));
6467 static gchar *compose_quote_list_of_addresses(gchar *str)
6469 GSList *list = NULL, *item = NULL;
6470 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6472 list = address_list_append_with_comments(list, str);
6473 for (item = list; item != NULL; item = item->next) {
6474 gchar *spec = item->data;
6475 gchar *endofname = strstr(spec, " <");
6476 if (endofname != NULL) {
6479 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6480 qqname = escape_internal_quotes(qname, '"');
6482 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6483 gchar *addr = g_strdup(endofname);
6484 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6485 faddr = g_strconcat(name, addr, NULL);
6488 debug_print("new auto-quoted address: '%s'\n", faddr);
6492 result = g_strdup((faddr != NULL)? faddr: spec);
6494 result = g_strconcat(result,
6496 (faddr != NULL)? faddr: spec,
6499 if (faddr != NULL) {
6504 slist_free_strings_full(list);
6509 #define IS_IN_CUSTOM_HEADER(header) \
6510 (compose->account->add_customhdr && \
6511 custom_header_find(compose->account->customhdr_list, header) != NULL)
6513 static const gchar *compose_untranslated_header_name(gchar *header_name)
6515 /* return the untranslated header name, if header_name is a known
6516 header name, in either its translated or untranslated form, with
6517 or without trailing colon. otherwise, returns header_name. */
6518 gchar *translated_header_name;
6519 gchar *translated_header_name_wcolon;
6520 const gchar *untranslated_header_name;
6521 const gchar *untranslated_header_name_wcolon;
6524 cm_return_val_if_fail(header_name != NULL, NULL);
6526 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6527 untranslated_header_name = HEADERS[i].header_name;
6528 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6530 translated_header_name = gettext(untranslated_header_name);
6531 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6533 if (!strcmp(header_name, untranslated_header_name) ||
6534 !strcmp(header_name, translated_header_name)) {
6535 return untranslated_header_name;
6537 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6538 !strcmp(header_name, translated_header_name_wcolon)) {
6539 return untranslated_header_name_wcolon;
6543 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6547 static void compose_add_headerfield_from_headerlist(Compose *compose,
6549 const gchar *fieldname,
6550 const gchar *seperator)
6552 gchar *str, *fieldname_w_colon;
6553 gboolean add_field = FALSE;
6555 ComposeHeaderEntry *headerentry;
6556 const gchar *headerentryname;
6557 const gchar *trans_fieldname;
6560 if (IS_IN_CUSTOM_HEADER(fieldname))
6563 debug_print("Adding %s-fields\n", fieldname);
6565 fieldstr = g_string_sized_new(64);
6567 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6568 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6570 for (list = compose->header_list; list; list = list->next) {
6571 headerentry = ((ComposeHeaderEntry *)list->data);
6572 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6574 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6575 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6577 str = compose_quote_list_of_addresses(ustr);
6579 if (str != NULL && str[0] != '\0') {
6581 g_string_append(fieldstr, seperator);
6582 g_string_append(fieldstr, str);
6591 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6592 compose_convert_header
6593 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6594 strlen(fieldname) + 2, TRUE);
6595 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6599 g_free(fieldname_w_colon);
6600 g_string_free(fieldstr, TRUE);
6605 static gchar *compose_get_manual_headers_info(Compose *compose)
6607 GString *sh_header = g_string_new(" ");
6609 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6611 for (list = compose->header_list; list; list = list->next) {
6612 ComposeHeaderEntry *headerentry;
6615 gchar *headername_wcolon;
6616 const gchar *headername_trans;
6618 gboolean standard_header = FALSE;
6620 headerentry = ((ComposeHeaderEntry *)list->data);
6622 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6624 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6629 if (!strstr(tmp, ":")) {
6630 headername_wcolon = g_strconcat(tmp, ":", NULL);
6631 headername = g_strdup(tmp);
6633 headername_wcolon = g_strdup(tmp);
6634 headername = g_strdup(strtok(tmp, ":"));
6638 string = std_headers;
6639 while (*string != NULL) {
6640 headername_trans = prefs_common_translated_header_name(*string);
6641 if (!strcmp(headername_trans, headername_wcolon))
6642 standard_header = TRUE;
6645 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6646 g_string_append_printf(sh_header, "%s ", headername);
6648 g_free(headername_wcolon);
6650 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6651 return g_string_free(sh_header, FALSE);
6654 static gchar *compose_get_header(Compose *compose)
6656 gchar date[RFC822_DATE_BUFFSIZE];
6657 gchar buf[BUFFSIZE];
6658 const gchar *entry_str;
6662 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6664 gchar *from_name = NULL, *from_address = NULL;
6667 cm_return_val_if_fail(compose->account != NULL, NULL);
6668 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6670 header = g_string_sized_new(64);
6673 if (prefs_common.hide_timezone)
6674 get_rfc822_date_hide_tz(date, sizeof(date));
6676 get_rfc822_date(date, sizeof(date));
6677 g_string_append_printf(header, "Date: %s\n", date);
6681 if (compose->account->name && *compose->account->name) {
6683 QUOTE_IF_REQUIRED(buf, compose->account->name);
6684 tmp = g_strdup_printf("%s <%s>",
6685 buf, compose->account->address);
6687 tmp = g_strdup_printf("%s",
6688 compose->account->address);
6690 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6691 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6693 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6694 from_address = g_strdup(compose->account->address);
6696 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6697 /* extract name and address */
6698 if (strstr(spec, " <") && strstr(spec, ">")) {
6699 from_address = g_strdup(strrchr(spec, '<')+1);
6700 *(strrchr(from_address, '>')) = '\0';
6701 from_name = g_strdup(spec);
6702 *(strrchr(from_name, '<')) = '\0';
6705 from_address = g_strdup(spec);
6712 if (from_name && *from_name) {
6714 compose_convert_header
6715 (compose, buf, sizeof(buf), from_name,
6716 strlen("From: "), TRUE);
6717 QUOTE_IF_REQUIRED(name, buf);
6718 qname = escape_internal_quotes(name, '"');
6720 g_string_append_printf(header, "From: %s <%s>\n",
6721 qname, from_address);
6722 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6723 compose->return_receipt) {
6724 compose_convert_header(compose, buf, sizeof(buf), from_name,
6725 strlen("Disposition-Notification-To: "),
6727 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6732 g_string_append_printf(header, "From: %s\n", from_address);
6733 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6734 compose->return_receipt)
6735 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6739 g_free(from_address);
6742 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6745 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6748 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6752 * If this account is a NNTP account remove Bcc header from
6753 * message body since it otherwise will be publicly shown
6755 if (compose->account->protocol != A_NNTP)
6756 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6759 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6761 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6764 compose_convert_header(compose, buf, sizeof(buf), str,
6765 strlen("Subject: "), FALSE);
6766 g_string_append_printf(header, "Subject: %s\n", buf);
6772 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6773 g_string_append_printf(header, "Message-ID: <%s>\n",
6777 if (compose->remove_references == FALSE) {
6779 if (compose->inreplyto && compose->to_list)
6780 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6783 if (compose->references)
6784 g_string_append_printf(header, "References: %s\n", compose->references);
6788 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6791 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6794 if (compose->account->organization &&
6795 strlen(compose->account->organization) &&
6796 !IS_IN_CUSTOM_HEADER("Organization")) {
6797 compose_convert_header(compose, buf, sizeof(buf),
6798 compose->account->organization,
6799 strlen("Organization: "), FALSE);
6800 g_string_append_printf(header, "Organization: %s\n", buf);
6803 /* Program version and system info */
6804 if (compose->account->gen_xmailer &&
6805 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6806 !compose->newsgroup_list) {
6807 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6809 gtk_major_version, gtk_minor_version, gtk_micro_version,
6812 if (compose->account->gen_xmailer &&
6813 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6814 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6816 gtk_major_version, gtk_minor_version, gtk_micro_version,
6820 /* custom headers */
6821 if (compose->account->add_customhdr) {
6824 for (cur = compose->account->customhdr_list; cur != NULL;
6826 CustomHeader *chdr = (CustomHeader *)cur->data;
6828 if (custom_header_is_allowed(chdr->name)
6829 && chdr->value != NULL
6830 && *(chdr->value) != '\0') {
6831 compose_convert_header
6832 (compose, buf, sizeof(buf),
6834 strlen(chdr->name) + 2, FALSE);
6835 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6840 /* Automatic Faces and X-Faces */
6841 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6842 g_string_append_printf(header, "X-Face: %s\n", buf);
6844 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6845 g_string_append_printf(header, "X-Face: %s\n", buf);
6847 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6848 g_string_append_printf(header, "Face: %s\n", buf);
6850 else if (get_default_face (buf, sizeof(buf)) == 0) {
6851 g_string_append_printf(header, "Face: %s\n", buf);
6855 switch (compose->priority) {
6856 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6857 "X-Priority: 1 (Highest)\n");
6859 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6860 "X-Priority: 2 (High)\n");
6862 case PRIORITY_NORMAL: break;
6863 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6864 "X-Priority: 4 (Low)\n");
6866 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6867 "X-Priority: 5 (Lowest)\n");
6869 default: debug_print("compose: priority unknown : %d\n",
6873 /* get special headers */
6874 for (list = compose->header_list; list; list = list->next) {
6875 ComposeHeaderEntry *headerentry;
6878 gchar *headername_wcolon;
6879 const gchar *headername_trans;
6882 gboolean standard_header = FALSE;
6884 headerentry = ((ComposeHeaderEntry *)list->data);
6886 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6888 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6893 if (!strstr(tmp, ":")) {
6894 headername_wcolon = g_strconcat(tmp, ":", NULL);
6895 headername = g_strdup(tmp);
6897 headername_wcolon = g_strdup(tmp);
6898 headername = g_strdup(strtok(tmp, ":"));
6902 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6903 Xstrdup_a(headervalue, entry_str, return NULL);
6904 subst_char(headervalue, '\r', ' ');
6905 subst_char(headervalue, '\n', ' ');
6906 g_strstrip(headervalue);
6907 if (*headervalue != '\0') {
6908 string = std_headers;
6909 while (*string != NULL && !standard_header) {
6910 headername_trans = prefs_common_translated_header_name(*string);
6911 /* support mixed translated and untranslated headers */
6912 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6913 standard_header = TRUE;
6916 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6917 /* store untranslated header name */
6918 g_string_append_printf(header, "%s %s\n",
6919 compose_untranslated_header_name(headername_wcolon), headervalue);
6923 g_free(headername_wcolon);
6927 g_string_free(header, FALSE);
6932 #undef IS_IN_CUSTOM_HEADER
6934 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6935 gint header_len, gboolean addr_field)
6937 gchar *tmpstr = NULL;
6938 const gchar *out_codeset = NULL;
6940 cm_return_if_fail(src != NULL);
6941 cm_return_if_fail(dest != NULL);
6943 if (len < 1) return;
6945 tmpstr = g_strdup(src);
6947 subst_char(tmpstr, '\n', ' ');
6948 subst_char(tmpstr, '\r', ' ');
6951 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6952 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6953 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6958 codeconv_set_strict(TRUE);
6959 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6960 conv_get_charset_str(compose->out_encoding));
6961 codeconv_set_strict(FALSE);
6963 if (!dest || *dest == '\0') {
6964 gchar *test_conv_global_out = NULL;
6965 gchar *test_conv_reply = NULL;
6967 /* automatic mode. be automatic. */
6968 codeconv_set_strict(TRUE);
6970 out_codeset = conv_get_outgoing_charset_str();
6972 debug_print("trying to convert to %s\n", out_codeset);
6973 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6976 if (!test_conv_global_out && compose->orig_charset
6977 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6978 out_codeset = compose->orig_charset;
6979 debug_print("failure; trying to convert to %s\n", out_codeset);
6980 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6983 if (!test_conv_global_out && !test_conv_reply) {
6985 out_codeset = CS_INTERNAL;
6986 debug_print("finally using %s\n", out_codeset);
6988 g_free(test_conv_global_out);
6989 g_free(test_conv_reply);
6990 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6992 codeconv_set_strict(FALSE);
6997 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7001 cm_return_if_fail(user_data != NULL);
7003 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7004 g_strstrip(address);
7005 if (*address != '\0') {
7006 gchar *name = procheader_get_fromname(address);
7007 extract_address(address);
7008 #ifndef USE_ALT_ADDRBOOK
7009 addressbook_add_contact(name, address, NULL, NULL);
7011 debug_print("%s: %s\n", name, address);
7012 if (addressadd_selection(name, address, NULL, NULL)) {
7013 debug_print( "addressbook_add_contact - added\n" );
7020 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7022 GtkWidget *menuitem;
7025 cm_return_if_fail(menu != NULL);
7026 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7028 menuitem = gtk_separator_menu_item_new();
7029 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7030 gtk_widget_show(menuitem);
7032 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7033 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7035 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7036 g_strstrip(address);
7037 if (*address == '\0') {
7038 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7041 g_signal_connect(G_OBJECT(menuitem), "activate",
7042 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7043 gtk_widget_show(menuitem);
7046 void compose_add_extra_header(gchar *header, GtkListStore *model)
7049 if (strcmp(header, "")) {
7050 COMBOBOX_ADD(model, header, COMPOSE_TO);
7054 void compose_add_extra_header_entries(GtkListStore *model)
7058 gchar buf[BUFFSIZE];
7061 if (extra_headers == NULL) {
7062 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7063 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7064 debug_print("extra headers file not found\n");
7065 goto extra_headers_done;
7067 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7068 lastc = strlen(buf) - 1; /* remove trailing control chars */
7069 while (lastc >= 0 && buf[lastc] != ':')
7070 buf[lastc--] = '\0';
7071 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7072 buf[lastc] = '\0'; /* remove trailing : for comparison */
7073 if (custom_header_is_allowed(buf)) {
7075 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7078 g_message("disallowed extra header line: %s\n", buf);
7082 g_message("invalid extra header line: %s\n", buf);
7088 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7089 extra_headers = g_slist_reverse(extra_headers);
7091 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7094 static void compose_create_header_entry(Compose *compose)
7096 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7103 const gchar *header = NULL;
7104 ComposeHeaderEntry *headerentry;
7105 gboolean standard_header = FALSE;
7106 GtkListStore *model;
7109 headerentry = g_new0(ComposeHeaderEntry, 1);
7111 /* Combo box model */
7112 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7113 #if !GTK_CHECK_VERSION(2, 24, 0)
7114 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7116 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7118 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7120 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7122 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7123 COMPOSE_NEWSGROUPS);
7124 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7126 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7127 COMPOSE_FOLLOWUPTO);
7128 compose_add_extra_header_entries(model);
7131 #if GTK_CHECK_VERSION(2, 24, 0)
7132 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7133 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7134 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7135 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7136 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7138 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7139 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7140 G_CALLBACK(compose_grab_focus_cb), compose);
7141 gtk_widget_show(combo);
7143 /* Putting only the combobox child into focus chain of its parent causes
7144 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7145 * This eliminates need to pres Tab twice in order to really get from the
7146 * combobox to next widget. */
7148 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7149 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7152 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7153 compose->header_nextrow, compose->header_nextrow+1,
7154 GTK_SHRINK, GTK_FILL, 0, 0);
7155 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7156 const gchar *last_header_entry = gtk_entry_get_text(
7157 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7159 while (*string != NULL) {
7160 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7161 standard_header = TRUE;
7164 if (standard_header)
7165 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7167 if (!compose->header_last || !standard_header) {
7168 switch(compose->account->protocol) {
7170 header = prefs_common_translated_header_name("Newsgroups:");
7173 header = prefs_common_translated_header_name("To:");
7178 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7180 gtk_editable_set_editable(
7181 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7182 prefs_common.type_any_header);
7184 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7185 G_CALLBACK(compose_grab_focus_cb), compose);
7187 /* Entry field with cleanup button */
7188 button = gtk_button_new();
7189 gtk_button_set_image(GTK_BUTTON(button),
7190 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7191 gtk_widget_show(button);
7192 CLAWS_SET_TIP(button,
7193 _("Delete entry contents"));
7194 entry = gtk_entry_new();
7195 gtk_widget_show(entry);
7196 CLAWS_SET_TIP(entry,
7197 _("Use <tab> to autocomplete from addressbook"));
7198 hbox = gtk_hbox_new (FALSE, 0);
7199 gtk_widget_show(hbox);
7200 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7201 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7202 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7203 compose->header_nextrow, compose->header_nextrow+1,
7204 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7206 g_signal_connect(G_OBJECT(entry), "key-press-event",
7207 G_CALLBACK(compose_headerentry_key_press_event_cb),
7209 g_signal_connect(G_OBJECT(entry), "changed",
7210 G_CALLBACK(compose_headerentry_changed_cb),
7212 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7213 G_CALLBACK(compose_grab_focus_cb), compose);
7215 g_signal_connect(G_OBJECT(button), "clicked",
7216 G_CALLBACK(compose_headerentry_button_clicked_cb),
7220 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7221 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7222 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7223 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7224 G_CALLBACK(compose_header_drag_received_cb),
7226 g_signal_connect(G_OBJECT(entry), "drag-drop",
7227 G_CALLBACK(compose_drag_drop),
7229 g_signal_connect(G_OBJECT(entry), "populate-popup",
7230 G_CALLBACK(compose_entry_popup_extend),
7233 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7235 headerentry->compose = compose;
7236 headerentry->combo = combo;
7237 headerentry->entry = entry;
7238 headerentry->button = button;
7239 headerentry->hbox = hbox;
7240 headerentry->headernum = compose->header_nextrow;
7241 headerentry->type = PREF_NONE;
7243 compose->header_nextrow++;
7244 compose->header_last = headerentry;
7245 compose->header_list =
7246 g_slist_append(compose->header_list,
7250 static void compose_add_header_entry(Compose *compose, const gchar *header,
7251 gchar *text, ComposePrefType pref_type)
7253 ComposeHeaderEntry *last_header = compose->header_last;
7254 gchar *tmp = g_strdup(text), *email;
7255 gboolean replyto_hdr;
7257 replyto_hdr = (!strcasecmp(header,
7258 prefs_common_translated_header_name("Reply-To:")) ||
7260 prefs_common_translated_header_name("Followup-To:")) ||
7262 prefs_common_translated_header_name("In-Reply-To:")));
7264 extract_address(tmp);
7265 email = g_utf8_strdown(tmp, -1);
7267 if (replyto_hdr == FALSE &&
7268 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7270 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7271 header, text, (gint) pref_type);
7277 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7278 gtk_entry_set_text(GTK_ENTRY(
7279 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7281 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7282 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7283 last_header->type = pref_type;
7285 if (replyto_hdr == FALSE)
7286 g_hash_table_insert(compose->email_hashtable, email,
7287 GUINT_TO_POINTER(1));
7294 static void compose_destroy_headerentry(Compose *compose,
7295 ComposeHeaderEntry *headerentry)
7297 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7300 extract_address(text);
7301 email = g_utf8_strdown(text, -1);
7302 g_hash_table_remove(compose->email_hashtable, email);
7306 gtk_widget_destroy(headerentry->combo);
7307 gtk_widget_destroy(headerentry->entry);
7308 gtk_widget_destroy(headerentry->button);
7309 gtk_widget_destroy(headerentry->hbox);
7310 g_free(headerentry);
7313 static void compose_remove_header_entries(Compose *compose)
7316 for (list = compose->header_list; list; list = list->next)
7317 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7319 compose->header_last = NULL;
7320 g_slist_free(compose->header_list);
7321 compose->header_list = NULL;
7322 compose->header_nextrow = 1;
7323 compose_create_header_entry(compose);
7326 static GtkWidget *compose_create_header(Compose *compose)
7328 GtkWidget *from_optmenu_hbox;
7329 GtkWidget *header_table_main;
7330 GtkWidget *header_scrolledwin;
7331 GtkWidget *header_table;
7333 /* parent with account selection and from header */
7334 header_table_main = gtk_table_new(2, 2, FALSE);
7335 gtk_widget_show(header_table_main);
7336 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7338 from_optmenu_hbox = compose_account_option_menu_create(compose);
7339 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7340 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7342 /* child with header labels and entries */
7343 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7344 gtk_widget_show(header_scrolledwin);
7345 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7347 header_table = gtk_table_new(2, 2, FALSE);
7348 gtk_widget_show(header_table);
7349 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7350 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7351 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7352 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7353 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7355 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7356 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7358 compose->header_table = header_table;
7359 compose->header_list = NULL;
7360 compose->header_nextrow = 0;
7362 compose_create_header_entry(compose);
7364 compose->table = NULL;
7366 return header_table_main;
7369 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7371 Compose *compose = (Compose *)data;
7372 GdkEventButton event;
7375 event.time = gtk_get_current_event_time();
7377 return attach_button_pressed(compose->attach_clist, &event, compose);
7380 static GtkWidget *compose_create_attach(Compose *compose)
7382 GtkWidget *attach_scrwin;
7383 GtkWidget *attach_clist;
7385 GtkListStore *store;
7386 GtkCellRenderer *renderer;
7387 GtkTreeViewColumn *column;
7388 GtkTreeSelection *selection;
7390 /* attachment list */
7391 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7392 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7393 GTK_POLICY_AUTOMATIC,
7394 GTK_POLICY_AUTOMATIC);
7395 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7397 store = gtk_list_store_new(N_ATTACH_COLS,
7403 G_TYPE_AUTO_POINTER,
7405 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7406 (GTK_TREE_MODEL(store)));
7407 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7408 g_object_unref(store);
7410 renderer = gtk_cell_renderer_text_new();
7411 column = gtk_tree_view_column_new_with_attributes
7412 (_("Mime type"), renderer, "text",
7413 COL_MIMETYPE, NULL);
7414 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7416 renderer = gtk_cell_renderer_text_new();
7417 column = gtk_tree_view_column_new_with_attributes
7418 (_("Size"), renderer, "text",
7420 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7422 renderer = gtk_cell_renderer_text_new();
7423 column = gtk_tree_view_column_new_with_attributes
7424 (_("Name"), renderer, "text",
7426 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7428 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7429 prefs_common.use_stripes_everywhere);
7430 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7431 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7433 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7434 G_CALLBACK(attach_selected), compose);
7435 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7436 G_CALLBACK(attach_button_pressed), compose);
7437 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7438 G_CALLBACK(popup_attach_button_pressed), compose);
7439 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7440 G_CALLBACK(attach_key_pressed), compose);
7443 gtk_drag_dest_set(attach_clist,
7444 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7445 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7446 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7447 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7448 G_CALLBACK(compose_attach_drag_received_cb),
7450 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7451 G_CALLBACK(compose_drag_drop),
7454 compose->attach_scrwin = attach_scrwin;
7455 compose->attach_clist = attach_clist;
7457 return attach_scrwin;
7460 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7461 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7463 static GtkWidget *compose_create_others(Compose *compose)
7466 GtkWidget *savemsg_checkbtn;
7467 GtkWidget *savemsg_combo;
7468 GtkWidget *savemsg_select;
7471 gchar *folderidentifier;
7473 /* Table for settings */
7474 table = gtk_table_new(3, 1, FALSE);
7475 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7476 gtk_widget_show(table);
7477 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7480 /* Save Message to folder */
7481 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7482 gtk_widget_show(savemsg_checkbtn);
7483 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7484 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7485 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7487 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7488 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7490 #if !GTK_CHECK_VERSION(2, 24, 0)
7491 savemsg_combo = gtk_combo_box_entry_new_text();
7493 savemsg_combo = gtk_combo_box_text_new_with_entry();
7495 compose->savemsg_checkbtn = savemsg_checkbtn;
7496 compose->savemsg_combo = savemsg_combo;
7497 gtk_widget_show(savemsg_combo);
7499 if (prefs_common.compose_save_to_history)
7500 #if !GTK_CHECK_VERSION(2, 24, 0)
7501 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7502 prefs_common.compose_save_to_history);
7504 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7505 prefs_common.compose_save_to_history);
7507 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7508 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7509 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7510 G_CALLBACK(compose_grab_focus_cb), compose);
7511 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7512 folderidentifier = folder_item_get_identifier(account_get_special_folder
7513 (compose->account, F_OUTBOX));
7514 compose_set_save_to(compose, folderidentifier);
7515 g_free(folderidentifier);
7518 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7519 gtk_widget_show(savemsg_select);
7520 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7521 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7522 G_CALLBACK(compose_savemsg_select_cb),
7528 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7530 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7531 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7534 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7539 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7540 _("Select folder to save message to"));
7543 path = folder_item_get_identifier(dest);
7545 compose_set_save_to(compose, path);
7549 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7550 GdkAtom clip, GtkTextIter *insert_place);
7553 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7557 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7559 if (event->button == 3) {
7561 GtkTextIter sel_start, sel_end;
7562 gboolean stuff_selected;
7564 /* move the cursor to allow GtkAspell to check the word
7565 * under the mouse */
7566 if (event->x && event->y) {
7567 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7568 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7570 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7573 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7574 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7577 stuff_selected = gtk_text_buffer_get_selection_bounds(
7579 &sel_start, &sel_end);
7581 gtk_text_buffer_place_cursor (buffer, &iter);
7582 /* reselect stuff */
7584 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7585 gtk_text_buffer_select_range(buffer,
7586 &sel_start, &sel_end);
7588 return FALSE; /* pass the event so that the right-click goes through */
7591 if (event->button == 2) {
7596 /* get the middle-click position to paste at the correct place */
7597 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7598 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7600 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7603 entry_paste_clipboard(compose, text,
7604 prefs_common.linewrap_pastes,
7605 GDK_SELECTION_PRIMARY, &iter);
7613 static void compose_spell_menu_changed(void *data)
7615 Compose *compose = (Compose *)data;
7617 GtkWidget *menuitem;
7618 GtkWidget *parent_item;
7619 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7622 if (compose->gtkaspell == NULL)
7625 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7626 "/Menu/Spelling/Options");
7628 /* setting the submenu removes /Spelling/Options from the factory
7629 * so we need to save it */
7631 if (parent_item == NULL) {
7632 parent_item = compose->aspell_options_menu;
7633 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7635 compose->aspell_options_menu = parent_item;
7637 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7639 spell_menu = g_slist_reverse(spell_menu);
7640 for (items = spell_menu;
7641 items; items = items->next) {
7642 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7643 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7644 gtk_widget_show(GTK_WIDGET(menuitem));
7646 g_slist_free(spell_menu);
7648 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7649 gtk_widget_show(parent_item);
7652 static void compose_dict_changed(void *data)
7654 Compose *compose = (Compose *) data;
7656 if(!compose->gtkaspell)
7658 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7661 gtkaspell_highlight_all(compose->gtkaspell);
7662 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7666 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7668 Compose *compose = (Compose *)data;
7669 GdkEventButton event;
7672 event.time = gtk_get_current_event_time();
7676 return text_clicked(compose->text, &event, compose);
7679 static gboolean compose_force_window_origin = TRUE;
7680 static Compose *compose_create(PrefsAccount *account,
7689 GtkWidget *handlebox;
7691 GtkWidget *notebook;
7693 GtkWidget *attach_hbox;
7694 GtkWidget *attach_lab1;
7695 GtkWidget *attach_lab2;
7700 GtkWidget *subject_hbox;
7701 GtkWidget *subject_frame;
7702 GtkWidget *subject_entry;
7706 GtkWidget *edit_vbox;
7707 GtkWidget *ruler_hbox;
7709 GtkWidget *scrolledwin;
7711 GtkTextBuffer *buffer;
7712 GtkClipboard *clipboard;
7714 UndoMain *undostruct;
7716 GtkWidget *popupmenu;
7717 GtkWidget *tmpl_menu;
7718 GtkActionGroup *action_group = NULL;
7721 GtkAspell * gtkaspell = NULL;
7724 static GdkGeometry geometry;
7726 cm_return_val_if_fail(account != NULL, NULL);
7728 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7729 &default_header_bgcolor);
7730 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7731 &default_header_color);
7733 debug_print("Creating compose window...\n");
7734 compose = g_new0(Compose, 1);
7736 compose->batch = batch;
7737 compose->account = account;
7738 compose->folder = folder;
7740 compose->mutex = cm_mutex_new();
7741 compose->set_cursor_pos = -1;
7743 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7745 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7746 gtk_widget_set_size_request(window, prefs_common.compose_width,
7747 prefs_common.compose_height);
7749 if (!geometry.max_width) {
7750 geometry.max_width = gdk_screen_width();
7751 geometry.max_height = gdk_screen_height();
7754 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7755 &geometry, GDK_HINT_MAX_SIZE);
7756 if (!geometry.min_width) {
7757 geometry.min_width = 600;
7758 geometry.min_height = 440;
7760 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7761 &geometry, GDK_HINT_MIN_SIZE);
7763 #ifndef GENERIC_UMPC
7764 if (compose_force_window_origin)
7765 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7766 prefs_common.compose_y);
7768 g_signal_connect(G_OBJECT(window), "delete_event",
7769 G_CALLBACK(compose_delete_cb), compose);
7770 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7771 gtk_widget_realize(window);
7773 gtkut_widget_set_composer_icon(window);
7775 vbox = gtk_vbox_new(FALSE, 0);
7776 gtk_container_add(GTK_CONTAINER(window), vbox);
7778 compose->ui_manager = gtk_ui_manager_new();
7779 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7780 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7781 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7782 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7783 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7784 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7785 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7786 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7787 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7788 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7907 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)
7908 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)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7914 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)
7915 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)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7920 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)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7924 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)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7930 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)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7937 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)
7938 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)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7951 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)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7969 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7970 gtk_widget_show_all(menubar);
7972 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7973 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7975 if (prefs_common.toolbar_detachable) {
7976 handlebox = gtk_handle_box_new();
7978 handlebox = gtk_hbox_new(FALSE, 0);
7980 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7982 gtk_widget_realize(handlebox);
7983 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7986 vbox2 = gtk_vbox_new(FALSE, 2);
7987 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7988 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7991 notebook = gtk_notebook_new();
7992 gtk_widget_show(notebook);
7994 /* header labels and entries */
7995 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7996 compose_create_header(compose),
7997 gtk_label_new_with_mnemonic(_("Hea_der")));
7998 /* attachment list */
7999 attach_hbox = gtk_hbox_new(FALSE, 0);
8000 gtk_widget_show(attach_hbox);
8002 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8003 gtk_widget_show(attach_lab1);
8004 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8006 attach_lab2 = gtk_label_new("");
8007 gtk_widget_show(attach_lab2);
8008 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8010 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8011 compose_create_attach(compose),
8014 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8015 compose_create_others(compose),
8016 gtk_label_new_with_mnemonic(_("Othe_rs")));
8019 subject_hbox = gtk_hbox_new(FALSE, 0);
8020 gtk_widget_show(subject_hbox);
8022 subject_frame = gtk_frame_new(NULL);
8023 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8024 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8025 gtk_widget_show(subject_frame);
8027 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8028 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8029 gtk_widget_show(subject);
8031 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8032 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8033 gtk_widget_show(label);
8036 subject_entry = claws_spell_entry_new();
8038 subject_entry = gtk_entry_new();
8040 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8041 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8042 G_CALLBACK(compose_grab_focus_cb), compose);
8043 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8044 gtk_widget_show(subject_entry);
8045 compose->subject_entry = subject_entry;
8046 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8048 edit_vbox = gtk_vbox_new(FALSE, 0);
8050 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8053 ruler_hbox = gtk_hbox_new(FALSE, 0);
8054 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8056 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8057 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8058 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8062 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8063 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8064 GTK_POLICY_AUTOMATIC,
8065 GTK_POLICY_AUTOMATIC);
8066 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8068 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8070 text = gtk_text_view_new();
8071 if (prefs_common.show_compose_margin) {
8072 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8073 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8075 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8076 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8077 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8078 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8079 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8081 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8082 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8083 G_CALLBACK(compose_edit_size_alloc),
8085 g_signal_connect(G_OBJECT(buffer), "changed",
8086 G_CALLBACK(compose_changed_cb), compose);
8087 g_signal_connect(G_OBJECT(text), "grab_focus",
8088 G_CALLBACK(compose_grab_focus_cb), compose);
8089 g_signal_connect(G_OBJECT(buffer), "insert_text",
8090 G_CALLBACK(text_inserted), compose);
8091 g_signal_connect(G_OBJECT(text), "button_press_event",
8092 G_CALLBACK(text_clicked), compose);
8093 g_signal_connect(G_OBJECT(text), "popup-menu",
8094 G_CALLBACK(compose_popup_menu), compose);
8095 g_signal_connect(G_OBJECT(subject_entry), "changed",
8096 G_CALLBACK(compose_changed_cb), compose);
8097 g_signal_connect(G_OBJECT(subject_entry), "activate",
8098 G_CALLBACK(compose_subject_entry_activated), compose);
8101 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8102 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8103 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8104 g_signal_connect(G_OBJECT(text), "drag_data_received",
8105 G_CALLBACK(compose_insert_drag_received_cb),
8107 g_signal_connect(G_OBJECT(text), "drag-drop",
8108 G_CALLBACK(compose_drag_drop),
8110 g_signal_connect(G_OBJECT(text), "key-press-event",
8111 G_CALLBACK(completion_set_focus_to_subject),
8113 gtk_widget_show_all(vbox);
8115 /* pane between attach clist and text */
8116 paned = gtk_vpaned_new();
8117 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8118 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8119 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8120 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8121 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8122 G_CALLBACK(compose_notebook_size_alloc), paned);
8124 gtk_widget_show_all(paned);
8127 if (prefs_common.textfont) {
8128 PangoFontDescription *font_desc;
8130 font_desc = pango_font_description_from_string
8131 (prefs_common.textfont);
8133 gtk_widget_modify_font(text, font_desc);
8134 pango_font_description_free(font_desc);
8138 gtk_action_group_add_actions(action_group, compose_popup_entries,
8139 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8147 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8149 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8150 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8151 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8153 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8155 undostruct = undo_init(text);
8156 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8159 address_completion_start(window);
8161 compose->window = window;
8162 compose->vbox = vbox;
8163 compose->menubar = menubar;
8164 compose->handlebox = handlebox;
8166 compose->vbox2 = vbox2;
8168 compose->paned = paned;
8170 compose->attach_label = attach_lab2;
8172 compose->notebook = notebook;
8173 compose->edit_vbox = edit_vbox;
8174 compose->ruler_hbox = ruler_hbox;
8175 compose->ruler = ruler;
8176 compose->scrolledwin = scrolledwin;
8177 compose->text = text;
8179 compose->focused_editable = NULL;
8181 compose->popupmenu = popupmenu;
8183 compose->tmpl_menu = tmpl_menu;
8185 compose->mode = mode;
8186 compose->rmode = mode;
8188 compose->targetinfo = NULL;
8189 compose->replyinfo = NULL;
8190 compose->fwdinfo = NULL;
8192 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8193 g_str_equal, (GDestroyNotify) g_free, NULL);
8195 compose->replyto = NULL;
8197 compose->bcc = NULL;
8198 compose->followup_to = NULL;
8200 compose->ml_post = NULL;
8202 compose->inreplyto = NULL;
8203 compose->references = NULL;
8204 compose->msgid = NULL;
8205 compose->boundary = NULL;
8207 compose->autowrap = prefs_common.autowrap;
8208 compose->autoindent = prefs_common.auto_indent;
8209 compose->use_signing = FALSE;
8210 compose->use_encryption = FALSE;
8211 compose->privacy_system = NULL;
8212 compose->encdata = NULL;
8214 compose->modified = FALSE;
8216 compose->return_receipt = FALSE;
8218 compose->to_list = NULL;
8219 compose->newsgroup_list = NULL;
8221 compose->undostruct = undostruct;
8223 compose->sig_str = NULL;
8225 compose->exteditor_file = NULL;
8226 compose->exteditor_pid = -1;
8227 compose->exteditor_tag = -1;
8228 compose->exteditor_socket = NULL;
8229 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8231 compose->folder_update_callback_id =
8232 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8233 compose_update_folder_hook,
8234 (gpointer) compose);
8237 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8238 if (mode != COMPOSE_REDIRECT) {
8239 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8240 strcmp(prefs_common.dictionary, "")) {
8241 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8242 prefs_common.alt_dictionary,
8243 conv_get_locale_charset_str(),
8244 prefs_common.misspelled_col,
8245 prefs_common.check_while_typing,
8246 prefs_common.recheck_when_changing_dict,
8247 prefs_common.use_alternate,
8248 prefs_common.use_both_dicts,
8249 GTK_TEXT_VIEW(text),
8250 GTK_WINDOW(compose->window),
8251 compose_dict_changed,
8252 compose_spell_menu_changed,
8255 alertpanel_error(_("Spell checker could not "
8257 gtkaspell_checkers_strerror());
8258 gtkaspell_checkers_reset_error();
8260 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8264 compose->gtkaspell = gtkaspell;
8265 compose_spell_menu_changed(compose);
8266 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8269 compose_select_account(compose, account, TRUE);
8271 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8272 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8274 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8275 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8277 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8278 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8280 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8281 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8283 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8284 if (account->protocol != A_NNTP)
8285 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8286 prefs_common_translated_header_name("To:"));
8288 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8289 prefs_common_translated_header_name("Newsgroups:"));
8291 #ifndef USE_ALT_ADDRBOOK
8292 addressbook_set_target_compose(compose);
8294 if (mode != COMPOSE_REDIRECT)
8295 compose_set_template_menu(compose);
8297 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8300 compose_list = g_list_append(compose_list, compose);
8302 if (!prefs_common.show_ruler)
8303 gtk_widget_hide(ruler_hbox);
8305 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8308 compose->priority = PRIORITY_NORMAL;
8309 compose_update_priority_menu_item(compose);
8311 compose_set_out_encoding(compose);
8314 compose_update_actions_menu(compose);
8316 /* Privacy Systems menu */
8317 compose_update_privacy_systems_menu(compose);
8319 activate_privacy_system(compose, account, TRUE);
8320 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8322 gtk_widget_realize(window);
8324 gtk_widget_show(window);
8330 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8335 GtkWidget *optmenubox;
8336 GtkWidget *fromlabel;
8339 GtkWidget *from_name = NULL;
8341 gint num = 0, def_menu = 0;
8343 accounts = account_get_list();
8344 cm_return_val_if_fail(accounts != NULL, NULL);
8346 optmenubox = gtk_event_box_new();
8347 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8348 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8350 hbox = gtk_hbox_new(FALSE, 4);
8351 from_name = gtk_entry_new();
8353 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8354 G_CALLBACK(compose_grab_focus_cb), compose);
8355 g_signal_connect_after(G_OBJECT(from_name), "activate",
8356 G_CALLBACK(from_name_activate_cb), optmenu);
8358 for (; accounts != NULL; accounts = accounts->next, num++) {
8359 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8360 gchar *name, *from = NULL;
8362 if (ac == compose->account) def_menu = num;
8364 name = g_markup_printf_escaped("<i>%s</i>",
8367 if (ac == compose->account) {
8368 if (ac->name && *ac->name) {
8370 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8371 from = g_strdup_printf("%s <%s>",
8373 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8375 from = g_strdup_printf("%s",
8377 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8379 if (cur_account != compose->account) {
8380 gtk_widget_modify_base(
8381 GTK_WIDGET(from_name),
8382 GTK_STATE_NORMAL, &default_header_bgcolor);
8383 gtk_widget_modify_text(
8384 GTK_WIDGET(from_name),
8385 GTK_STATE_NORMAL, &default_header_color);
8388 COMBOBOX_ADD(menu, name, ac->account_id);
8393 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8395 g_signal_connect(G_OBJECT(optmenu), "changed",
8396 G_CALLBACK(account_activated),
8398 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8399 G_CALLBACK(compose_entry_popup_extend),
8402 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8403 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8405 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8406 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8407 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8409 /* Putting only the GtkEntry into focus chain of parent hbox causes
8410 * the account selector combobox next to it to be unreachable when
8411 * navigating widgets in GtkTable with up/down arrow keys.
8412 * Note: gtk_widget_set_can_focus() was not enough. */
8414 l = g_list_prepend(l, from_name);
8415 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8418 CLAWS_SET_TIP(optmenubox,
8419 _("Account to use for this email"));
8420 CLAWS_SET_TIP(from_name,
8421 _("Sender address to be used"));
8423 compose->account_combo = optmenu;
8424 compose->from_name = from_name;
8429 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8431 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8432 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8433 Compose *compose = (Compose *) data;
8435 compose->priority = value;
8439 static void compose_reply_change_mode(Compose *compose,
8442 gboolean was_modified = compose->modified;
8444 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8446 cm_return_if_fail(compose->replyinfo != NULL);
8448 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8450 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8452 if (action == COMPOSE_REPLY_TO_ALL)
8454 if (action == COMPOSE_REPLY_TO_SENDER)
8456 if (action == COMPOSE_REPLY_TO_LIST)
8459 compose_remove_header_entries(compose);
8460 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8461 if (compose->account->set_autocc && compose->account->auto_cc)
8462 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8464 if (compose->account->set_autobcc && compose->account->auto_bcc)
8465 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8467 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8468 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8469 compose_show_first_last_header(compose, TRUE);
8470 compose->modified = was_modified;
8471 compose_set_title(compose);
8474 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8476 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8477 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8478 Compose *compose = (Compose *) data;
8481 compose_reply_change_mode(compose, value);
8484 static void compose_update_priority_menu_item(Compose * compose)
8486 GtkWidget *menuitem = NULL;
8487 switch (compose->priority) {
8488 case PRIORITY_HIGHEST:
8489 menuitem = gtk_ui_manager_get_widget
8490 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8493 menuitem = gtk_ui_manager_get_widget
8494 (compose->ui_manager, "/Menu/Options/Priority/High");
8496 case PRIORITY_NORMAL:
8497 menuitem = gtk_ui_manager_get_widget
8498 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8501 menuitem = gtk_ui_manager_get_widget
8502 (compose->ui_manager, "/Menu/Options/Priority/Low");
8504 case PRIORITY_LOWEST:
8505 menuitem = gtk_ui_manager_get_widget
8506 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8509 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8512 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8514 Compose *compose = (Compose *) data;
8516 gboolean can_sign = FALSE, can_encrypt = FALSE;
8518 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8520 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8523 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8524 g_free(compose->privacy_system);
8525 compose->privacy_system = NULL;
8526 g_free(compose->encdata);
8527 compose->encdata = NULL;
8528 if (systemid != NULL) {
8529 compose->privacy_system = g_strdup(systemid);
8531 can_sign = privacy_system_can_sign(systemid);
8532 can_encrypt = privacy_system_can_encrypt(systemid);
8535 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8539 if (compose->toolbar->privacy_sign_btn != NULL) {
8540 gtk_widget_set_sensitive(
8541 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8543 gtk_toggle_tool_button_set_active(
8544 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8545 can_sign ? compose->use_signing : FALSE);
8547 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8548 gtk_widget_set_sensitive(
8549 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8551 gtk_toggle_tool_button_set_active(
8552 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8553 can_encrypt ? compose->use_encryption : FALSE);
8557 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8559 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8560 GtkWidget *menuitem = NULL;
8561 GList *children, *amenu;
8562 gboolean can_sign = FALSE, can_encrypt = FALSE;
8563 gboolean found = FALSE;
8565 if (compose->privacy_system != NULL) {
8567 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8568 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8569 cm_return_if_fail(menuitem != NULL);
8571 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8574 while (amenu != NULL) {
8575 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8576 if (systemid != NULL) {
8577 if (strcmp(systemid, compose->privacy_system) == 0 &&
8578 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8579 menuitem = GTK_WIDGET(amenu->data);
8581 can_sign = privacy_system_can_sign(systemid);
8582 can_encrypt = privacy_system_can_encrypt(systemid);
8586 } else if (strlen(compose->privacy_system) == 0 &&
8587 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8588 menuitem = GTK_WIDGET(amenu->data);
8591 can_encrypt = FALSE;
8596 amenu = amenu->next;
8598 g_list_free(children);
8599 if (menuitem != NULL)
8600 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8602 if (warn && !found && strlen(compose->privacy_system)) {
8603 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8604 "will not be able to sign or encrypt this message."),
8605 compose->privacy_system);
8609 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8610 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8611 if (compose->toolbar->privacy_sign_btn != NULL) {
8612 gtk_widget_set_sensitive(
8613 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8616 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8617 gtk_widget_set_sensitive(
8618 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8623 static void compose_set_out_encoding(Compose *compose)
8625 CharSet out_encoding;
8626 const gchar *branch = NULL;
8627 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8629 switch(out_encoding) {
8630 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8631 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8632 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8633 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8634 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8635 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8636 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8637 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8638 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8639 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8640 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8641 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8642 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8643 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8644 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8645 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8646 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8647 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8648 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8649 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8650 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8651 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8652 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8653 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8654 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8655 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8656 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8657 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8658 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8659 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8660 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8661 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8662 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8663 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8665 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8668 static void compose_set_template_menu(Compose *compose)
8670 GSList *tmpl_list, *cur;
8674 tmpl_list = template_get_config();
8676 menu = gtk_menu_new();
8678 gtk_menu_set_accel_group (GTK_MENU (menu),
8679 gtk_ui_manager_get_accel_group(compose->ui_manager));
8680 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8681 Template *tmpl = (Template *)cur->data;
8682 gchar *accel_path = NULL;
8683 item = gtk_menu_item_new_with_label(tmpl->name);
8684 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8685 g_signal_connect(G_OBJECT(item), "activate",
8686 G_CALLBACK(compose_template_activate_cb),
8688 g_object_set_data(G_OBJECT(item), "template", tmpl);
8689 gtk_widget_show(item);
8690 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8691 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8695 gtk_widget_show(menu);
8696 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8699 void compose_update_actions_menu(Compose *compose)
8701 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8704 static void compose_update_privacy_systems_menu(Compose *compose)
8706 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8707 GSList *systems, *cur;
8709 GtkWidget *system_none;
8711 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8712 GtkWidget *privacy_menu = gtk_menu_new();
8714 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8715 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8717 g_signal_connect(G_OBJECT(system_none), "activate",
8718 G_CALLBACK(compose_set_privacy_system_cb), compose);
8720 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8721 gtk_widget_show(system_none);
8723 systems = privacy_get_system_ids();
8724 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8725 gchar *systemid = cur->data;
8727 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8728 widget = gtk_radio_menu_item_new_with_label(group,
8729 privacy_system_get_name(systemid));
8730 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8731 g_strdup(systemid), g_free);
8732 g_signal_connect(G_OBJECT(widget), "activate",
8733 G_CALLBACK(compose_set_privacy_system_cb), compose);
8735 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8736 gtk_widget_show(widget);
8739 g_slist_free(systems);
8740 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8741 gtk_widget_show_all(privacy_menu);
8742 gtk_widget_show_all(privacy_menuitem);
8745 void compose_reflect_prefs_all(void)
8750 for (cur = compose_list; cur != NULL; cur = cur->next) {
8751 compose = (Compose *)cur->data;
8752 compose_set_template_menu(compose);
8756 void compose_reflect_prefs_pixmap_theme(void)
8761 for (cur = compose_list; cur != NULL; cur = cur->next) {
8762 compose = (Compose *)cur->data;
8763 toolbar_update(TOOLBAR_COMPOSE, compose);
8767 static const gchar *compose_quote_char_from_context(Compose *compose)
8769 const gchar *qmark = NULL;
8771 cm_return_val_if_fail(compose != NULL, NULL);
8773 switch (compose->mode) {
8774 /* use forward-specific quote char */
8775 case COMPOSE_FORWARD:
8776 case COMPOSE_FORWARD_AS_ATTACH:
8777 case COMPOSE_FORWARD_INLINE:
8778 if (compose->folder && compose->folder->prefs &&
8779 compose->folder->prefs->forward_with_format)
8780 qmark = compose->folder->prefs->forward_quotemark;
8781 else if (compose->account->forward_with_format)
8782 qmark = compose->account->forward_quotemark;
8784 qmark = prefs_common.fw_quotemark;
8787 /* use reply-specific quote char in all other modes */
8789 if (compose->folder && compose->folder->prefs &&
8790 compose->folder->prefs->reply_with_format)
8791 qmark = compose->folder->prefs->reply_quotemark;
8792 else if (compose->account->reply_with_format)
8793 qmark = compose->account->reply_quotemark;
8795 qmark = prefs_common.quotemark;
8799 if (qmark == NULL || *qmark == '\0')
8805 static void compose_template_apply(Compose *compose, Template *tmpl,
8809 GtkTextBuffer *buffer;
8813 gchar *parsed_str = NULL;
8814 gint cursor_pos = 0;
8815 const gchar *err_msg = _("The body of the template has an error at line %d.");
8818 /* process the body */
8820 text = GTK_TEXT_VIEW(compose->text);
8821 buffer = gtk_text_view_get_buffer(text);
8824 qmark = compose_quote_char_from_context(compose);
8826 if (compose->replyinfo != NULL) {
8829 gtk_text_buffer_set_text(buffer, "", -1);
8830 mark = gtk_text_buffer_get_insert(buffer);
8831 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8833 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8834 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8836 } else if (compose->fwdinfo != NULL) {
8839 gtk_text_buffer_set_text(buffer, "", -1);
8840 mark = gtk_text_buffer_get_insert(buffer);
8841 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8843 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8844 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8847 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8849 GtkTextIter start, end;
8852 gtk_text_buffer_get_start_iter(buffer, &start);
8853 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8854 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8856 /* clear the buffer now */
8858 gtk_text_buffer_set_text(buffer, "", -1);
8860 parsed_str = compose_quote_fmt(compose, dummyinfo,
8861 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8862 procmsg_msginfo_free( &dummyinfo );
8868 gtk_text_buffer_set_text(buffer, "", -1);
8869 mark = gtk_text_buffer_get_insert(buffer);
8870 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8873 if (replace && parsed_str && compose->account->auto_sig)
8874 compose_insert_sig(compose, FALSE);
8876 if (replace && parsed_str) {
8877 gtk_text_buffer_get_start_iter(buffer, &iter);
8878 gtk_text_buffer_place_cursor(buffer, &iter);
8882 cursor_pos = quote_fmt_get_cursor_pos();
8883 compose->set_cursor_pos = cursor_pos;
8884 if (cursor_pos == -1)
8886 gtk_text_buffer_get_start_iter(buffer, &iter);
8887 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8888 gtk_text_buffer_place_cursor(buffer, &iter);
8891 /* process the other fields */
8893 compose_template_apply_fields(compose, tmpl);
8894 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8895 quote_fmt_reset_vartable();
8896 quote_fmtlex_destroy();
8898 compose_changed_cb(NULL, compose);
8901 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8902 gtkaspell_highlight_all(compose->gtkaspell);
8906 static void compose_template_apply_fields_error(const gchar *header)
8911 tr = g_strdup(C_("'%s' stands for a header name",
8912 "Template '%s' format error."));
8913 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8914 alertpanel_error("%s", text);
8920 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8922 MsgInfo* dummyinfo = NULL;
8923 MsgInfo *msginfo = NULL;
8926 if (compose->replyinfo != NULL)
8927 msginfo = compose->replyinfo;
8928 else if (compose->fwdinfo != NULL)
8929 msginfo = compose->fwdinfo;
8931 dummyinfo = compose_msginfo_new_from_compose(compose);
8932 msginfo = dummyinfo;
8935 if (tmpl->from && *tmpl->from != '\0') {
8937 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8938 compose->gtkaspell);
8940 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8942 quote_fmt_scan_string(tmpl->from);
8945 buf = quote_fmt_get_buffer();
8947 compose_template_apply_fields_error("From");
8949 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8952 quote_fmt_reset_vartable();
8953 quote_fmtlex_destroy();
8956 if (tmpl->to && *tmpl->to != '\0') {
8958 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8959 compose->gtkaspell);
8961 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8963 quote_fmt_scan_string(tmpl->to);
8966 buf = quote_fmt_get_buffer();
8968 compose_template_apply_fields_error("To");
8970 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8973 quote_fmt_reset_vartable();
8974 quote_fmtlex_destroy();
8977 if (tmpl->cc && *tmpl->cc != '\0') {
8979 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8980 compose->gtkaspell);
8982 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8984 quote_fmt_scan_string(tmpl->cc);
8987 buf = quote_fmt_get_buffer();
8989 compose_template_apply_fields_error("Cc");
8991 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8994 quote_fmt_reset_vartable();
8995 quote_fmtlex_destroy();
8998 if (tmpl->bcc && *tmpl->bcc != '\0') {
9000 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9001 compose->gtkaspell);
9003 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9005 quote_fmt_scan_string(tmpl->bcc);
9008 buf = quote_fmt_get_buffer();
9010 compose_template_apply_fields_error("Bcc");
9012 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9015 quote_fmt_reset_vartable();
9016 quote_fmtlex_destroy();
9019 if (tmpl->replyto && *tmpl->replyto != '\0') {
9021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9022 compose->gtkaspell);
9024 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9026 quote_fmt_scan_string(tmpl->replyto);
9029 buf = quote_fmt_get_buffer();
9031 compose_template_apply_fields_error("Reply-To");
9033 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9036 quote_fmt_reset_vartable();
9037 quote_fmtlex_destroy();
9040 /* process the subject */
9041 if (tmpl->subject && *tmpl->subject != '\0') {
9043 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9044 compose->gtkaspell);
9046 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9048 quote_fmt_scan_string(tmpl->subject);
9051 buf = quote_fmt_get_buffer();
9053 compose_template_apply_fields_error("Subject");
9055 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9058 quote_fmt_reset_vartable();
9059 quote_fmtlex_destroy();
9062 procmsg_msginfo_free( &dummyinfo );
9065 static void compose_destroy(Compose *compose)
9067 GtkAllocation allocation;
9068 GtkTextBuffer *buffer;
9069 GtkClipboard *clipboard;
9071 compose_list = g_list_remove(compose_list, compose);
9073 if (compose->updating) {
9074 debug_print("danger, not destroying anything now\n");
9075 compose->deferred_destroy = TRUE;
9079 /* NOTE: address_completion_end() does nothing with the window
9080 * however this may change. */
9081 address_completion_end(compose->window);
9083 slist_free_strings_full(compose->to_list);
9084 slist_free_strings_full(compose->newsgroup_list);
9085 slist_free_strings_full(compose->header_list);
9087 slist_free_strings_full(extra_headers);
9088 extra_headers = NULL;
9090 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9092 g_hash_table_destroy(compose->email_hashtable);
9094 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9095 compose->folder_update_callback_id);
9097 procmsg_msginfo_free(&(compose->targetinfo));
9098 procmsg_msginfo_free(&(compose->replyinfo));
9099 procmsg_msginfo_free(&(compose->fwdinfo));
9101 g_free(compose->replyto);
9102 g_free(compose->cc);
9103 g_free(compose->bcc);
9104 g_free(compose->newsgroups);
9105 g_free(compose->followup_to);
9107 g_free(compose->ml_post);
9109 g_free(compose->inreplyto);
9110 g_free(compose->references);
9111 g_free(compose->msgid);
9112 g_free(compose->boundary);
9114 g_free(compose->redirect_filename);
9115 if (compose->undostruct)
9116 undo_destroy(compose->undostruct);
9118 g_free(compose->sig_str);
9120 g_free(compose->exteditor_file);
9122 g_free(compose->orig_charset);
9124 g_free(compose->privacy_system);
9125 g_free(compose->encdata);
9127 #ifndef USE_ALT_ADDRBOOK
9128 if (addressbook_get_target_compose() == compose)
9129 addressbook_set_target_compose(NULL);
9132 if (compose->gtkaspell) {
9133 gtkaspell_delete(compose->gtkaspell);
9134 compose->gtkaspell = NULL;
9138 if (!compose->batch) {
9139 gtk_widget_get_allocation(compose->window, &allocation);
9140 prefs_common.compose_width = allocation.width;
9141 prefs_common.compose_height = allocation.height;
9144 if (!gtk_widget_get_parent(compose->paned))
9145 gtk_widget_destroy(compose->paned);
9146 gtk_widget_destroy(compose->popupmenu);
9148 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9149 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9150 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9152 gtk_widget_destroy(compose->window);
9153 toolbar_destroy(compose->toolbar);
9154 g_free(compose->toolbar);
9155 cm_mutex_free(compose->mutex);
9159 static void compose_attach_info_free(AttachInfo *ainfo)
9161 g_free(ainfo->file);
9162 g_free(ainfo->content_type);
9163 g_free(ainfo->name);
9164 g_free(ainfo->charset);
9168 static void compose_attach_update_label(Compose *compose)
9173 GtkTreeModel *model;
9177 if (compose == NULL)
9180 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9181 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9182 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9186 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9187 total_size = ainfo->size;
9188 while(gtk_tree_model_iter_next(model, &iter)) {
9189 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9190 total_size += ainfo->size;
9193 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9194 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9198 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9200 Compose *compose = (Compose *)data;
9201 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9202 GtkTreeSelection *selection;
9204 GtkTreeModel *model;
9206 selection = gtk_tree_view_get_selection(tree_view);
9207 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9212 for (cur = sel; cur != NULL; cur = cur->next) {
9213 GtkTreePath *path = cur->data;
9214 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9217 gtk_tree_path_free(path);
9220 for (cur = sel; cur != NULL; cur = cur->next) {
9221 GtkTreeRowReference *ref = cur->data;
9222 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9225 if (gtk_tree_model_get_iter(model, &iter, path))
9226 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9228 gtk_tree_path_free(path);
9229 gtk_tree_row_reference_free(ref);
9233 compose_attach_update_label(compose);
9236 static struct _AttachProperty
9239 GtkWidget *mimetype_entry;
9240 GtkWidget *encoding_optmenu;
9241 GtkWidget *path_entry;
9242 GtkWidget *filename_entry;
9244 GtkWidget *cancel_btn;
9247 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9249 gtk_tree_path_free((GtkTreePath *)ptr);
9252 static void compose_attach_property(GtkAction *action, gpointer data)
9254 Compose *compose = (Compose *)data;
9255 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9257 GtkComboBox *optmenu;
9258 GtkTreeSelection *selection;
9260 GtkTreeModel *model;
9263 static gboolean cancelled;
9265 /* only if one selected */
9266 selection = gtk_tree_view_get_selection(tree_view);
9267 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9270 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9274 path = (GtkTreePath *) sel->data;
9275 gtk_tree_model_get_iter(model, &iter, path);
9276 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9279 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9285 if (!attach_prop.window)
9286 compose_attach_property_create(&cancelled);
9287 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9288 gtk_widget_grab_focus(attach_prop.ok_btn);
9289 gtk_widget_show(attach_prop.window);
9290 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9291 GTK_WINDOW(compose->window));
9293 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9294 if (ainfo->encoding == ENC_UNKNOWN)
9295 combobox_select_by_data(optmenu, ENC_BASE64);
9297 combobox_select_by_data(optmenu, ainfo->encoding);
9299 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9300 ainfo->content_type ? ainfo->content_type : "");
9301 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9302 ainfo->file ? ainfo->file : "");
9303 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9304 ainfo->name ? ainfo->name : "");
9307 const gchar *entry_text;
9309 gchar *cnttype = NULL;
9316 gtk_widget_hide(attach_prop.window);
9317 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9322 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9323 if (*entry_text != '\0') {
9326 text = g_strstrip(g_strdup(entry_text));
9327 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9328 cnttype = g_strdup(text);
9331 alertpanel_error(_("Invalid MIME type."));
9337 ainfo->encoding = combobox_get_active_data(optmenu);
9339 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9340 if (*entry_text != '\0') {
9341 if (is_file_exist(entry_text) &&
9342 (size = get_file_size(entry_text)) > 0)
9343 file = g_strdup(entry_text);
9346 (_("File doesn't exist or is empty."));
9352 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9353 if (*entry_text != '\0') {
9354 g_free(ainfo->name);
9355 ainfo->name = g_strdup(entry_text);
9359 g_free(ainfo->content_type);
9360 ainfo->content_type = cnttype;
9363 g_free(ainfo->file);
9367 ainfo->size = (goffset)size;
9369 /* update tree store */
9370 text = to_human_readable(ainfo->size);
9371 gtk_tree_model_get_iter(model, &iter, path);
9372 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9373 COL_MIMETYPE, ainfo->content_type,
9375 COL_NAME, ainfo->name,
9376 COL_CHARSET, ainfo->charset,
9382 gtk_tree_path_free(path);
9385 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9387 label = gtk_label_new(str); \
9388 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9389 GTK_FILL, 0, 0, 0); \
9390 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9392 entry = gtk_entry_new(); \
9393 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9394 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9397 static void compose_attach_property_create(gboolean *cancelled)
9403 GtkWidget *mimetype_entry;
9406 GtkListStore *optmenu_menu;
9407 GtkWidget *path_entry;
9408 GtkWidget *filename_entry;
9411 GtkWidget *cancel_btn;
9412 GList *mime_type_list, *strlist;
9415 debug_print("Creating attach_property window...\n");
9417 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9418 gtk_widget_set_size_request(window, 480, -1);
9419 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9420 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9421 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9422 g_signal_connect(G_OBJECT(window), "delete_event",
9423 G_CALLBACK(attach_property_delete_event),
9425 g_signal_connect(G_OBJECT(window), "key_press_event",
9426 G_CALLBACK(attach_property_key_pressed),
9429 vbox = gtk_vbox_new(FALSE, 8);
9430 gtk_container_add(GTK_CONTAINER(window), vbox);
9432 table = gtk_table_new(4, 2, FALSE);
9433 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9434 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9435 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9437 label = gtk_label_new(_("MIME type"));
9438 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9440 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9441 #if !GTK_CHECK_VERSION(2, 24, 0)
9442 mimetype_entry = gtk_combo_box_entry_new_text();
9444 mimetype_entry = gtk_combo_box_text_new_with_entry();
9446 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9447 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9449 /* stuff with list */
9450 mime_type_list = procmime_get_mime_type_list();
9452 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9453 MimeType *type = (MimeType *) mime_type_list->data;
9456 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9458 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9461 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9462 (GCompareFunc)strcmp2);
9465 for (mime_type_list = strlist; mime_type_list != NULL;
9466 mime_type_list = mime_type_list->next) {
9467 #if !GTK_CHECK_VERSION(2, 24, 0)
9468 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9470 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9472 g_free(mime_type_list->data);
9474 g_list_free(strlist);
9475 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9476 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9478 label = gtk_label_new(_("Encoding"));
9479 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9481 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9483 hbox = gtk_hbox_new(FALSE, 0);
9484 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9485 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9487 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9488 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9490 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9491 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9492 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9493 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9494 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9496 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9498 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9499 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9501 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9502 &ok_btn, GTK_STOCK_OK,
9504 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9505 gtk_widget_grab_default(ok_btn);
9507 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9508 G_CALLBACK(attach_property_ok),
9510 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9511 G_CALLBACK(attach_property_cancel),
9514 gtk_widget_show_all(vbox);
9516 attach_prop.window = window;
9517 attach_prop.mimetype_entry = mimetype_entry;
9518 attach_prop.encoding_optmenu = optmenu;
9519 attach_prop.path_entry = path_entry;
9520 attach_prop.filename_entry = filename_entry;
9521 attach_prop.ok_btn = ok_btn;
9522 attach_prop.cancel_btn = cancel_btn;
9525 #undef SET_LABEL_AND_ENTRY
9527 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9533 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9539 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9540 gboolean *cancelled)
9548 static gboolean attach_property_key_pressed(GtkWidget *widget,
9550 gboolean *cancelled)
9552 if (event && event->keyval == GDK_KEY_Escape) {
9556 if (event && event->keyval == GDK_KEY_Return) {
9564 static void compose_exec_ext_editor(Compose *compose)
9569 GdkNativeWindow socket_wid = 0;
9573 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9574 G_DIR_SEPARATOR, compose);
9576 if (compose_get_ext_editor_uses_socket()) {
9577 /* Only allow one socket */
9578 if (compose->exteditor_socket != NULL) {
9579 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9580 /* Move the focus off of the socket */
9581 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9586 /* Create the receiving GtkSocket */
9587 socket = gtk_socket_new ();
9588 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9589 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9591 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9592 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9593 /* Realize the socket so that we can use its ID */
9594 gtk_widget_realize(socket);
9595 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9596 compose->exteditor_socket = socket;
9599 if (pipe(pipe_fds) < 0) {
9605 if ((pid = fork()) < 0) {
9612 /* close the write side of the pipe */
9615 compose->exteditor_file = g_strdup(tmp);
9616 compose->exteditor_pid = pid;
9618 compose_set_ext_editor_sensitive(compose, FALSE);
9621 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9623 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9625 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9629 } else { /* process-monitoring process */
9635 /* close the read side of the pipe */
9638 if (compose_write_body_to_file(compose, tmp) < 0) {
9639 fd_write_all(pipe_fds[1], "2\n", 2);
9643 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9645 fd_write_all(pipe_fds[1], "1\n", 2);
9649 /* wait until editor is terminated */
9650 waitpid(pid_ed, NULL, 0);
9652 fd_write_all(pipe_fds[1], "0\n", 2);
9659 #endif /* G_OS_UNIX */
9662 static gboolean compose_can_autosave(Compose *compose)
9664 if (compose->privacy_system && compose->use_encryption)
9665 return prefs_common.autosave && prefs_common.autosave_encrypted;
9667 return prefs_common.autosave;
9671 static gboolean compose_get_ext_editor_cmd_valid()
9673 gboolean has_s = FALSE;
9674 gboolean has_w = FALSE;
9675 const gchar *p = prefs_common_get_ext_editor_cmd();
9678 while ((p = strchr(p, '%'))) {
9684 } else if (*p == 'w') {
9695 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9702 cm_return_val_if_fail(file != NULL, -1);
9704 if ((pid = fork()) < 0) {
9709 if (pid != 0) return pid;
9711 /* grandchild process */
9713 if (setpgid(0, getppid()))
9716 if (compose_get_ext_editor_cmd_valid()) {
9717 if (compose_get_ext_editor_uses_socket()) {
9718 p = g_strdup(prefs_common_get_ext_editor_cmd());
9719 s = strstr(p, "%w");
9721 if (strstr(p, "%s") < s)
9722 buf = g_strdup_printf(p, file, socket_wid);
9724 buf = g_strdup_printf(p, socket_wid, file);
9727 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9730 if (prefs_common_get_ext_editor_cmd())
9731 g_warning("External editor command-line is invalid: '%s'",
9732 prefs_common_get_ext_editor_cmd());
9733 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9736 cmdline = strsplit_with_quote(buf, " ", 0);
9738 execvp(cmdline[0], cmdline);
9741 g_strfreev(cmdline);
9746 static gboolean compose_ext_editor_kill(Compose *compose)
9748 pid_t pgid = compose->exteditor_pid * -1;
9751 ret = kill(pgid, 0);
9753 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9757 msg = g_strdup_printf
9758 (_("The external editor is still working.\n"
9759 "Force terminating the process?\n"
9760 "process group id: %d"), -pgid);
9761 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9762 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9766 if (val == G_ALERTALTERNATE) {
9767 g_source_remove(compose->exteditor_tag);
9768 g_io_channel_shutdown(compose->exteditor_ch,
9770 g_io_channel_unref(compose->exteditor_ch);
9772 if (kill(pgid, SIGTERM) < 0) perror("kill");
9773 waitpid(compose->exteditor_pid, NULL, 0);
9775 g_warning("Terminated process group id: %d. "
9776 "Temporary file: %s", -pgid, compose->exteditor_file);
9778 compose_set_ext_editor_sensitive(compose, TRUE);
9780 g_free(compose->exteditor_file);
9781 compose->exteditor_file = NULL;
9782 compose->exteditor_pid = -1;
9783 compose->exteditor_ch = NULL;
9784 compose->exteditor_tag = -1;
9792 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9796 Compose *compose = (Compose *)data;
9799 debug_print("Compose: input from monitoring process\n");
9801 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9806 g_io_channel_shutdown(source, FALSE, NULL);
9807 g_io_channel_unref(source);
9809 waitpid(compose->exteditor_pid, NULL, 0);
9811 if (buf[0] == '0') { /* success */
9812 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9813 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9814 GtkTextIter start, end;
9817 gtk_text_buffer_set_text(buffer, "", -1);
9818 compose_insert_file(compose, compose->exteditor_file);
9819 compose_changed_cb(NULL, compose);
9821 /* Check if we should save the draft or not */
9822 if (compose_can_autosave(compose))
9823 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9825 if (claws_unlink(compose->exteditor_file) < 0)
9826 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9828 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9829 gtk_text_buffer_get_start_iter(buffer, &start);
9830 gtk_text_buffer_get_end_iter(buffer, &end);
9831 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9832 if (chars && strlen(chars) > 0)
9833 compose->modified = TRUE;
9835 } else if (buf[0] == '1') { /* failed */
9836 g_warning("Couldn't exec external editor");
9837 if (claws_unlink(compose->exteditor_file) < 0)
9838 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9839 } else if (buf[0] == '2') {
9840 g_warning("Couldn't write to file");
9841 } else if (buf[0] == '3') {
9842 g_warning("Pipe read failed");
9845 compose_set_ext_editor_sensitive(compose, TRUE);
9847 g_free(compose->exteditor_file);
9848 compose->exteditor_file = NULL;
9849 compose->exteditor_pid = -1;
9850 compose->exteditor_ch = NULL;
9851 compose->exteditor_tag = -1;
9852 if (compose->exteditor_socket) {
9853 gtk_widget_destroy(compose->exteditor_socket);
9854 compose->exteditor_socket = NULL;
9861 static char *ext_editor_menu_entries[] = {
9862 "Menu/Message/Send",
9863 "Menu/Message/SendLater",
9864 "Menu/Message/InsertFile",
9865 "Menu/Message/InsertSig",
9866 "Menu/Message/ReplaceSig",
9867 "Menu/Message/Save",
9868 "Menu/Message/Print",
9873 "Menu/Tools/ShowRuler",
9874 "Menu/Tools/Actions",
9879 static void compose_set_ext_editor_sensitive(Compose *compose,
9884 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9885 cm_menu_set_sensitive_full(compose->ui_manager,
9886 ext_editor_menu_entries[i], sensitive);
9889 if (compose_get_ext_editor_uses_socket()) {
9891 if (compose->exteditor_socket)
9892 gtk_widget_hide(compose->exteditor_socket);
9893 gtk_widget_show(compose->scrolledwin);
9894 if (prefs_common.show_ruler)
9895 gtk_widget_show(compose->ruler_hbox);
9896 /* Fix the focus, as it doesn't go anywhere when the
9897 * socket is hidden or destroyed */
9898 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9900 g_assert (compose->exteditor_socket != NULL);
9901 /* Fix the focus, as it doesn't go anywhere when the
9902 * edit box is hidden */
9903 if (gtk_widget_is_focus(compose->text))
9904 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9905 gtk_widget_hide(compose->scrolledwin);
9906 gtk_widget_hide(compose->ruler_hbox);
9907 gtk_widget_show(compose->exteditor_socket);
9910 gtk_widget_set_sensitive(compose->text, sensitive);
9912 if (compose->toolbar->send_btn)
9913 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9914 if (compose->toolbar->sendl_btn)
9915 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9916 if (compose->toolbar->draft_btn)
9917 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9918 if (compose->toolbar->insert_btn)
9919 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9920 if (compose->toolbar->sig_btn)
9921 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9922 if (compose->toolbar->exteditor_btn)
9923 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9924 if (compose->toolbar->linewrap_current_btn)
9925 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9926 if (compose->toolbar->linewrap_all_btn)
9927 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9930 static gboolean compose_get_ext_editor_uses_socket()
9932 return (prefs_common_get_ext_editor_cmd() &&
9933 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9936 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9938 compose->exteditor_socket = NULL;
9939 /* returning FALSE allows destruction of the socket */
9942 #endif /* G_OS_UNIX */
9945 * compose_undo_state_changed:
9947 * Change the sensivity of the menuentries undo and redo
9949 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9950 gint redo_state, gpointer data)
9952 Compose *compose = (Compose *)data;
9954 switch (undo_state) {
9955 case UNDO_STATE_TRUE:
9956 if (!undostruct->undo_state) {
9957 undostruct->undo_state = TRUE;
9958 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9961 case UNDO_STATE_FALSE:
9962 if (undostruct->undo_state) {
9963 undostruct->undo_state = FALSE;
9964 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9967 case UNDO_STATE_UNCHANGED:
9969 case UNDO_STATE_REFRESH:
9970 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9973 g_warning("Undo state not recognized");
9977 switch (redo_state) {
9978 case UNDO_STATE_TRUE:
9979 if (!undostruct->redo_state) {
9980 undostruct->redo_state = TRUE;
9981 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9984 case UNDO_STATE_FALSE:
9985 if (undostruct->redo_state) {
9986 undostruct->redo_state = FALSE;
9987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9990 case UNDO_STATE_UNCHANGED:
9992 case UNDO_STATE_REFRESH:
9993 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9996 g_warning("Redo state not recognized");
10001 /* callback functions */
10003 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10004 GtkAllocation *allocation,
10007 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10010 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10011 * includes "non-client" (windows-izm) in calculation, so this calculation
10012 * may not be accurate.
10014 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10015 GtkAllocation *allocation,
10016 GtkSHRuler *shruler)
10018 if (prefs_common.show_ruler) {
10019 gint char_width = 0, char_height = 0;
10020 gint line_width_in_chars;
10022 gtkut_get_font_size(GTK_WIDGET(widget),
10023 &char_width, &char_height);
10024 line_width_in_chars =
10025 (allocation->width - allocation->x) / char_width;
10027 /* got the maximum */
10028 gtk_shruler_set_range(GTK_SHRULER(shruler),
10029 0.0, line_width_in_chars, 0);
10038 ComposePrefType type;
10039 gboolean entry_marked;
10040 } HeaderEntryState;
10042 static void account_activated(GtkComboBox *optmenu, gpointer data)
10044 Compose *compose = (Compose *)data;
10047 gchar *folderidentifier;
10048 gint account_id = 0;
10049 GtkTreeModel *menu;
10051 GSList *list, *saved_list = NULL;
10052 HeaderEntryState *state;
10054 /* Get ID of active account in the combo box */
10055 menu = gtk_combo_box_get_model(optmenu);
10056 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10057 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10059 ac = account_find_from_id(account_id);
10060 cm_return_if_fail(ac != NULL);
10062 if (ac != compose->account) {
10063 compose_select_account(compose, ac, FALSE);
10065 for (list = compose->header_list; list; list = list->next) {
10066 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10068 if (hentry->type == PREF_ACCOUNT || !list->next) {
10069 compose_destroy_headerentry(compose, hentry);
10072 state = g_malloc0(sizeof(HeaderEntryState));
10073 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10074 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10075 state->entry = gtk_editable_get_chars(
10076 GTK_EDITABLE(hentry->entry), 0, -1);
10077 state->type = hentry->type;
10079 saved_list = g_slist_append(saved_list, state);
10080 compose_destroy_headerentry(compose, hentry);
10083 compose->header_last = NULL;
10084 g_slist_free(compose->header_list);
10085 compose->header_list = NULL;
10086 compose->header_nextrow = 1;
10087 compose_create_header_entry(compose);
10089 if (ac->set_autocc && ac->auto_cc)
10090 compose_entry_append(compose, ac->auto_cc,
10091 COMPOSE_CC, PREF_ACCOUNT);
10092 if (ac->set_autobcc && ac->auto_bcc)
10093 compose_entry_append(compose, ac->auto_bcc,
10094 COMPOSE_BCC, PREF_ACCOUNT);
10095 if (ac->set_autoreplyto && ac->auto_replyto)
10096 compose_entry_append(compose, ac->auto_replyto,
10097 COMPOSE_REPLYTO, PREF_ACCOUNT);
10099 for (list = saved_list; list; list = list->next) {
10100 state = (HeaderEntryState *) list->data;
10102 compose_add_header_entry(compose, state->header,
10103 state->entry, state->type);
10105 g_free(state->header);
10106 g_free(state->entry);
10109 g_slist_free(saved_list);
10111 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10112 (ac->protocol == A_NNTP) ?
10113 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10116 /* Set message save folder */
10117 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10118 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10120 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10121 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10123 compose_set_save_to(compose, NULL);
10124 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10125 folderidentifier = folder_item_get_identifier(account_get_special_folder
10126 (compose->account, F_OUTBOX));
10127 compose_set_save_to(compose, folderidentifier);
10128 g_free(folderidentifier);
10132 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10133 GtkTreeViewColumn *column, Compose *compose)
10135 compose_attach_property(NULL, compose);
10138 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10141 Compose *compose = (Compose *)data;
10142 GtkTreeSelection *attach_selection;
10143 gint attach_nr_selected;
10146 if (!event) return FALSE;
10148 if (event->button == 3) {
10149 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10150 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10152 /* If no rows, or just one row is selected, right-click should
10153 * open menu relevant to the row being right-clicked on. We
10154 * achieve that by selecting the clicked row first. If more
10155 * than one row is selected, we shouldn't modify the selection,
10156 * as user may want to remove selected rows (attachments). */
10157 if (attach_nr_selected < 2) {
10158 gtk_tree_selection_unselect_all(attach_selection);
10159 attach_nr_selected = 0;
10160 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10161 event->x, event->y, &path, NULL, NULL, NULL);
10162 if (path != NULL) {
10163 gtk_tree_selection_select_path(attach_selection, path);
10164 gtk_tree_path_free(path);
10165 attach_nr_selected++;
10169 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10170 /* Properties menu item makes no sense with more than one row
10171 * selected, the properties dialog can only edit one attachment. */
10172 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10174 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10175 NULL, NULL, event->button, event->time);
10182 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10185 Compose *compose = (Compose *)data;
10187 if (!event) return FALSE;
10189 switch (event->keyval) {
10190 case GDK_KEY_Delete:
10191 compose_attach_remove_selected(NULL, compose);
10197 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10199 toolbar_comp_set_sensitive(compose, allow);
10200 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10201 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10203 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10205 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10206 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10207 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10209 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10213 static void compose_send_cb(GtkAction *action, gpointer data)
10215 Compose *compose = (Compose *)data;
10218 if (compose->exteditor_tag != -1) {
10219 debug_print("ignoring send: external editor still open\n");
10223 if (prefs_common.work_offline &&
10224 !inc_offline_should_override(TRUE,
10225 _("Claws Mail needs network access in order "
10226 "to send this email.")))
10229 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10230 g_source_remove(compose->draft_timeout_tag);
10231 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10234 compose_send(compose);
10237 static void compose_send_later_cb(GtkAction *action, gpointer data)
10239 Compose *compose = (Compose *)data;
10243 compose_allow_user_actions(compose, FALSE);
10244 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10245 compose_allow_user_actions(compose, TRUE);
10249 compose_close(compose);
10250 } else if (val == -1) {
10251 alertpanel_error(_("Could not queue message."));
10252 } else if (val == -2) {
10253 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10254 } else if (val == -3) {
10255 if (privacy_peek_error())
10256 alertpanel_error(_("Could not queue message for sending:\n\n"
10257 "Signature failed: %s"), privacy_get_error());
10258 } else if (val == -4) {
10259 alertpanel_error(_("Could not queue message for sending:\n\n"
10260 "Charset conversion failed."));
10261 } else if (val == -5) {
10262 alertpanel_error(_("Could not queue message for sending:\n\n"
10263 "Couldn't get recipient encryption key."));
10264 } else if (val == -6) {
10267 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10270 #define DRAFTED_AT_EXIT "drafted_at_exit"
10271 static void compose_register_draft(MsgInfo *info)
10273 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10274 DRAFTED_AT_EXIT, NULL);
10275 FILE *fp = g_fopen(filepath, "ab");
10278 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10286 gboolean compose_draft (gpointer data, guint action)
10288 Compose *compose = (Compose *)data;
10293 MsgFlags flag = {0, 0};
10294 static gboolean lock = FALSE;
10295 MsgInfo *newmsginfo;
10297 gboolean target_locked = FALSE;
10298 gboolean err = FALSE;
10300 if (lock) return FALSE;
10302 if (compose->sending)
10305 draft = account_get_special_folder(compose->account, F_DRAFT);
10306 cm_return_val_if_fail(draft != NULL, FALSE);
10308 if (!g_mutex_trylock(compose->mutex)) {
10309 /* we don't want to lock the mutex once it's available,
10310 * because as the only other part of compose.c locking
10311 * it is compose_close - which means once unlocked,
10312 * the compose struct will be freed */
10313 debug_print("couldn't lock mutex, probably sending\n");
10319 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10320 G_DIR_SEPARATOR, compose);
10321 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10322 FILE_OP_ERROR(tmp, "fopen");
10326 /* chmod for security */
10327 if (change_file_mode_rw(fp, tmp) < 0) {
10328 FILE_OP_ERROR(tmp, "chmod");
10329 g_warning("can't change file mode");
10332 /* Save draft infos */
10333 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10334 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10336 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10337 gchar *savefolderid;
10339 savefolderid = compose_get_save_to(compose);
10340 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10341 g_free(savefolderid);
10343 if (compose->return_receipt) {
10344 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10346 if (compose->privacy_system) {
10347 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10348 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10349 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10352 /* Message-ID of message replying to */
10353 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10354 gchar *folderid = NULL;
10356 if (compose->replyinfo->folder)
10357 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10358 if (folderid == NULL)
10359 folderid = g_strdup("NULL");
10361 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10364 /* Message-ID of message forwarding to */
10365 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10366 gchar *folderid = NULL;
10368 if (compose->fwdinfo->folder)
10369 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10370 if (folderid == NULL)
10371 folderid = g_strdup("NULL");
10373 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10377 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10378 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10380 sheaders = compose_get_manual_headers_info(compose);
10381 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10384 /* end of headers */
10385 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10392 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10396 if (fclose(fp) == EOF) {
10400 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10401 if (compose->targetinfo) {
10402 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10404 flag.perm_flags |= MSG_LOCKED;
10406 flag.tmp_flags = MSG_DRAFT;
10408 folder_item_scan(draft);
10409 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10410 MsgInfo *tmpinfo = NULL;
10411 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10412 if (compose->msgid) {
10413 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10416 msgnum = tmpinfo->msgnum;
10417 procmsg_msginfo_free(&tmpinfo);
10418 debug_print("got draft msgnum %d from scanning\n", msgnum);
10420 debug_print("didn't get draft msgnum after scanning\n");
10423 debug_print("got draft msgnum %d from adding\n", msgnum);
10429 if (action != COMPOSE_AUTO_SAVE) {
10430 if (action != COMPOSE_DRAFT_FOR_EXIT)
10431 alertpanel_error(_("Could not save draft."));
10434 gtkut_window_popup(compose->window);
10435 val = alertpanel_full(_("Could not save draft"),
10436 _("Could not save draft.\n"
10437 "Do you want to cancel exit or discard this email?"),
10438 _("_Cancel exit"), _("_Discard email"), NULL,
10439 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10440 if (val == G_ALERTALTERNATE) {
10442 g_mutex_unlock(compose->mutex); /* must be done before closing */
10443 compose_close(compose);
10447 g_mutex_unlock(compose->mutex); /* must be done before closing */
10456 if (compose->mode == COMPOSE_REEDIT) {
10457 compose_remove_reedit_target(compose, TRUE);
10460 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10463 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10465 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10467 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10468 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10469 procmsg_msginfo_set_flags(newmsginfo, 0,
10470 MSG_HAS_ATTACHMENT);
10472 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10473 compose_register_draft(newmsginfo);
10475 procmsg_msginfo_free(&newmsginfo);
10478 folder_item_scan(draft);
10480 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10482 g_mutex_unlock(compose->mutex); /* must be done before closing */
10483 compose_close(compose);
10495 goffset size, mtime;
10497 path = folder_item_fetch_msg(draft, msgnum);
10498 if (path == NULL) {
10499 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10503 f = g_file_new_for_path(path);
10504 fi = g_file_query_info(f, "standard::size,time::modified",
10505 G_FILE_QUERY_INFO_NONE, NULL, &error);
10506 if (error != NULL) {
10507 debug_print("couldn't query file info for '%s': %s\n",
10508 path, error->message);
10509 g_error_free(error);
10514 size = g_file_info_get_size(fi);
10515 g_file_info_get_modification_time(fi, &tv);
10517 g_object_unref(fi);
10521 if (g_stat(path, &s) < 0) {
10522 FILE_OP_ERROR(path, "stat");
10527 mtime = s.st_mtime;
10531 procmsg_msginfo_free(&(compose->targetinfo));
10532 compose->targetinfo = procmsg_msginfo_new();
10533 compose->targetinfo->msgnum = msgnum;
10534 compose->targetinfo->size = size;
10535 compose->targetinfo->mtime = mtime;
10536 compose->targetinfo->folder = draft;
10538 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10539 compose->mode = COMPOSE_REEDIT;
10541 if (action == COMPOSE_AUTO_SAVE) {
10542 compose->autosaved_draft = compose->targetinfo;
10544 compose->modified = FALSE;
10545 compose_set_title(compose);
10549 g_mutex_unlock(compose->mutex);
10553 void compose_clear_exit_drafts(void)
10555 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10556 DRAFTED_AT_EXIT, NULL);
10557 if (is_file_exist(filepath))
10558 claws_unlink(filepath);
10563 void compose_reopen_exit_drafts(void)
10565 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10566 DRAFTED_AT_EXIT, NULL);
10567 FILE *fp = g_fopen(filepath, "rb");
10571 while (fgets(buf, sizeof(buf), fp)) {
10572 gchar **parts = g_strsplit(buf, "\t", 2);
10573 const gchar *folder = parts[0];
10574 int msgnum = parts[1] ? atoi(parts[1]):-1;
10576 if (folder && *folder && msgnum > -1) {
10577 FolderItem *item = folder_find_item_from_identifier(folder);
10578 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10580 compose_reedit(info, FALSE);
10587 compose_clear_exit_drafts();
10590 static void compose_save_cb(GtkAction *action, gpointer data)
10592 Compose *compose = (Compose *)data;
10593 compose_draft(compose, COMPOSE_KEEP_EDITING);
10594 compose->rmode = COMPOSE_REEDIT;
10597 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10599 if (compose && file_list) {
10602 for ( tmp = file_list; tmp; tmp = tmp->next) {
10603 gchar *file = (gchar *) tmp->data;
10604 gchar *utf8_filename = conv_filename_to_utf8(file);
10605 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10606 compose_changed_cb(NULL, compose);
10611 g_free(utf8_filename);
10616 static void compose_attach_cb(GtkAction *action, gpointer data)
10618 Compose *compose = (Compose *)data;
10621 if (compose->redirect_filename != NULL)
10624 /* Set focus_window properly, in case we were called via popup menu,
10625 * which unsets it (via focus_out_event callback on compose window). */
10626 manage_window_focus_in(compose->window, NULL, NULL);
10628 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10631 compose_attach_from_list(compose, file_list, TRUE);
10632 g_list_free(file_list);
10636 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10638 Compose *compose = (Compose *)data;
10640 gint files_inserted = 0;
10642 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10647 for ( tmp = file_list; tmp; tmp = tmp->next) {
10648 gchar *file = (gchar *) tmp->data;
10649 gchar *filedup = g_strdup(file);
10650 gchar *shortfile = g_path_get_basename(filedup);
10651 ComposeInsertResult res;
10652 /* insert the file if the file is short or if the user confirmed that
10653 he/she wants to insert the large file */
10654 res = compose_insert_file(compose, file);
10655 if (res == COMPOSE_INSERT_READ_ERROR) {
10656 alertpanel_error(_("File '%s' could not be read."), shortfile);
10657 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10658 alertpanel_error(_("File '%s' contained invalid characters\n"
10659 "for the current encoding, insertion may be incorrect."),
10661 } else if (res == COMPOSE_INSERT_SUCCESS)
10668 g_list_free(file_list);
10672 if (files_inserted > 0 && compose->gtkaspell &&
10673 compose->gtkaspell->check_while_typing)
10674 gtkaspell_highlight_all(compose->gtkaspell);
10678 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10680 Compose *compose = (Compose *)data;
10682 compose_insert_sig(compose, FALSE);
10685 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10687 Compose *compose = (Compose *)data;
10689 compose_insert_sig(compose, TRUE);
10692 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10696 Compose *compose = (Compose *)data;
10698 gtkut_widget_get_uposition(widget, &x, &y);
10699 if (!compose->batch) {
10700 prefs_common.compose_x = x;
10701 prefs_common.compose_y = y;
10703 if (compose->sending || compose->updating)
10705 compose_close_cb(NULL, compose);
10709 void compose_close_toolbar(Compose *compose)
10711 compose_close_cb(NULL, compose);
10714 static void compose_close_cb(GtkAction *action, gpointer data)
10716 Compose *compose = (Compose *)data;
10720 if (compose->exteditor_tag != -1) {
10721 if (!compose_ext_editor_kill(compose))
10726 if (compose->modified) {
10727 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10728 if (!g_mutex_trylock(compose->mutex)) {
10729 /* we don't want to lock the mutex once it's available,
10730 * because as the only other part of compose.c locking
10731 * it is compose_close - which means once unlocked,
10732 * the compose struct will be freed */
10733 debug_print("couldn't lock mutex, probably sending\n");
10737 val = alertpanel(_("Discard message"),
10738 _("This message has been modified. Discard it?"),
10739 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10741 val = alertpanel(_("Save changes"),
10742 _("This message has been modified. Save the latest changes?"),
10743 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10746 g_mutex_unlock(compose->mutex);
10748 case G_ALERTDEFAULT:
10749 if (compose_can_autosave(compose) && !reedit)
10750 compose_remove_draft(compose);
10752 case G_ALERTALTERNATE:
10753 compose_draft(data, COMPOSE_QUIT_EDITING);
10760 compose_close(compose);
10763 static void compose_print_cb(GtkAction *action, gpointer data)
10765 Compose *compose = (Compose *) data;
10767 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10768 if (compose->targetinfo)
10769 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10772 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10774 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10775 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10776 Compose *compose = (Compose *) data;
10779 compose->out_encoding = (CharSet)value;
10782 static void compose_address_cb(GtkAction *action, gpointer data)
10784 Compose *compose = (Compose *)data;
10786 #ifndef USE_ALT_ADDRBOOK
10787 addressbook_open(compose);
10789 GError* error = NULL;
10790 addressbook_connect_signals(compose);
10791 addressbook_dbus_open(TRUE, &error);
10793 g_warning("%s", error->message);
10794 g_error_free(error);
10799 static void about_show_cb(GtkAction *action, gpointer data)
10804 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10806 Compose *compose = (Compose *)data;
10811 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10812 cm_return_if_fail(tmpl != NULL);
10814 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10816 val = alertpanel(_("Apply template"), msg,
10817 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10820 if (val == G_ALERTDEFAULT)
10821 compose_template_apply(compose, tmpl, TRUE);
10822 else if (val == G_ALERTALTERNATE)
10823 compose_template_apply(compose, tmpl, FALSE);
10826 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10828 Compose *compose = (Compose *)data;
10831 if (compose->exteditor_tag != -1) {
10832 debug_print("ignoring open external editor: external editor still open\n");
10836 compose_exec_ext_editor(compose);
10839 static void compose_undo_cb(GtkAction *action, gpointer data)
10841 Compose *compose = (Compose *)data;
10842 gboolean prev_autowrap = compose->autowrap;
10844 compose->autowrap = FALSE;
10845 undo_undo(compose->undostruct);
10846 compose->autowrap = prev_autowrap;
10849 static void compose_redo_cb(GtkAction *action, gpointer data)
10851 Compose *compose = (Compose *)data;
10852 gboolean prev_autowrap = compose->autowrap;
10854 compose->autowrap = FALSE;
10855 undo_redo(compose->undostruct);
10856 compose->autowrap = prev_autowrap;
10859 static void entry_cut_clipboard(GtkWidget *entry)
10861 if (GTK_IS_EDITABLE(entry))
10862 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10863 else if (GTK_IS_TEXT_VIEW(entry))
10864 gtk_text_buffer_cut_clipboard(
10865 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10866 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10870 static void entry_copy_clipboard(GtkWidget *entry)
10872 if (GTK_IS_EDITABLE(entry))
10873 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10874 else if (GTK_IS_TEXT_VIEW(entry))
10875 gtk_text_buffer_copy_clipboard(
10876 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10877 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10880 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10881 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10883 if (GTK_IS_TEXT_VIEW(entry)) {
10884 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10885 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10886 GtkTextIter start_iter, end_iter;
10888 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10890 if (contents == NULL)
10893 /* we shouldn't delete the selection when middle-click-pasting, or we
10894 * can't mid-click-paste our own selection */
10895 if (clip != GDK_SELECTION_PRIMARY) {
10896 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10897 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10900 if (insert_place == NULL) {
10901 /* if insert_place isn't specified, insert at the cursor.
10902 * used for Ctrl-V pasting */
10903 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10904 start = gtk_text_iter_get_offset(&start_iter);
10905 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10907 /* if insert_place is specified, paste here.
10908 * used for mid-click-pasting */
10909 start = gtk_text_iter_get_offset(insert_place);
10910 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10911 if (prefs_common.primary_paste_unselects)
10912 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10916 /* paste unwrapped: mark the paste so it's not wrapped later */
10917 end = start + strlen(contents);
10918 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10919 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10920 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10921 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10922 /* rewrap paragraph now (after a mid-click-paste) */
10923 mark_start = gtk_text_buffer_get_insert(buffer);
10924 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10925 gtk_text_iter_backward_char(&start_iter);
10926 compose_beautify_paragraph(compose, &start_iter, TRUE);
10928 } else if (GTK_IS_EDITABLE(entry))
10929 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10931 compose->modified = TRUE;
10934 static void entry_allsel(GtkWidget *entry)
10936 if (GTK_IS_EDITABLE(entry))
10937 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10938 else if (GTK_IS_TEXT_VIEW(entry)) {
10939 GtkTextIter startiter, enditer;
10940 GtkTextBuffer *textbuf;
10942 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10943 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10944 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10946 gtk_text_buffer_move_mark_by_name(textbuf,
10947 "selection_bound", &startiter);
10948 gtk_text_buffer_move_mark_by_name(textbuf,
10949 "insert", &enditer);
10953 static void compose_cut_cb(GtkAction *action, gpointer data)
10955 Compose *compose = (Compose *)data;
10956 if (compose->focused_editable
10957 #ifndef GENERIC_UMPC
10958 && gtk_widget_has_focus(compose->focused_editable)
10961 entry_cut_clipboard(compose->focused_editable);
10964 static void compose_copy_cb(GtkAction *action, gpointer data)
10966 Compose *compose = (Compose *)data;
10967 if (compose->focused_editable
10968 #ifndef GENERIC_UMPC
10969 && gtk_widget_has_focus(compose->focused_editable)
10972 entry_copy_clipboard(compose->focused_editable);
10975 static void compose_paste_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,
10987 prefs_common.linewrap_pastes,
10988 GDK_SELECTION_CLIPBOARD, NULL);
10993 #ifndef GENERIC_UMPC
10994 gtk_widget_has_focus(compose->text) &&
10996 compose->gtkaspell &&
10997 compose->gtkaspell->check_while_typing)
10998 gtkaspell_highlight_all(compose->gtkaspell);
11002 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11004 Compose *compose = (Compose *)data;
11005 gint wrap_quote = prefs_common.linewrap_quote;
11006 if (compose->focused_editable
11007 #ifndef GENERIC_UMPC
11008 && gtk_widget_has_focus(compose->focused_editable)
11011 /* let text_insert() (called directly or at a later time
11012 * after the gtk_editable_paste_clipboard) know that
11013 * text is to be inserted as a quotation. implemented
11014 * by using a simple refcount... */
11015 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11016 G_OBJECT(compose->focused_editable),
11017 "paste_as_quotation"));
11018 g_object_set_data(G_OBJECT(compose->focused_editable),
11019 "paste_as_quotation",
11020 GINT_TO_POINTER(paste_as_quotation + 1));
11021 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11022 entry_paste_clipboard(compose, compose->focused_editable,
11023 prefs_common.linewrap_pastes,
11024 GDK_SELECTION_CLIPBOARD, NULL);
11025 prefs_common.linewrap_quote = wrap_quote;
11029 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11031 Compose *compose = (Compose *)data;
11032 gint prev_autowrap;
11033 GtkTextBuffer *buffer;
11035 if (compose->focused_editable
11036 #ifndef GENERIC_UMPC
11037 && gtk_widget_has_focus(compose->focused_editable)
11040 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11041 GDK_SELECTION_CLIPBOARD, NULL);
11046 #ifndef GENERIC_UMPC
11047 gtk_widget_has_focus(compose->text) &&
11049 compose->gtkaspell &&
11050 compose->gtkaspell->check_while_typing)
11051 gtkaspell_highlight_all(compose->gtkaspell);
11055 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11057 Compose *compose = (Compose *)data;
11058 gint prev_autowrap;
11059 GtkTextBuffer *buffer;
11061 if (compose->focused_editable
11062 #ifndef GENERIC_UMPC
11063 && gtk_widget_has_focus(compose->focused_editable)
11066 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11067 GDK_SELECTION_CLIPBOARD, NULL);
11072 #ifndef GENERIC_UMPC
11073 gtk_widget_has_focus(compose->text) &&
11075 compose->gtkaspell &&
11076 compose->gtkaspell->check_while_typing)
11077 gtkaspell_highlight_all(compose->gtkaspell);
11081 static void compose_allsel_cb(GtkAction *action, gpointer data)
11083 Compose *compose = (Compose *)data;
11084 if (compose->focused_editable
11085 #ifndef GENERIC_UMPC
11086 && gtk_widget_has_focus(compose->focused_editable)
11089 entry_allsel(compose->focused_editable);
11092 static void textview_move_beginning_of_line (GtkTextView *text)
11094 GtkTextBuffer *buffer;
11098 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11100 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11101 mark = gtk_text_buffer_get_insert(buffer);
11102 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11103 gtk_text_iter_set_line_offset(&ins, 0);
11104 gtk_text_buffer_place_cursor(buffer, &ins);
11107 static void textview_move_forward_character (GtkTextView *text)
11109 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 if (gtk_text_iter_forward_cursor_position(&ins))
11119 gtk_text_buffer_place_cursor(buffer, &ins);
11122 static void textview_move_backward_character (GtkTextView *text)
11124 GtkTextBuffer *buffer;
11128 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11130 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11131 mark = gtk_text_buffer_get_insert(buffer);
11132 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11133 if (gtk_text_iter_backward_cursor_position(&ins))
11134 gtk_text_buffer_place_cursor(buffer, &ins);
11137 static void textview_move_forward_word (GtkTextView *text)
11139 GtkTextBuffer *buffer;
11144 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11146 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11147 mark = gtk_text_buffer_get_insert(buffer);
11148 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11149 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11150 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11151 gtk_text_iter_backward_word_start(&ins);
11152 gtk_text_buffer_place_cursor(buffer, &ins);
11156 static void textview_move_backward_word (GtkTextView *text)
11158 GtkTextBuffer *buffer;
11162 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11164 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11165 mark = gtk_text_buffer_get_insert(buffer);
11166 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11167 if (gtk_text_iter_backward_word_starts(&ins, 1))
11168 gtk_text_buffer_place_cursor(buffer, &ins);
11171 static void textview_move_end_of_line (GtkTextView *text)
11173 GtkTextBuffer *buffer;
11177 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11179 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11180 mark = gtk_text_buffer_get_insert(buffer);
11181 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11182 if (gtk_text_iter_forward_to_line_end(&ins))
11183 gtk_text_buffer_place_cursor(buffer, &ins);
11186 static void textview_move_next_line (GtkTextView *text)
11188 GtkTextBuffer *buffer;
11193 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11195 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11196 mark = gtk_text_buffer_get_insert(buffer);
11197 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11198 offset = gtk_text_iter_get_line_offset(&ins);
11199 if (gtk_text_iter_forward_line(&ins)) {
11200 gtk_text_iter_set_line_offset(&ins, offset);
11201 gtk_text_buffer_place_cursor(buffer, &ins);
11205 static void textview_move_previous_line (GtkTextView *text)
11207 GtkTextBuffer *buffer;
11212 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11214 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11215 mark = gtk_text_buffer_get_insert(buffer);
11216 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11217 offset = gtk_text_iter_get_line_offset(&ins);
11218 if (gtk_text_iter_backward_line(&ins)) {
11219 gtk_text_iter_set_line_offset(&ins, offset);
11220 gtk_text_buffer_place_cursor(buffer, &ins);
11224 static void textview_delete_forward_character (GtkTextView *text)
11226 GtkTextBuffer *buffer;
11228 GtkTextIter ins, end_iter;
11230 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11232 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11233 mark = gtk_text_buffer_get_insert(buffer);
11234 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11236 if (gtk_text_iter_forward_char(&end_iter)) {
11237 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11241 static void textview_delete_backward_character (GtkTextView *text)
11243 GtkTextBuffer *buffer;
11245 GtkTextIter ins, end_iter;
11247 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11249 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11250 mark = gtk_text_buffer_get_insert(buffer);
11251 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11253 if (gtk_text_iter_backward_char(&end_iter)) {
11254 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11258 static void textview_delete_forward_word (GtkTextView *text)
11260 GtkTextBuffer *buffer;
11262 GtkTextIter ins, end_iter;
11264 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11266 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11267 mark = gtk_text_buffer_get_insert(buffer);
11268 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11270 if (gtk_text_iter_forward_word_end(&end_iter)) {
11271 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11275 static void textview_delete_backward_word (GtkTextView *text)
11277 GtkTextBuffer *buffer;
11279 GtkTextIter ins, end_iter;
11281 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11283 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11284 mark = gtk_text_buffer_get_insert(buffer);
11285 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11287 if (gtk_text_iter_backward_word_start(&end_iter)) {
11288 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11292 static void textview_delete_line (GtkTextView *text)
11294 GtkTextBuffer *buffer;
11296 GtkTextIter ins, start_iter, end_iter;
11298 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11300 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11301 mark = gtk_text_buffer_get_insert(buffer);
11302 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11305 gtk_text_iter_set_line_offset(&start_iter, 0);
11308 if (gtk_text_iter_ends_line(&end_iter)){
11309 if (!gtk_text_iter_forward_char(&end_iter))
11310 gtk_text_iter_backward_char(&start_iter);
11313 gtk_text_iter_forward_to_line_end(&end_iter);
11314 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11317 static void textview_delete_to_line_end (GtkTextView *text)
11319 GtkTextBuffer *buffer;
11321 GtkTextIter ins, end_iter;
11323 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11325 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11326 mark = gtk_text_buffer_get_insert(buffer);
11327 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11329 if (gtk_text_iter_ends_line(&end_iter))
11330 gtk_text_iter_forward_char(&end_iter);
11332 gtk_text_iter_forward_to_line_end(&end_iter);
11333 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11336 #define DO_ACTION(name, act) { \
11337 if(!strcmp(name, a_name)) { \
11341 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11343 const gchar *a_name = gtk_action_get_name(action);
11344 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11345 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11346 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11347 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11348 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11349 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11350 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11351 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11352 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11353 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11354 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11355 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11356 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11357 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11358 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11361 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11363 Compose *compose = (Compose *)data;
11364 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11365 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11367 action = compose_call_advanced_action_from_path(gaction);
11370 void (*do_action) (GtkTextView *text);
11371 } action_table[] = {
11372 {textview_move_beginning_of_line},
11373 {textview_move_forward_character},
11374 {textview_move_backward_character},
11375 {textview_move_forward_word},
11376 {textview_move_backward_word},
11377 {textview_move_end_of_line},
11378 {textview_move_next_line},
11379 {textview_move_previous_line},
11380 {textview_delete_forward_character},
11381 {textview_delete_backward_character},
11382 {textview_delete_forward_word},
11383 {textview_delete_backward_word},
11384 {textview_delete_line},
11385 {textview_delete_to_line_end}
11388 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11390 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11391 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11392 if (action_table[action].do_action)
11393 action_table[action].do_action(text);
11395 g_warning("Not implemented yet.");
11399 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11401 GtkAllocation allocation;
11405 if (GTK_IS_EDITABLE(widget)) {
11406 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11407 gtk_editable_set_position(GTK_EDITABLE(widget),
11410 if ((parent = gtk_widget_get_parent(widget))
11411 && (parent = gtk_widget_get_parent(parent))
11412 && (parent = gtk_widget_get_parent(parent))) {
11413 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11414 gtk_widget_get_allocation(widget, &allocation);
11415 gint y = allocation.y;
11416 gint height = allocation.height;
11417 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11418 (GTK_SCROLLED_WINDOW(parent));
11420 gfloat value = gtk_adjustment_get_value(shown);
11421 gfloat upper = gtk_adjustment_get_upper(shown);
11422 gfloat page_size = gtk_adjustment_get_page_size(shown);
11423 if (y < (int)value) {
11424 gtk_adjustment_set_value(shown, y - 1);
11426 if ((y + height) > ((int)value + (int)page_size)) {
11427 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11428 gtk_adjustment_set_value(shown,
11429 y + height - (int)page_size - 1);
11431 gtk_adjustment_set_value(shown,
11432 (int)upper - (int)page_size - 1);
11439 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11440 compose->focused_editable = widget;
11442 #ifdef GENERIC_UMPC
11443 if (GTK_IS_TEXT_VIEW(widget)
11444 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11445 g_object_ref(compose->notebook);
11446 g_object_ref(compose->edit_vbox);
11447 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11448 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11449 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11450 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11451 g_object_unref(compose->notebook);
11452 g_object_unref(compose->edit_vbox);
11453 g_signal_handlers_block_by_func(G_OBJECT(widget),
11454 G_CALLBACK(compose_grab_focus_cb),
11456 gtk_widget_grab_focus(widget);
11457 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11458 G_CALLBACK(compose_grab_focus_cb),
11460 } else if (!GTK_IS_TEXT_VIEW(widget)
11461 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11462 g_object_ref(compose->notebook);
11463 g_object_ref(compose->edit_vbox);
11464 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11465 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11466 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11467 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11468 g_object_unref(compose->notebook);
11469 g_object_unref(compose->edit_vbox);
11470 g_signal_handlers_block_by_func(G_OBJECT(widget),
11471 G_CALLBACK(compose_grab_focus_cb),
11473 gtk_widget_grab_focus(widget);
11474 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11475 G_CALLBACK(compose_grab_focus_cb),
11481 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11483 compose->modified = TRUE;
11484 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11485 #ifndef GENERIC_UMPC
11486 compose_set_title(compose);
11490 static void compose_wrap_cb(GtkAction *action, gpointer data)
11492 Compose *compose = (Compose *)data;
11493 compose_beautify_paragraph(compose, NULL, TRUE);
11496 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11498 Compose *compose = (Compose *)data;
11499 compose_wrap_all_full(compose, TRUE);
11502 static void compose_find_cb(GtkAction *action, gpointer data)
11504 Compose *compose = (Compose *)data;
11506 message_search_compose(compose);
11509 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11512 Compose *compose = (Compose *)data;
11513 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11514 if (compose->autowrap)
11515 compose_wrap_all_full(compose, TRUE);
11516 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11519 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11522 Compose *compose = (Compose *)data;
11523 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11526 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11528 Compose *compose = (Compose *)data;
11530 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11531 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11534 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11536 Compose *compose = (Compose *)data;
11538 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11539 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11542 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11544 g_free(compose->privacy_system);
11545 g_free(compose->encdata);
11547 compose->privacy_system = g_strdup(account->default_privacy_system);
11548 compose_update_privacy_system_menu_item(compose, warn);
11551 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11553 Compose *compose = (Compose *)data;
11555 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11556 gtk_widget_show(compose->ruler_hbox);
11557 prefs_common.show_ruler = TRUE;
11559 gtk_widget_hide(compose->ruler_hbox);
11560 gtk_widget_queue_resize(compose->edit_vbox);
11561 prefs_common.show_ruler = FALSE;
11565 static void compose_attach_drag_received_cb (GtkWidget *widget,
11566 GdkDragContext *context,
11569 GtkSelectionData *data,
11572 gpointer user_data)
11574 Compose *compose = (Compose *)user_data;
11578 type = gtk_selection_data_get_data_type(data);
11579 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11580 && gtk_drag_get_source_widget(context) !=
11581 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11582 list = uri_list_extract_filenames(
11583 (const gchar *)gtk_selection_data_get_data(data));
11584 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11585 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11586 compose_attach_append
11587 (compose, (const gchar *)tmp->data,
11588 utf8_filename, NULL, NULL);
11589 g_free(utf8_filename);
11591 if (list) compose_changed_cb(NULL, compose);
11592 list_free_strings(list);
11594 } else if (gtk_drag_get_source_widget(context)
11595 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11596 /* comes from our summaryview */
11597 SummaryView * summaryview = NULL;
11598 GSList * list = NULL, *cur = NULL;
11600 if (mainwindow_get_mainwindow())
11601 summaryview = mainwindow_get_mainwindow()->summaryview;
11604 list = summary_get_selected_msg_list(summaryview);
11606 for (cur = list; cur; cur = cur->next) {
11607 MsgInfo *msginfo = (MsgInfo *)cur->data;
11608 gchar *file = NULL;
11610 file = procmsg_get_message_file_full(msginfo,
11613 compose_attach_append(compose, (const gchar *)file,
11614 (const gchar *)file, "message/rfc822", NULL);
11618 g_slist_free(list);
11622 static gboolean compose_drag_drop(GtkWidget *widget,
11623 GdkDragContext *drag_context,
11625 guint time, gpointer user_data)
11627 /* not handling this signal makes compose_insert_drag_received_cb
11632 static gboolean completion_set_focus_to_subject
11633 (GtkWidget *widget,
11634 GdkEventKey *event,
11637 cm_return_val_if_fail(compose != NULL, FALSE);
11639 /* make backtab move to subject field */
11640 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11641 gtk_widget_grab_focus(compose->subject_entry);
11647 static void compose_insert_drag_received_cb (GtkWidget *widget,
11648 GdkDragContext *drag_context,
11651 GtkSelectionData *data,
11654 gpointer user_data)
11656 Compose *compose = (Compose *)user_data;
11662 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11664 type = gtk_selection_data_get_data_type(data);
11665 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11666 AlertValue val = G_ALERTDEFAULT;
11667 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11669 list = uri_list_extract_filenames(ddata);
11670 num_files = g_list_length(list);
11671 if (list == NULL && strstr(ddata, "://")) {
11672 /* Assume a list of no files, and data has ://, is a remote link */
11673 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11674 gchar *tmpfile = get_tmp_file();
11675 str_write_to_file(tmpdata, tmpfile);
11677 compose_insert_file(compose, tmpfile);
11678 claws_unlink(tmpfile);
11680 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11681 compose_beautify_paragraph(compose, NULL, TRUE);
11684 switch (prefs_common.compose_dnd_mode) {
11685 case COMPOSE_DND_ASK:
11686 msg = g_strdup_printf(
11688 "Do you want to insert the contents of the file "
11689 "into the message body, or attach it to the email?",
11690 "Do you want to insert the contents of the %d files "
11691 "into the message body, or attach them to the email?",
11694 val = alertpanel_full(_("Insert or attach?"), msg,
11695 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11696 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11699 case COMPOSE_DND_INSERT:
11700 val = G_ALERTALTERNATE;
11702 case COMPOSE_DND_ATTACH:
11703 val = G_ALERTOTHER;
11706 /* unexpected case */
11707 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11710 if (val & G_ALERTDISABLE) {
11711 val &= ~G_ALERTDISABLE;
11712 /* remember what action to perform by default, only if we don't click Cancel */
11713 if (val == G_ALERTALTERNATE)
11714 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11715 else if (val == G_ALERTOTHER)
11716 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11719 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11720 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11721 list_free_strings(list);
11724 } else if (val == G_ALERTOTHER) {
11725 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11726 list_free_strings(list);
11731 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11732 compose_insert_file(compose, (const gchar *)tmp->data);
11734 list_free_strings(list);
11736 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11741 static void compose_header_drag_received_cb (GtkWidget *widget,
11742 GdkDragContext *drag_context,
11745 GtkSelectionData *data,
11748 gpointer user_data)
11750 GtkEditable *entry = (GtkEditable *)user_data;
11751 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11753 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11756 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11757 gchar *decoded=g_new(gchar, strlen(email));
11760 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11761 gtk_editable_delete_text(entry, 0, -1);
11762 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11763 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11767 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11770 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11772 Compose *compose = (Compose *)data;
11774 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11775 compose->return_receipt = TRUE;
11777 compose->return_receipt = FALSE;
11780 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11782 Compose *compose = (Compose *)data;
11784 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11785 compose->remove_references = TRUE;
11787 compose->remove_references = FALSE;
11790 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11791 ComposeHeaderEntry *headerentry)
11793 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11794 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11795 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11799 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11800 GdkEventKey *event,
11801 ComposeHeaderEntry *headerentry)
11803 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11804 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11805 !(event->state & GDK_MODIFIER_MASK) &&
11806 (event->keyval == GDK_KEY_BackSpace) &&
11807 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11808 gtk_container_remove
11809 (GTK_CONTAINER(headerentry->compose->header_table),
11810 headerentry->combo);
11811 gtk_container_remove
11812 (GTK_CONTAINER(headerentry->compose->header_table),
11813 headerentry->entry);
11814 headerentry->compose->header_list =
11815 g_slist_remove(headerentry->compose->header_list,
11817 g_free(headerentry);
11818 } else if (event->keyval == GDK_KEY_Tab) {
11819 if (headerentry->compose->header_last == headerentry) {
11820 /* Override default next focus, and give it to subject_entry
11821 * instead of notebook tabs
11823 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11824 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11831 static gboolean scroll_postpone(gpointer data)
11833 Compose *compose = (Compose *)data;
11835 if (compose->batch)
11838 GTK_EVENTS_FLUSH();
11839 compose_show_first_last_header(compose, FALSE);
11843 static void compose_headerentry_changed_cb(GtkWidget *entry,
11844 ComposeHeaderEntry *headerentry)
11846 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11847 compose_create_header_entry(headerentry->compose);
11848 g_signal_handlers_disconnect_matched
11849 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11850 0, 0, NULL, NULL, headerentry);
11852 if (!headerentry->compose->batch)
11853 g_timeout_add(0, scroll_postpone, headerentry->compose);
11857 static gboolean compose_defer_auto_save_draft(Compose *compose)
11859 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11860 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11864 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11866 GtkAdjustment *vadj;
11868 cm_return_if_fail(compose);
11873 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11874 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11875 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11876 gtk_widget_get_parent(compose->header_table)));
11877 gtk_adjustment_set_value(vadj, (show_first ?
11878 gtk_adjustment_get_lower(vadj) :
11879 (gtk_adjustment_get_upper(vadj) -
11880 gtk_adjustment_get_page_size(vadj))));
11881 gtk_adjustment_changed(vadj);
11884 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11885 const gchar *text, gint len, Compose *compose)
11887 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11888 (G_OBJECT(compose->text), "paste_as_quotation"));
11891 cm_return_if_fail(text != NULL);
11893 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11894 G_CALLBACK(text_inserted),
11896 if (paste_as_quotation) {
11898 const gchar *qmark;
11900 GtkTextIter start_iter;
11903 len = strlen(text);
11905 new_text = g_strndup(text, len);
11907 qmark = compose_quote_char_from_context(compose);
11909 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11910 gtk_text_buffer_place_cursor(buffer, iter);
11912 pos = gtk_text_iter_get_offset(iter);
11914 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11915 _("Quote format error at line %d."));
11916 quote_fmt_reset_vartable();
11918 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11919 GINT_TO_POINTER(paste_as_quotation - 1));
11921 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11922 gtk_text_buffer_place_cursor(buffer, iter);
11923 gtk_text_buffer_delete_mark(buffer, mark);
11925 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11926 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11927 compose_beautify_paragraph(compose, &start_iter, FALSE);
11928 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11929 gtk_text_buffer_delete_mark(buffer, mark);
11931 if (strcmp(text, "\n") || compose->automatic_break
11932 || gtk_text_iter_starts_line(iter)) {
11933 GtkTextIter before_ins;
11934 gtk_text_buffer_insert(buffer, iter, text, len);
11935 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11936 before_ins = *iter;
11937 gtk_text_iter_backward_chars(&before_ins, len);
11938 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11941 /* check if the preceding is just whitespace or quote */
11942 GtkTextIter start_line;
11943 gchar *tmp = NULL, *quote = NULL;
11944 gint quote_len = 0, is_normal = 0;
11945 start_line = *iter;
11946 gtk_text_iter_set_line_offset(&start_line, 0);
11947 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11950 if (*tmp == '\0') {
11953 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11961 gtk_text_buffer_insert(buffer, iter, text, len);
11963 gtk_text_buffer_insert_with_tags_by_name(buffer,
11964 iter, text, len, "no_join", NULL);
11969 if (!paste_as_quotation) {
11970 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11971 compose_beautify_paragraph(compose, iter, FALSE);
11972 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11973 gtk_text_buffer_delete_mark(buffer, mark);
11976 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11977 G_CALLBACK(text_inserted),
11979 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11981 if (compose_can_autosave(compose) &&
11982 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11983 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11984 compose->draft_timeout_tag = g_timeout_add
11985 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11989 static void compose_check_all(GtkAction *action, gpointer data)
11991 Compose *compose = (Compose *)data;
11992 if (!compose->gtkaspell)
11995 if (gtk_widget_has_focus(compose->subject_entry))
11996 claws_spell_entry_check_all(
11997 CLAWS_SPELL_ENTRY(compose->subject_entry));
11999 gtkaspell_check_all(compose->gtkaspell);
12002 static void compose_highlight_all(GtkAction *action, gpointer data)
12004 Compose *compose = (Compose *)data;
12005 if (compose->gtkaspell) {
12006 claws_spell_entry_recheck_all(
12007 CLAWS_SPELL_ENTRY(compose->subject_entry));
12008 gtkaspell_highlight_all(compose->gtkaspell);
12012 static void compose_check_backwards(GtkAction *action, gpointer data)
12014 Compose *compose = (Compose *)data;
12015 if (!compose->gtkaspell) {
12016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12020 if (gtk_widget_has_focus(compose->subject_entry))
12021 claws_spell_entry_check_backwards(
12022 CLAWS_SPELL_ENTRY(compose->subject_entry));
12024 gtkaspell_check_backwards(compose->gtkaspell);
12027 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12029 Compose *compose = (Compose *)data;
12030 if (!compose->gtkaspell) {
12031 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12035 if (gtk_widget_has_focus(compose->subject_entry))
12036 claws_spell_entry_check_forwards_go(
12037 CLAWS_SPELL_ENTRY(compose->subject_entry));
12039 gtkaspell_check_forwards_go(compose->gtkaspell);
12044 *\brief Guess originating forward account from MsgInfo and several
12045 * "common preference" settings. Return NULL if no guess.
12047 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12049 PrefsAccount *account = NULL;
12051 cm_return_val_if_fail(msginfo, NULL);
12052 cm_return_val_if_fail(msginfo->folder, NULL);
12053 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12055 if (msginfo->folder->prefs->enable_default_account)
12056 account = account_find_from_id(msginfo->folder->prefs->default_account);
12058 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12060 Xstrdup_a(to, msginfo->to, return NULL);
12061 extract_address(to);
12062 account = account_find_from_address(to, FALSE);
12065 if (!account && prefs_common.forward_account_autosel) {
12067 if (!procheader_get_header_from_msginfo
12068 (msginfo, &cc, "Cc:")) {
12069 gchar *buf = cc + strlen("Cc:");
12070 extract_address(buf);
12071 account = account_find_from_address(buf, FALSE);
12076 if (!account && prefs_common.forward_account_autosel) {
12077 gchar *deliveredto = NULL;
12078 if (!procheader_get_header_from_msginfo
12079 (msginfo, &deliveredto, "Delivered-To:")) {
12080 gchar *buf = deliveredto + strlen("Delivered-To:");
12081 extract_address(buf);
12082 account = account_find_from_address(buf, FALSE);
12083 g_free(deliveredto);
12088 account = msginfo->folder->folder->account;
12093 gboolean compose_close(Compose *compose)
12097 cm_return_val_if_fail(compose, FALSE);
12099 if (!g_mutex_trylock(compose->mutex)) {
12100 /* we have to wait for the (possibly deferred by auto-save)
12101 * drafting to be done, before destroying the compose under
12103 debug_print("waiting for drafting to finish...\n");
12104 compose_allow_user_actions(compose, FALSE);
12105 if (compose->close_timeout_tag == 0) {
12106 compose->close_timeout_tag =
12107 g_timeout_add (500, (GSourceFunc) compose_close,
12113 if (compose->draft_timeout_tag >= 0) {
12114 g_source_remove(compose->draft_timeout_tag);
12115 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12118 gtkut_widget_get_uposition(compose->window, &x, &y);
12119 if (!compose->batch) {
12120 prefs_common.compose_x = x;
12121 prefs_common.compose_y = y;
12123 g_mutex_unlock(compose->mutex);
12124 compose_destroy(compose);
12129 * Add entry field for each address in list.
12130 * \param compose E-Mail composition object.
12131 * \param listAddress List of (formatted) E-Mail addresses.
12133 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12136 node = listAddress;
12138 addr = ( gchar * ) node->data;
12139 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12140 node = g_list_next( node );
12144 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12145 guint action, gboolean opening_multiple)
12147 gchar *body = NULL;
12148 GSList *new_msglist = NULL;
12149 MsgInfo *tmp_msginfo = NULL;
12150 gboolean originally_enc = FALSE;
12151 gboolean originally_sig = FALSE;
12152 Compose *compose = NULL;
12153 gchar *s_system = NULL;
12155 cm_return_if_fail(msgview != NULL);
12157 cm_return_if_fail(msginfo_list != NULL);
12159 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12160 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12161 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12163 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12164 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12165 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12166 orig_msginfo, mimeinfo);
12167 if (tmp_msginfo != NULL) {
12168 new_msglist = g_slist_append(NULL, tmp_msginfo);
12170 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12171 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12172 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12174 tmp_msginfo->folder = orig_msginfo->folder;
12175 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12176 if (orig_msginfo->tags) {
12177 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12178 tmp_msginfo->folder->tags_dirty = TRUE;
12184 if (!opening_multiple)
12185 body = messageview_get_selection(msgview);
12188 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12189 procmsg_msginfo_free(&tmp_msginfo);
12190 g_slist_free(new_msglist);
12192 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12194 if (compose && originally_enc) {
12195 compose_force_encryption(compose, compose->account, FALSE, s_system);
12198 if (compose && originally_sig && compose->account->default_sign_reply) {
12199 compose_force_signing(compose, compose->account, s_system);
12203 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12206 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12209 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12210 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12211 GSList *cur = msginfo_list;
12212 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12213 "messages. Opening the windows "
12214 "could take some time. Do you "
12215 "want to continue?"),
12216 g_slist_length(msginfo_list));
12217 if (g_slist_length(msginfo_list) > 9
12218 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12219 != G_ALERTALTERNATE) {
12224 /* We'll open multiple compose windows */
12225 /* let the WM place the next windows */
12226 compose_force_window_origin = FALSE;
12227 for (; cur; cur = cur->next) {
12229 tmplist.data = cur->data;
12230 tmplist.next = NULL;
12231 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12233 compose_force_window_origin = TRUE;
12235 /* forwarding multiple mails as attachments is done via a
12236 * single compose window */
12237 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12241 void compose_check_for_email_account(Compose *compose)
12243 PrefsAccount *ac = NULL, *curr = NULL;
12249 if (compose->account && compose->account->protocol == A_NNTP) {
12250 ac = account_get_cur_account();
12251 if (ac->protocol == A_NNTP) {
12252 list = account_get_list();
12254 for( ; list != NULL ; list = g_list_next(list)) {
12255 curr = (PrefsAccount *) list->data;
12256 if (curr->protocol != A_NNTP) {
12262 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12267 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12268 const gchar *address)
12270 GSList *msginfo_list = NULL;
12271 gchar *body = messageview_get_selection(msgview);
12274 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12276 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12277 compose_check_for_email_account(compose);
12278 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12279 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12280 compose_reply_set_subject(compose, msginfo);
12283 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12286 void compose_set_position(Compose *compose, gint pos)
12288 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12290 gtkut_text_view_set_position(text, pos);
12293 gboolean compose_search_string(Compose *compose,
12294 const gchar *str, gboolean case_sens)
12296 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12298 return gtkut_text_view_search_string(text, str, case_sens);
12301 gboolean compose_search_string_backward(Compose *compose,
12302 const gchar *str, gboolean case_sens)
12304 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12306 return gtkut_text_view_search_string_backward(text, str, case_sens);
12309 /* allocate a msginfo structure and populate its data from a compose data structure */
12310 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12312 MsgInfo *newmsginfo;
12314 gchar date[RFC822_DATE_BUFFSIZE];
12316 cm_return_val_if_fail( compose != NULL, NULL );
12318 newmsginfo = procmsg_msginfo_new();
12321 get_rfc822_date(date, sizeof(date));
12322 newmsginfo->date = g_strdup(date);
12325 if (compose->from_name) {
12326 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12327 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12331 if (compose->subject_entry)
12332 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12334 /* to, cc, reply-to, newsgroups */
12335 for (list = compose->header_list; list; list = list->next) {
12336 gchar *header = gtk_editable_get_chars(
12338 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12339 gchar *entry = gtk_editable_get_chars(
12340 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12342 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12343 if ( newmsginfo->to == NULL ) {
12344 newmsginfo->to = g_strdup(entry);
12345 } else if (entry && *entry) {
12346 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12347 g_free(newmsginfo->to);
12348 newmsginfo->to = tmp;
12351 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12352 if ( newmsginfo->cc == NULL ) {
12353 newmsginfo->cc = g_strdup(entry);
12354 } else if (entry && *entry) {
12355 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12356 g_free(newmsginfo->cc);
12357 newmsginfo->cc = tmp;
12360 if ( strcasecmp(header,
12361 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12362 if ( newmsginfo->newsgroups == NULL ) {
12363 newmsginfo->newsgroups = g_strdup(entry);
12364 } else if (entry && *entry) {
12365 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12366 g_free(newmsginfo->newsgroups);
12367 newmsginfo->newsgroups = tmp;
12375 /* other data is unset */
12381 /* update compose's dictionaries from folder dict settings */
12382 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12383 FolderItem *folder_item)
12385 cm_return_if_fail(compose != NULL);
12387 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12388 FolderItemPrefs *prefs = folder_item->prefs;
12390 if (prefs->enable_default_dictionary)
12391 gtkaspell_change_dict(compose->gtkaspell,
12392 prefs->default_dictionary, FALSE);
12393 if (folder_item->prefs->enable_default_alt_dictionary)
12394 gtkaspell_change_alt_dict(compose->gtkaspell,
12395 prefs->default_alt_dictionary);
12396 if (prefs->enable_default_dictionary
12397 || prefs->enable_default_alt_dictionary)
12398 compose_spell_menu_changed(compose);
12403 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12405 Compose *compose = (Compose *)data;
12407 cm_return_if_fail(compose != NULL);
12409 gtk_widget_grab_focus(compose->text);
12412 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12414 gtk_combo_box_popup(GTK_COMBO_BOX(data));