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();
1091 compose->replyinfo = NULL;
1092 compose->fwdinfo = NULL;
1094 textview = GTK_TEXT_VIEW(compose->text);
1095 textbuf = gtk_text_view_get_buffer(textview);
1096 compose_create_tags(textview, compose);
1098 undo_block(compose->undostruct);
1100 compose_set_dictionaries_from_folder_prefs(compose, item);
1103 if (account->auto_sig)
1104 compose_insert_sig(compose, FALSE);
1105 gtk_text_buffer_get_start_iter(textbuf, &iter);
1106 gtk_text_buffer_place_cursor(textbuf, &iter);
1108 if (account->protocol != A_NNTP) {
1109 if (mailto && *mailto != '\0') {
1110 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1113 compose_set_folder_prefs(compose, item, TRUE);
1115 if (item && item->ret_rcpt) {
1116 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1119 if (mailto && *mailto != '\0') {
1120 if (!strchr(mailto, '@'))
1121 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1123 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1124 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1125 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1126 mfield = TO_FIELD_PRESENT;
1129 * CLAWS: just don't allow return receipt request, even if the user
1130 * may want to send an email. simple but foolproof.
1132 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1134 compose_add_field_list( compose, listAddress );
1136 if (item && item->prefs && item->prefs->compose_with_format) {
1137 subject_format = item->prefs->compose_subject_format;
1138 body_format = item->prefs->compose_body_format;
1139 } else if (account->compose_with_format) {
1140 subject_format = account->compose_subject_format;
1141 body_format = account->compose_body_format;
1142 } else if (prefs_common.compose_with_format) {
1143 subject_format = prefs_common.compose_subject_format;
1144 body_format = prefs_common.compose_body_format;
1147 if (subject_format || body_format) {
1150 && *subject_format != '\0' )
1152 gchar *subject = NULL;
1157 dummyinfo = compose_msginfo_new_from_compose(compose);
1159 /* decode \-escape sequences in the internal representation of the quote format */
1160 tmp = g_malloc(strlen(subject_format)+1);
1161 pref_get_unescaped_pref(tmp, subject_format);
1163 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1165 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1166 compose->gtkaspell);
1168 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1170 quote_fmt_scan_string(tmp);
1173 buf = quote_fmt_get_buffer();
1175 alertpanel_error(_("New message subject format error."));
1177 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1178 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1179 quote_fmt_reset_vartable();
1183 mfield = SUBJECT_FIELD_PRESENT;
1187 && *body_format != '\0' )
1190 GtkTextBuffer *buffer;
1191 GtkTextIter start, end;
1195 dummyinfo = compose_msginfo_new_from_compose(compose);
1197 text = GTK_TEXT_VIEW(compose->text);
1198 buffer = gtk_text_view_get_buffer(text);
1199 gtk_text_buffer_get_start_iter(buffer, &start);
1200 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1201 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1203 compose_quote_fmt(compose, dummyinfo,
1205 NULL, tmp, FALSE, TRUE,
1206 _("The body of the \"New message\" template has an error at line %d."));
1207 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1208 quote_fmt_reset_vartable();
1212 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1213 gtkaspell_highlight_all(compose->gtkaspell);
1215 mfield = BODY_FIELD_PRESENT;
1219 procmsg_msginfo_free( &dummyinfo );
1225 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1226 ainfo = (AttachInfo *) curr->data;
1227 compose_attach_append(compose, ainfo->file, ainfo->file,
1228 ainfo->content_type, ainfo->charset);
1232 compose_show_first_last_header(compose, TRUE);
1234 /* Set save folder */
1235 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1236 gchar *folderidentifier;
1238 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1239 folderidentifier = folder_item_get_identifier(item);
1240 compose_set_save_to(compose, folderidentifier);
1241 g_free(folderidentifier);
1244 /* Place cursor according to provided input (mfield) */
1246 case NO_FIELD_PRESENT:
1247 if (compose->header_last)
1248 gtk_widget_grab_focus(compose->header_last->entry);
1250 case TO_FIELD_PRESENT:
1251 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1253 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1256 gtk_widget_grab_focus(compose->subject_entry);
1258 case SUBJECT_FIELD_PRESENT:
1259 textview = GTK_TEXT_VIEW(compose->text);
1262 textbuf = gtk_text_view_get_buffer(textview);
1265 mark = gtk_text_buffer_get_insert(textbuf);
1266 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1267 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1269 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1270 * only defers where it comes to the variable body
1271 * is not null. If no body is present compose->text
1272 * will be null in which case you cannot place the
1273 * cursor inside the component so. An empty component
1274 * is therefore created before placing the cursor
1276 case BODY_FIELD_PRESENT:
1277 cursor_pos = quote_fmt_get_cursor_pos();
1278 if (cursor_pos == -1)
1279 gtk_widget_grab_focus(compose->header_last->entry);
1281 gtk_widget_grab_focus(compose->text);
1285 undo_unblock(compose->undostruct);
1287 if (prefs_common.auto_exteditor)
1288 compose_exec_ext_editor(compose);
1290 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1292 SCROLL_TO_CURSOR(compose);
1294 compose->modified = FALSE;
1295 compose_set_title(compose);
1297 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1302 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1303 gboolean override_pref, const gchar *system)
1305 const gchar *privacy = NULL;
1307 cm_return_if_fail(compose != NULL);
1308 cm_return_if_fail(account != NULL);
1310 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1313 if (account->default_privacy_system && strlen(account->default_privacy_system))
1314 privacy = account->default_privacy_system;
1318 GSList *privacy_avail = privacy_get_system_ids();
1319 if (privacy_avail && g_slist_length(privacy_avail)) {
1320 privacy = (gchar *)(privacy_avail->data);
1322 g_slist_free_full(privacy_avail, g_free);
1324 if (privacy != NULL) {
1326 g_free(compose->privacy_system);
1327 compose->privacy_system = NULL;
1328 g_free(compose->encdata);
1329 compose->encdata = NULL;
1331 if (compose->privacy_system == NULL)
1332 compose->privacy_system = g_strdup(privacy);
1333 else if (*(compose->privacy_system) == '\0') {
1334 g_free(compose->privacy_system);
1335 g_free(compose->encdata);
1336 compose->encdata = NULL;
1337 compose->privacy_system = g_strdup(privacy);
1339 compose_update_privacy_system_menu_item(compose, FALSE);
1340 compose_use_encryption(compose, TRUE);
1344 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1346 const gchar *privacy = NULL;
1348 if (account->default_privacy_system && strlen(account->default_privacy_system))
1349 privacy = account->default_privacy_system;
1353 GSList *privacy_avail = privacy_get_system_ids();
1354 if (privacy_avail && g_slist_length(privacy_avail)) {
1355 privacy = (gchar *)(privacy_avail->data);
1359 if (privacy != NULL) {
1361 g_free(compose->privacy_system);
1362 compose->privacy_system = NULL;
1363 g_free(compose->encdata);
1364 compose->encdata = NULL;
1366 if (compose->privacy_system == NULL)
1367 compose->privacy_system = g_strdup(privacy);
1368 compose_update_privacy_system_menu_item(compose, FALSE);
1369 compose_use_signing(compose, TRUE);
1373 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1377 Compose *compose = NULL;
1379 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1381 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1382 cm_return_val_if_fail(msginfo != NULL, NULL);
1384 list_len = g_slist_length(msginfo_list);
1388 case COMPOSE_REPLY_TO_ADDRESS:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1390 FALSE, prefs_common.default_reply_list, FALSE, body);
1392 case COMPOSE_REPLY_WITH_QUOTE:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1394 FALSE, prefs_common.default_reply_list, FALSE, body);
1396 case COMPOSE_REPLY_WITHOUT_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1398 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1400 case COMPOSE_REPLY_TO_SENDER:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1402 FALSE, FALSE, TRUE, body);
1404 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1405 compose = compose_followup_and_reply_to(msginfo,
1406 COMPOSE_QUOTE_CHECK,
1407 FALSE, FALSE, body);
1409 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1411 FALSE, FALSE, TRUE, body);
1413 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1414 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1415 FALSE, FALSE, TRUE, NULL);
1417 case COMPOSE_REPLY_TO_ALL:
1418 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1419 TRUE, FALSE, FALSE, body);
1421 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1422 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1423 TRUE, FALSE, FALSE, body);
1425 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1426 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1427 TRUE, FALSE, FALSE, NULL);
1429 case COMPOSE_REPLY_TO_LIST:
1430 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1431 FALSE, TRUE, FALSE, body);
1433 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1434 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1435 FALSE, TRUE, FALSE, body);
1437 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1438 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1439 FALSE, TRUE, FALSE, NULL);
1441 case COMPOSE_FORWARD:
1442 if (prefs_common.forward_as_attachment) {
1443 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1446 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1450 case COMPOSE_FORWARD_INLINE:
1451 /* check if we reply to more than one Message */
1452 if (list_len == 1) {
1453 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1456 /* more messages FALL THROUGH */
1457 case COMPOSE_FORWARD_AS_ATTACH:
1458 compose = compose_forward_multiple(NULL, msginfo_list);
1460 case COMPOSE_REDIRECT:
1461 compose = compose_redirect(NULL, msginfo, FALSE);
1464 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1467 if (compose == NULL) {
1468 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1472 compose->rmode = mode;
1473 switch (compose->rmode) {
1475 case COMPOSE_REPLY_WITH_QUOTE:
1476 case COMPOSE_REPLY_WITHOUT_QUOTE:
1477 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1478 debug_print("reply mode Normal\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1480 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1482 case COMPOSE_REPLY_TO_SENDER:
1483 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1484 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1485 debug_print("reply mode Sender\n");
1486 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1488 case COMPOSE_REPLY_TO_ALL:
1489 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1490 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1491 debug_print("reply mode All\n");
1492 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1494 case COMPOSE_REPLY_TO_LIST:
1495 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1496 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1497 debug_print("reply mode List\n");
1498 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1500 case COMPOSE_REPLY_TO_ADDRESS:
1501 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1509 static Compose *compose_reply(MsgInfo *msginfo,
1510 ComposeQuoteMode quote_mode,
1516 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1517 to_sender, FALSE, body);
1520 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1521 ComposeQuoteMode quote_mode,
1526 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1527 to_sender, TRUE, body);
1530 static void compose_extract_original_charset(Compose *compose)
1532 MsgInfo *info = NULL;
1533 if (compose->replyinfo) {
1534 info = compose->replyinfo;
1535 } else if (compose->fwdinfo) {
1536 info = compose->fwdinfo;
1537 } else if (compose->targetinfo) {
1538 info = compose->targetinfo;
1541 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1542 MimeInfo *partinfo = mimeinfo;
1543 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1544 partinfo = procmime_mimeinfo_next(partinfo);
1546 compose->orig_charset =
1547 g_strdup(procmime_mimeinfo_get_parameter(
1548 partinfo, "charset"));
1550 procmime_mimeinfo_free_all(&mimeinfo);
1554 #define SIGNAL_BLOCK(buffer) { \
1555 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1556 G_CALLBACK(compose_changed_cb), \
1558 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1559 G_CALLBACK(text_inserted), \
1563 #define SIGNAL_UNBLOCK(buffer) { \
1564 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1565 G_CALLBACK(compose_changed_cb), \
1567 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1568 G_CALLBACK(text_inserted), \
1572 static Compose *compose_generic_reply(MsgInfo *msginfo,
1573 ComposeQuoteMode quote_mode,
1574 gboolean to_all, gboolean to_ml,
1576 gboolean followup_and_reply_to,
1580 PrefsAccount *account = NULL;
1581 GtkTextView *textview;
1582 GtkTextBuffer *textbuf;
1583 gboolean quote = FALSE;
1584 const gchar *qmark = NULL;
1585 const gchar *body_fmt = NULL;
1586 gchar *s_system = NULL;
1588 cm_return_val_if_fail(msginfo != NULL, NULL);
1589 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1591 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1593 cm_return_val_if_fail(account != NULL, NULL);
1595 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1597 compose->updating = TRUE;
1599 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1600 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1602 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1603 if (!compose->replyinfo)
1604 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1606 compose_extract_original_charset(compose);
1608 if (msginfo->folder && msginfo->folder->ret_rcpt)
1609 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1611 /* Set save folder */
1612 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1613 gchar *folderidentifier;
1615 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1616 folderidentifier = folder_item_get_identifier(msginfo->folder);
1617 compose_set_save_to(compose, folderidentifier);
1618 g_free(folderidentifier);
1621 if (compose_parse_header(compose, msginfo) < 0) {
1622 compose->updating = FALSE;
1623 compose_destroy(compose);
1627 /* override from name according to folder properties */
1628 if (msginfo->folder && msginfo->folder->prefs &&
1629 msginfo->folder->prefs->reply_with_format &&
1630 msginfo->folder->prefs->reply_override_from_format &&
1631 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1636 /* decode \-escape sequences in the internal representation of the quote format */
1637 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1638 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1641 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1642 compose->gtkaspell);
1644 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1646 quote_fmt_scan_string(tmp);
1649 buf = quote_fmt_get_buffer();
1651 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1653 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1654 quote_fmt_reset_vartable();
1659 textview = (GTK_TEXT_VIEW(compose->text));
1660 textbuf = gtk_text_view_get_buffer(textview);
1661 compose_create_tags(textview, compose);
1663 undo_block(compose->undostruct);
1665 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1666 gtkaspell_block_check(compose->gtkaspell);
1669 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1670 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1671 /* use the reply format of folder (if enabled), or the account's one
1672 (if enabled) or fallback to the global reply format, which is always
1673 enabled (even if empty), and use the relevant quotemark */
1675 if (msginfo->folder && msginfo->folder->prefs &&
1676 msginfo->folder->prefs->reply_with_format) {
1677 qmark = msginfo->folder->prefs->reply_quotemark;
1678 body_fmt = msginfo->folder->prefs->reply_body_format;
1680 } else if (account->reply_with_format) {
1681 qmark = account->reply_quotemark;
1682 body_fmt = account->reply_body_format;
1685 qmark = prefs_common.quotemark;
1686 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1687 body_fmt = gettext(prefs_common.quotefmt);
1694 /* empty quotemark is not allowed */
1695 if (qmark == NULL || *qmark == '\0')
1697 compose_quote_fmt(compose, compose->replyinfo,
1698 body_fmt, qmark, body, FALSE, TRUE,
1699 _("The body of the \"Reply\" template has an error at line %d."));
1700 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1701 quote_fmt_reset_vartable();
1704 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1705 compose_force_encryption(compose, account, FALSE, s_system);
1708 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1709 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1710 compose_force_signing(compose, account, s_system);
1714 SIGNAL_BLOCK(textbuf);
1716 if (account->auto_sig)
1717 compose_insert_sig(compose, FALSE);
1719 compose_wrap_all(compose);
1722 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1723 gtkaspell_highlight_all(compose->gtkaspell);
1724 gtkaspell_unblock_check(compose->gtkaspell);
1726 SIGNAL_UNBLOCK(textbuf);
1728 gtk_widget_grab_focus(compose->text);
1730 undo_unblock(compose->undostruct);
1732 if (prefs_common.auto_exteditor)
1733 compose_exec_ext_editor(compose);
1735 compose->modified = FALSE;
1736 compose_set_title(compose);
1738 compose->updating = FALSE;
1739 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1740 SCROLL_TO_CURSOR(compose);
1742 if (compose->deferred_destroy) {
1743 compose_destroy(compose);
1751 #define INSERT_FW_HEADER(var, hdr) \
1752 if (msginfo->var && *msginfo->var) { \
1753 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1754 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1755 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1758 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1759 gboolean as_attach, const gchar *body,
1760 gboolean no_extedit,
1764 GtkTextView *textview;
1765 GtkTextBuffer *textbuf;
1766 gint cursor_pos = -1;
1769 cm_return_val_if_fail(msginfo != NULL, NULL);
1770 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1772 if (!account && !(account = compose_find_account(msginfo)))
1773 account = cur_account;
1775 if (!prefs_common.forward_as_attachment)
1776 mode = COMPOSE_FORWARD_INLINE;
1778 mode = COMPOSE_FORWARD;
1779 compose = compose_create(account, msginfo->folder, mode, batch);
1781 compose->updating = TRUE;
1782 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1783 if (!compose->fwdinfo)
1784 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1786 compose_extract_original_charset(compose);
1788 if (msginfo->subject && *msginfo->subject) {
1789 gchar *buf, *buf2, *p;
1791 buf = p = g_strdup(msginfo->subject);
1792 p += subject_get_prefix_length(p);
1793 memmove(buf, p, strlen(p) + 1);
1795 buf2 = g_strdup_printf("Fw: %s", buf);
1796 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1802 /* override from name according to folder properties */
1803 if (msginfo->folder && msginfo->folder->prefs &&
1804 msginfo->folder->prefs->forward_with_format &&
1805 msginfo->folder->prefs->forward_override_from_format &&
1806 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1810 MsgInfo *full_msginfo = NULL;
1813 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1815 full_msginfo = procmsg_msginfo_copy(msginfo);
1817 /* decode \-escape sequences in the internal representation of the quote format */
1818 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1819 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1822 gtkaspell_block_check(compose->gtkaspell);
1823 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1824 compose->gtkaspell);
1826 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1828 quote_fmt_scan_string(tmp);
1831 buf = quote_fmt_get_buffer();
1833 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1835 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1836 quote_fmt_reset_vartable();
1839 procmsg_msginfo_free(&full_msginfo);
1842 textview = GTK_TEXT_VIEW(compose->text);
1843 textbuf = gtk_text_view_get_buffer(textview);
1844 compose_create_tags(textview, compose);
1846 undo_block(compose->undostruct);
1850 msgfile = procmsg_get_message_file(msginfo);
1851 if (!is_file_exist(msgfile))
1852 g_warning("%s: file does not exist", msgfile);
1854 compose_attach_append(compose, msgfile, msgfile,
1855 "message/rfc822", NULL);
1859 const gchar *qmark = NULL;
1860 const gchar *body_fmt = NULL;
1861 MsgInfo *full_msginfo;
1863 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1865 full_msginfo = procmsg_msginfo_copy(msginfo);
1867 /* use the forward format of folder (if enabled), or the account's one
1868 (if enabled) or fallback to the global forward format, which is always
1869 enabled (even if empty), and use the relevant quotemark */
1870 if (msginfo->folder && msginfo->folder->prefs &&
1871 msginfo->folder->prefs->forward_with_format) {
1872 qmark = msginfo->folder->prefs->forward_quotemark;
1873 body_fmt = msginfo->folder->prefs->forward_body_format;
1875 } else if (account->forward_with_format) {
1876 qmark = account->forward_quotemark;
1877 body_fmt = account->forward_body_format;
1880 qmark = prefs_common.fw_quotemark;
1881 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1882 body_fmt = gettext(prefs_common.fw_quotefmt);
1887 /* empty quotemark is not allowed */
1888 if (qmark == NULL || *qmark == '\0')
1891 compose_quote_fmt(compose, full_msginfo,
1892 body_fmt, qmark, body, FALSE, TRUE,
1893 _("The body of the \"Forward\" template has an error at line %d."));
1894 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1895 quote_fmt_reset_vartable();
1896 compose_attach_parts(compose, msginfo);
1898 procmsg_msginfo_free(&full_msginfo);
1901 SIGNAL_BLOCK(textbuf);
1903 if (account->auto_sig)
1904 compose_insert_sig(compose, FALSE);
1906 compose_wrap_all(compose);
1909 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1910 gtkaspell_highlight_all(compose->gtkaspell);
1911 gtkaspell_unblock_check(compose->gtkaspell);
1913 SIGNAL_UNBLOCK(textbuf);
1915 cursor_pos = quote_fmt_get_cursor_pos();
1916 if (cursor_pos == -1)
1917 gtk_widget_grab_focus(compose->header_last->entry);
1919 gtk_widget_grab_focus(compose->text);
1921 if (!no_extedit && prefs_common.auto_exteditor)
1922 compose_exec_ext_editor(compose);
1925 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1926 gchar *folderidentifier;
1928 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1929 folderidentifier = folder_item_get_identifier(msginfo->folder);
1930 compose_set_save_to(compose, folderidentifier);
1931 g_free(folderidentifier);
1934 undo_unblock(compose->undostruct);
1936 compose->modified = FALSE;
1937 compose_set_title(compose);
1939 compose->updating = FALSE;
1940 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1941 SCROLL_TO_CURSOR(compose);
1943 if (compose->deferred_destroy) {
1944 compose_destroy(compose);
1948 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1953 #undef INSERT_FW_HEADER
1955 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1958 GtkTextView *textview;
1959 GtkTextBuffer *textbuf;
1963 gboolean single_mail = TRUE;
1965 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1967 if (g_slist_length(msginfo_list) > 1)
1968 single_mail = FALSE;
1970 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1971 if (((MsgInfo *)msginfo->data)->folder == NULL)
1974 /* guess account from first selected message */
1976 !(account = compose_find_account(msginfo_list->data)))
1977 account = cur_account;
1979 cm_return_val_if_fail(account != NULL, NULL);
1981 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1982 if (msginfo->data) {
1983 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1984 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1988 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1989 g_warning("no msginfo_list");
1993 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1995 compose->updating = TRUE;
1997 /* override from name according to folder properties */
1998 if (msginfo_list->data) {
1999 MsgInfo *msginfo = msginfo_list->data;
2001 if (msginfo->folder && msginfo->folder->prefs &&
2002 msginfo->folder->prefs->forward_with_format &&
2003 msginfo->folder->prefs->forward_override_from_format &&
2004 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2009 /* decode \-escape sequences in the internal representation of the quote format */
2010 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2011 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2014 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2015 compose->gtkaspell);
2017 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2019 quote_fmt_scan_string(tmp);
2022 buf = quote_fmt_get_buffer();
2024 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2026 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2027 quote_fmt_reset_vartable();
2033 textview = GTK_TEXT_VIEW(compose->text);
2034 textbuf = gtk_text_view_get_buffer(textview);
2035 compose_create_tags(textview, compose);
2037 undo_block(compose->undostruct);
2038 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2039 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2041 if (!is_file_exist(msgfile))
2042 g_warning("%s: file does not exist", msgfile);
2044 compose_attach_append(compose, msgfile, msgfile,
2045 "message/rfc822", NULL);
2050 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2051 if (info->subject && *info->subject) {
2052 gchar *buf, *buf2, *p;
2054 buf = p = g_strdup(info->subject);
2055 p += subject_get_prefix_length(p);
2056 memmove(buf, p, strlen(p) + 1);
2058 buf2 = g_strdup_printf("Fw: %s", buf);
2059 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2065 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2066 _("Fw: multiple emails"));
2069 SIGNAL_BLOCK(textbuf);
2071 if (account->auto_sig)
2072 compose_insert_sig(compose, FALSE);
2074 compose_wrap_all(compose);
2076 SIGNAL_UNBLOCK(textbuf);
2078 gtk_text_buffer_get_start_iter(textbuf, &iter);
2079 gtk_text_buffer_place_cursor(textbuf, &iter);
2081 if (prefs_common.auto_exteditor)
2082 compose_exec_ext_editor(compose);
2084 gtk_widget_grab_focus(compose->header_last->entry);
2085 undo_unblock(compose->undostruct);
2086 compose->modified = FALSE;
2087 compose_set_title(compose);
2089 compose->updating = FALSE;
2090 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2091 SCROLL_TO_CURSOR(compose);
2093 if (compose->deferred_destroy) {
2094 compose_destroy(compose);
2098 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2103 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2105 GtkTextIter start = *iter;
2106 GtkTextIter end_iter;
2107 int start_pos = gtk_text_iter_get_offset(&start);
2109 if (!compose->account->sig_sep)
2112 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2113 start_pos+strlen(compose->account->sig_sep));
2115 /* check sig separator */
2116 str = gtk_text_iter_get_text(&start, &end_iter);
2117 if (!strcmp(str, compose->account->sig_sep)) {
2119 /* check end of line (\n) */
2120 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2121 start_pos+strlen(compose->account->sig_sep));
2122 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2123 start_pos+strlen(compose->account->sig_sep)+1);
2124 tmp = gtk_text_iter_get_text(&start, &end_iter);
2125 if (!strcmp(tmp,"\n")) {
2137 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2139 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2140 Compose *compose = (Compose *)data;
2141 FolderItem *old_item = NULL;
2142 FolderItem *new_item = NULL;
2143 gchar *old_id, *new_id;
2145 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2146 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2149 old_item = hookdata->item;
2150 new_item = hookdata->item2;
2152 old_id = folder_item_get_identifier(old_item);
2153 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2155 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2156 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2157 compose->targetinfo->folder = new_item;
2160 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2161 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2162 compose->replyinfo->folder = new_item;
2165 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2166 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2167 compose->fwdinfo->folder = new_item;
2175 static void compose_colorize_signature(Compose *compose)
2177 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2179 GtkTextIter end_iter;
2180 gtk_text_buffer_get_start_iter(buffer, &iter);
2181 while (gtk_text_iter_forward_line(&iter))
2182 if (compose_is_sig_separator(compose, buffer, &iter)) {
2183 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2184 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2188 #define BLOCK_WRAP() { \
2189 prev_autowrap = compose->autowrap; \
2190 buffer = gtk_text_view_get_buffer( \
2191 GTK_TEXT_VIEW(compose->text)); \
2192 compose->autowrap = FALSE; \
2194 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2195 G_CALLBACK(compose_changed_cb), \
2197 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2198 G_CALLBACK(text_inserted), \
2201 #define UNBLOCK_WRAP() { \
2202 compose->autowrap = prev_autowrap; \
2203 if (compose->autowrap) { \
2204 gint old = compose->draft_timeout_tag; \
2205 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2206 compose_wrap_all(compose); \
2207 compose->draft_timeout_tag = old; \
2210 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2211 G_CALLBACK(compose_changed_cb), \
2213 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2214 G_CALLBACK(text_inserted), \
2218 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2220 Compose *compose = NULL;
2221 PrefsAccount *account = NULL;
2222 GtkTextView *textview;
2223 GtkTextBuffer *textbuf;
2227 gboolean use_signing = FALSE;
2228 gboolean use_encryption = FALSE;
2229 gchar *privacy_system = NULL;
2230 int priority = PRIORITY_NORMAL;
2231 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2232 gboolean autowrap = prefs_common.autowrap;
2233 gboolean autoindent = prefs_common.auto_indent;
2234 HeaderEntry *manual_headers = NULL;
2236 cm_return_val_if_fail(msginfo != NULL, NULL);
2237 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2239 if (compose_put_existing_to_front(msginfo)) {
2243 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2244 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2245 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2246 gchar *queueheader_buf = NULL;
2249 /* Select Account from queue headers */
2250 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2251 "X-Claws-Account-Id:")) {
2252 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2253 account = account_find_from_id(id);
2254 g_free(queueheader_buf);
2256 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2257 "X-Sylpheed-Account-Id:")) {
2258 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2259 account = account_find_from_id(id);
2260 g_free(queueheader_buf);
2262 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2264 id = atoi(&queueheader_buf[strlen("NAID:")]);
2265 account = account_find_from_id(id);
2266 g_free(queueheader_buf);
2268 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2270 id = atoi(&queueheader_buf[strlen("MAID:")]);
2271 account = account_find_from_id(id);
2272 g_free(queueheader_buf);
2274 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2276 account = account_find_from_address(queueheader_buf, FALSE);
2277 g_free(queueheader_buf);
2279 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2281 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2282 use_signing = param;
2283 g_free(queueheader_buf);
2285 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2286 "X-Sylpheed-Sign:")) {
2287 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2288 use_signing = param;
2289 g_free(queueheader_buf);
2291 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2292 "X-Claws-Encrypt:")) {
2293 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2294 use_encryption = param;
2295 g_free(queueheader_buf);
2297 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2298 "X-Sylpheed-Encrypt:")) {
2299 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2300 use_encryption = param;
2301 g_free(queueheader_buf);
2303 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2304 "X-Claws-Auto-Wrapping:")) {
2305 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2307 g_free(queueheader_buf);
2309 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2310 "X-Claws-Auto-Indent:")) {
2311 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2313 g_free(queueheader_buf);
2315 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2316 "X-Claws-Privacy-System:")) {
2317 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2318 g_free(queueheader_buf);
2320 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2321 "X-Sylpheed-Privacy-System:")) {
2322 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2323 g_free(queueheader_buf);
2325 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2327 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2329 g_free(queueheader_buf);
2331 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2333 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2334 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2335 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2336 if (orig_item != NULL) {
2337 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2341 g_free(queueheader_buf);
2343 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2345 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2346 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2347 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2348 if (orig_item != NULL) {
2349 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2353 g_free(queueheader_buf);
2355 /* Get manual headers */
2356 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2357 "X-Claws-Manual-Headers:")) {
2358 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2359 if (listmh && *listmh != '\0') {
2360 debug_print("Got manual headers: %s\n", listmh);
2361 manual_headers = procheader_entries_from_str(listmh);
2364 g_free(queueheader_buf);
2367 account = msginfo->folder->folder->account;
2370 if (!account && prefs_common.reedit_account_autosel) {
2372 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2373 extract_address(from);
2374 account = account_find_from_address(from, FALSE);
2379 account = cur_account;
2381 cm_return_val_if_fail(account != NULL, NULL);
2383 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2385 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2386 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2387 compose->autowrap = autowrap;
2388 compose->replyinfo = replyinfo;
2389 compose->fwdinfo = fwdinfo;
2391 compose->updating = TRUE;
2392 compose->priority = priority;
2394 if (privacy_system != NULL) {
2395 compose->privacy_system = privacy_system;
2396 compose_use_signing(compose, use_signing);
2397 compose_use_encryption(compose, use_encryption);
2398 compose_update_privacy_system_menu_item(compose, FALSE);
2400 activate_privacy_system(compose, account, FALSE);
2403 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2405 compose_extract_original_charset(compose);
2407 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2408 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2409 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2410 gchar *queueheader_buf = NULL;
2412 /* Set message save folder */
2413 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2414 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2415 compose_set_save_to(compose, &queueheader_buf[4]);
2416 g_free(queueheader_buf);
2418 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2419 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2421 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2423 g_free(queueheader_buf);
2427 if (compose_parse_header(compose, msginfo) < 0) {
2428 compose->updating = FALSE;
2429 compose_destroy(compose);
2432 compose_reedit_set_entry(compose, msginfo);
2434 textview = GTK_TEXT_VIEW(compose->text);
2435 textbuf = gtk_text_view_get_buffer(textview);
2436 compose_create_tags(textview, compose);
2438 mark = gtk_text_buffer_get_insert(textbuf);
2439 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2441 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2442 G_CALLBACK(compose_changed_cb),
2445 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2446 fp = procmime_get_first_encrypted_text_content(msginfo);
2448 compose_force_encryption(compose, account, TRUE, NULL);
2451 fp = procmime_get_first_text_content(msginfo);
2454 g_warning("Can't get text part");
2458 gchar buf[BUFFSIZE];
2459 gboolean prev_autowrap;
2460 GtkTextBuffer *buffer;
2462 while (fgets(buf, sizeof(buf), fp) != NULL) {
2464 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2470 compose_attach_parts(compose, msginfo);
2472 compose_colorize_signature(compose);
2474 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2475 G_CALLBACK(compose_changed_cb),
2478 if (manual_headers != NULL) {
2479 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2480 procheader_entries_free(manual_headers);
2481 compose->updating = FALSE;
2482 compose_destroy(compose);
2485 procheader_entries_free(manual_headers);
2488 gtk_widget_grab_focus(compose->text);
2490 if (prefs_common.auto_exteditor) {
2491 compose_exec_ext_editor(compose);
2493 compose->modified = FALSE;
2494 compose_set_title(compose);
2496 compose->updating = FALSE;
2497 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2498 SCROLL_TO_CURSOR(compose);
2500 if (compose->deferred_destroy) {
2501 compose_destroy(compose);
2505 compose->sig_str = account_get_signature_str(compose->account);
2507 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2512 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2519 cm_return_val_if_fail(msginfo != NULL, NULL);
2522 account = account_get_reply_account(msginfo,
2523 prefs_common.reply_account_autosel);
2524 cm_return_val_if_fail(account != NULL, NULL);
2526 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2528 compose->updating = TRUE;
2530 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2531 compose->replyinfo = NULL;
2532 compose->fwdinfo = NULL;
2534 compose_show_first_last_header(compose, TRUE);
2536 gtk_widget_grab_focus(compose->header_last->entry);
2538 filename = procmsg_get_message_file(msginfo);
2540 if (filename == NULL) {
2541 compose->updating = FALSE;
2542 compose_destroy(compose);
2547 compose->redirect_filename = filename;
2549 /* Set save folder */
2550 item = msginfo->folder;
2551 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2552 gchar *folderidentifier;
2554 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2555 folderidentifier = folder_item_get_identifier(item);
2556 compose_set_save_to(compose, folderidentifier);
2557 g_free(folderidentifier);
2560 compose_attach_parts(compose, msginfo);
2562 if (msginfo->subject)
2563 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2565 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2567 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2568 _("The body of the \"Redirect\" template has an error at line %d."));
2569 quote_fmt_reset_vartable();
2570 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2572 compose_colorize_signature(compose);
2575 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2576 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2577 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2589 if (compose->toolbar->draft_btn)
2590 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2591 if (compose->toolbar->insert_btn)
2592 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2593 if (compose->toolbar->attach_btn)
2594 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2595 if (compose->toolbar->sig_btn)
2596 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2597 if (compose->toolbar->exteditor_btn)
2598 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2599 if (compose->toolbar->linewrap_current_btn)
2600 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2601 if (compose->toolbar->linewrap_all_btn)
2602 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2604 compose->modified = FALSE;
2605 compose_set_title(compose);
2606 compose->updating = FALSE;
2607 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2608 SCROLL_TO_CURSOR(compose);
2610 if (compose->deferred_destroy) {
2611 compose_destroy(compose);
2615 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2620 const GList *compose_get_compose_list(void)
2622 return compose_list;
2625 void compose_entry_append(Compose *compose, const gchar *address,
2626 ComposeEntryType type, ComposePrefType pref_type)
2628 const gchar *header;
2630 gboolean in_quote = FALSE;
2631 if (!address || *address == '\0') return;
2638 header = N_("Bcc:");
2640 case COMPOSE_REPLYTO:
2641 header = N_("Reply-To:");
2643 case COMPOSE_NEWSGROUPS:
2644 header = N_("Newsgroups:");
2646 case COMPOSE_FOLLOWUPTO:
2647 header = N_( "Followup-To:");
2649 case COMPOSE_INREPLYTO:
2650 header = N_( "In-Reply-To:");
2657 header = prefs_common_translated_header_name(header);
2659 cur = begin = (gchar *)address;
2661 /* we separate the line by commas, but not if we're inside a quoted
2663 while (*cur != '\0') {
2665 in_quote = !in_quote;
2666 if (*cur == ',' && !in_quote) {
2667 gchar *tmp = g_strdup(begin);
2669 tmp[cur-begin]='\0';
2672 while (*tmp == ' ' || *tmp == '\t')
2674 compose_add_header_entry(compose, header, tmp, pref_type);
2675 compose_entry_indicate(compose, tmp);
2682 gchar *tmp = g_strdup(begin);
2684 tmp[cur-begin]='\0';
2685 while (*tmp == ' ' || *tmp == '\t')
2687 compose_add_header_entry(compose, header, tmp, pref_type);
2688 compose_entry_indicate(compose, tmp);
2693 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2698 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2699 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2700 if (gtk_entry_get_text(entry) &&
2701 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2702 gtk_widget_modify_base(
2703 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2704 GTK_STATE_NORMAL, &default_header_bgcolor);
2705 gtk_widget_modify_text(
2706 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2707 GTK_STATE_NORMAL, &default_header_color);
2712 void compose_toolbar_cb(gint action, gpointer data)
2714 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2715 Compose *compose = (Compose*)toolbar_item->parent;
2717 cm_return_if_fail(compose != NULL);
2721 compose_send_cb(NULL, compose);
2724 compose_send_later_cb(NULL, compose);
2727 compose_draft(compose, COMPOSE_QUIT_EDITING);
2730 compose_insert_file_cb(NULL, compose);
2733 compose_attach_cb(NULL, compose);
2736 compose_insert_sig(compose, FALSE);
2739 compose_insert_sig(compose, TRUE);
2742 compose_ext_editor_cb(NULL, compose);
2744 case A_LINEWRAP_CURRENT:
2745 compose_beautify_paragraph(compose, NULL, TRUE);
2747 case A_LINEWRAP_ALL:
2748 compose_wrap_all_full(compose, TRUE);
2751 compose_address_cb(NULL, compose);
2754 case A_CHECK_SPELLING:
2755 compose_check_all(NULL, compose);
2758 case A_PRIVACY_SIGN:
2760 case A_PRIVACY_ENCRYPT:
2767 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2772 gchar *subject = NULL;
2776 gchar **attach = NULL;
2777 gchar *inreplyto = NULL;
2778 MailField mfield = NO_FIELD_PRESENT;
2780 /* get mailto parts but skip from */
2781 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2784 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2785 mfield = TO_FIELD_PRESENT;
2788 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2790 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2792 if (!g_utf8_validate (subject, -1, NULL)) {
2793 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2794 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2797 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2799 mfield = SUBJECT_FIELD_PRESENT;
2802 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2803 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2806 gboolean prev_autowrap = compose->autowrap;
2808 compose->autowrap = FALSE;
2810 mark = gtk_text_buffer_get_insert(buffer);
2811 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2813 if (!g_utf8_validate (body, -1, NULL)) {
2814 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2815 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2818 gtk_text_buffer_insert(buffer, &iter, body, -1);
2820 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2822 compose->autowrap = prev_autowrap;
2823 if (compose->autowrap)
2824 compose_wrap_all(compose);
2825 mfield = BODY_FIELD_PRESENT;
2829 gint i = 0, att = 0;
2830 gchar *warn_files = NULL;
2831 while (attach[i] != NULL) {
2832 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2833 if (utf8_filename) {
2834 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2835 gchar *tmp = g_strdup_printf("%s%s\n",
2836 warn_files?warn_files:"",
2842 g_free(utf8_filename);
2844 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2849 alertpanel_notice(ngettext(
2850 "The following file has been attached: \n%s",
2851 "The following files have been attached: \n%s", att), warn_files);
2856 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2869 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2871 static HeaderEntry hentry[] = {
2872 {"Reply-To:", NULL, TRUE },
2873 {"Cc:", NULL, TRUE },
2874 {"References:", NULL, FALSE },
2875 {"Bcc:", NULL, TRUE },
2876 {"Newsgroups:", NULL, TRUE },
2877 {"Followup-To:", NULL, TRUE },
2878 {"List-Post:", NULL, FALSE },
2879 {"X-Priority:", NULL, FALSE },
2880 {NULL, NULL, FALSE }
2897 cm_return_val_if_fail(msginfo != NULL, -1);
2899 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2900 procheader_get_header_fields(fp, hentry);
2903 if (hentry[H_REPLY_TO].body != NULL) {
2904 if (hentry[H_REPLY_TO].body[0] != '\0') {
2906 conv_unmime_header(hentry[H_REPLY_TO].body,
2909 g_free(hentry[H_REPLY_TO].body);
2910 hentry[H_REPLY_TO].body = NULL;
2912 if (hentry[H_CC].body != NULL) {
2913 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2914 g_free(hentry[H_CC].body);
2915 hentry[H_CC].body = NULL;
2917 if (hentry[H_REFERENCES].body != NULL) {
2918 if (compose->mode == COMPOSE_REEDIT)
2919 compose->references = hentry[H_REFERENCES].body;
2921 compose->references = compose_parse_references
2922 (hentry[H_REFERENCES].body, msginfo->msgid);
2923 g_free(hentry[H_REFERENCES].body);
2925 hentry[H_REFERENCES].body = NULL;
2927 if (hentry[H_BCC].body != NULL) {
2928 if (compose->mode == COMPOSE_REEDIT)
2930 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2931 g_free(hentry[H_BCC].body);
2932 hentry[H_BCC].body = NULL;
2934 if (hentry[H_NEWSGROUPS].body != NULL) {
2935 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2936 hentry[H_NEWSGROUPS].body = NULL;
2938 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2939 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2940 compose->followup_to =
2941 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2944 g_free(hentry[H_FOLLOWUP_TO].body);
2945 hentry[H_FOLLOWUP_TO].body = NULL;
2947 if (hentry[H_LIST_POST].body != NULL) {
2948 gchar *to = NULL, *start = NULL;
2950 extract_address(hentry[H_LIST_POST].body);
2951 if (hentry[H_LIST_POST].body[0] != '\0') {
2952 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2954 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2955 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2958 g_free(compose->ml_post);
2959 compose->ml_post = to;
2962 g_free(hentry[H_LIST_POST].body);
2963 hentry[H_LIST_POST].body = NULL;
2966 /* CLAWS - X-Priority */
2967 if (compose->mode == COMPOSE_REEDIT)
2968 if (hentry[H_X_PRIORITY].body != NULL) {
2971 priority = atoi(hentry[H_X_PRIORITY].body);
2972 g_free(hentry[H_X_PRIORITY].body);
2974 hentry[H_X_PRIORITY].body = NULL;
2976 if (priority < PRIORITY_HIGHEST ||
2977 priority > PRIORITY_LOWEST)
2978 priority = PRIORITY_NORMAL;
2980 compose->priority = priority;
2983 if (compose->mode == COMPOSE_REEDIT) {
2984 if (msginfo->inreplyto && *msginfo->inreplyto)
2985 compose->inreplyto = g_strdup(msginfo->inreplyto);
2987 if (msginfo->msgid && *msginfo->msgid &&
2988 compose->folder != NULL &&
2989 compose->folder->stype == F_DRAFT)
2990 compose->msgid = g_strdup(msginfo->msgid);
2992 if (msginfo->msgid && *msginfo->msgid)
2993 compose->inreplyto = g_strdup(msginfo->msgid);
2995 if (!compose->references) {
2996 if (msginfo->msgid && *msginfo->msgid) {
2997 if (msginfo->inreplyto && *msginfo->inreplyto)
2998 compose->references =
2999 g_strdup_printf("<%s>\n\t<%s>",
3003 compose->references =
3004 g_strconcat("<", msginfo->msgid, ">",
3006 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3007 compose->references =
3008 g_strconcat("<", msginfo->inreplyto, ">",
3017 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3022 cm_return_val_if_fail(msginfo != NULL, -1);
3024 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3025 procheader_get_header_fields(fp, entries);
3029 while (he != NULL && he->name != NULL) {
3031 GtkListStore *model = NULL;
3033 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3034 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3035 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3036 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3037 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3044 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3046 GSList *ref_id_list, *cur;
3050 ref_id_list = references_list_append(NULL, ref);
3051 if (!ref_id_list) return NULL;
3052 if (msgid && *msgid)
3053 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3058 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3059 /* "<" + Message-ID + ">" + CR+LF+TAB */
3060 len += strlen((gchar *)cur->data) + 5;
3062 if (len > MAX_REFERENCES_LEN) {
3063 /* remove second message-ID */
3064 if (ref_id_list && ref_id_list->next &&
3065 ref_id_list->next->next) {
3066 g_free(ref_id_list->next->data);
3067 ref_id_list = g_slist_remove
3068 (ref_id_list, ref_id_list->next->data);
3070 slist_free_strings_full(ref_id_list);
3077 new_ref = g_string_new("");
3078 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3079 if (new_ref->len > 0)
3080 g_string_append(new_ref, "\n\t");
3081 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3084 slist_free_strings_full(ref_id_list);
3086 new_ref_str = new_ref->str;
3087 g_string_free(new_ref, FALSE);
3092 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3093 const gchar *fmt, const gchar *qmark,
3094 const gchar *body, gboolean rewrap,
3095 gboolean need_unescape,
3096 const gchar *err_msg)
3098 MsgInfo* dummyinfo = NULL;
3099 gchar *quote_str = NULL;
3101 gboolean prev_autowrap;
3102 const gchar *trimmed_body = body;
3103 gint cursor_pos = -1;
3104 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3105 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3110 SIGNAL_BLOCK(buffer);
3113 dummyinfo = compose_msginfo_new_from_compose(compose);
3114 msginfo = dummyinfo;
3117 if (qmark != NULL) {
3119 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3120 compose->gtkaspell);
3122 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3124 quote_fmt_scan_string(qmark);
3127 buf = quote_fmt_get_buffer();
3129 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3131 Xstrdup_a(quote_str, buf, goto error)
3134 if (fmt && *fmt != '\0') {
3137 while (*trimmed_body == '\n')
3141 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3142 compose->gtkaspell);
3144 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3146 if (need_unescape) {
3149 /* decode \-escape sequences in the internal representation of the quote format */
3150 tmp = g_malloc(strlen(fmt)+1);
3151 pref_get_unescaped_pref(tmp, fmt);
3152 quote_fmt_scan_string(tmp);
3156 quote_fmt_scan_string(fmt);
3160 buf = quote_fmt_get_buffer();
3162 gint line = quote_fmt_get_line();
3163 alertpanel_error(err_msg, line);
3169 prev_autowrap = compose->autowrap;
3170 compose->autowrap = FALSE;
3172 mark = gtk_text_buffer_get_insert(buffer);
3173 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3174 if (g_utf8_validate(buf, -1, NULL)) {
3175 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3177 gchar *tmpout = NULL;
3178 tmpout = conv_codeset_strdup
3179 (buf, conv_get_locale_charset_str_no_utf8(),
3181 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3183 tmpout = g_malloc(strlen(buf)*2+1);
3184 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3186 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3190 cursor_pos = quote_fmt_get_cursor_pos();
3191 if (cursor_pos == -1)
3192 cursor_pos = gtk_text_iter_get_offset(&iter);
3193 compose->set_cursor_pos = cursor_pos;
3195 gtk_text_buffer_get_start_iter(buffer, &iter);
3196 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3197 gtk_text_buffer_place_cursor(buffer, &iter);
3199 compose->autowrap = prev_autowrap;
3200 if (compose->autowrap && rewrap)
3201 compose_wrap_all(compose);
3208 SIGNAL_UNBLOCK(buffer);
3210 procmsg_msginfo_free( &dummyinfo );
3215 /* if ml_post is of type addr@host and from is of type
3216 * addr-anything@host, return TRUE
3218 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3220 gchar *left_ml = NULL;
3221 gchar *right_ml = NULL;
3222 gchar *left_from = NULL;
3223 gchar *right_from = NULL;
3224 gboolean result = FALSE;
3226 if (!ml_post || !from)
3229 left_ml = g_strdup(ml_post);
3230 if (strstr(left_ml, "@")) {
3231 right_ml = strstr(left_ml, "@")+1;
3232 *(strstr(left_ml, "@")) = '\0';
3235 left_from = g_strdup(from);
3236 if (strstr(left_from, "@")) {
3237 right_from = strstr(left_from, "@")+1;
3238 *(strstr(left_from, "@")) = '\0';
3241 if (right_ml && right_from
3242 && !strncmp(left_from, left_ml, strlen(left_ml))
3243 && !strcmp(right_from, right_ml)) {
3252 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3253 gboolean respect_default_to)
3257 if (!folder || !folder->prefs)
3260 if (respect_default_to && folder->prefs->enable_default_to) {
3261 compose_entry_append(compose, folder->prefs->default_to,
3262 COMPOSE_TO, PREF_FOLDER);
3263 compose_entry_indicate(compose, folder->prefs->default_to);
3265 if (folder->prefs->enable_default_cc) {
3266 compose_entry_append(compose, folder->prefs->default_cc,
3267 COMPOSE_CC, PREF_FOLDER);
3268 compose_entry_indicate(compose, folder->prefs->default_cc);
3270 if (folder->prefs->enable_default_bcc) {
3271 compose_entry_append(compose, folder->prefs->default_bcc,
3272 COMPOSE_BCC, PREF_FOLDER);
3273 compose_entry_indicate(compose, folder->prefs->default_bcc);
3275 if (folder->prefs->enable_default_replyto) {
3276 compose_entry_append(compose, folder->prefs->default_replyto,
3277 COMPOSE_REPLYTO, PREF_FOLDER);
3278 compose_entry_indicate(compose, folder->prefs->default_replyto);
3282 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3287 if (!compose || !msginfo)
3290 if (msginfo->subject && *msginfo->subject) {
3291 buf = p = g_strdup(msginfo->subject);
3292 p += subject_get_prefix_length(p);
3293 memmove(buf, p, strlen(p) + 1);
3295 buf2 = g_strdup_printf("Re: %s", buf);
3296 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3301 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3304 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3305 gboolean to_all, gboolean to_ml,
3307 gboolean followup_and_reply_to)
3309 GSList *cc_list = NULL;
3312 gchar *replyto = NULL;
3313 gchar *ac_email = NULL;
3315 gboolean reply_to_ml = FALSE;
3316 gboolean default_reply_to = FALSE;
3318 cm_return_if_fail(compose->account != NULL);
3319 cm_return_if_fail(msginfo != NULL);
3321 reply_to_ml = to_ml && compose->ml_post;
3323 default_reply_to = msginfo->folder &&
3324 msginfo->folder->prefs->enable_default_reply_to;
3326 if (compose->account->protocol != A_NNTP) {
3327 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3329 if (reply_to_ml && !default_reply_to) {
3331 gboolean is_subscr = is_subscription(compose->ml_post,
3334 /* normal answer to ml post with a reply-to */
3335 compose_entry_append(compose,
3337 COMPOSE_TO, PREF_ML);
3338 if (compose->replyto)
3339 compose_entry_append(compose,
3341 COMPOSE_CC, PREF_ML);
3343 /* answer to subscription confirmation */
3344 if (compose->replyto)
3345 compose_entry_append(compose,
3347 COMPOSE_TO, PREF_ML);
3348 else if (msginfo->from)
3349 compose_entry_append(compose,
3351 COMPOSE_TO, PREF_ML);
3354 else if (!(to_all || to_sender) && default_reply_to) {
3355 compose_entry_append(compose,
3356 msginfo->folder->prefs->default_reply_to,
3357 COMPOSE_TO, PREF_FOLDER);
3358 compose_entry_indicate(compose,
3359 msginfo->folder->prefs->default_reply_to);
3365 compose_entry_append(compose, msginfo->from,
3366 COMPOSE_TO, PREF_NONE);
3368 Xstrdup_a(tmp1, msginfo->from, return);
3369 extract_address(tmp1);
3370 compose_entry_append(compose,
3371 (!account_find_from_address(tmp1, FALSE))
3374 COMPOSE_TO, PREF_NONE);
3375 if (compose->replyto)
3376 compose_entry_append(compose,
3378 COMPOSE_CC, PREF_NONE);
3380 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3381 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3382 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3383 if (compose->replyto) {
3384 compose_entry_append(compose,
3386 COMPOSE_TO, PREF_NONE);
3388 compose_entry_append(compose,
3389 msginfo->from ? msginfo->from : "",
3390 COMPOSE_TO, PREF_NONE);
3393 /* replying to own mail, use original recp */
3394 compose_entry_append(compose,
3395 msginfo->to ? msginfo->to : "",
3396 COMPOSE_TO, PREF_NONE);
3397 compose_entry_append(compose,
3398 msginfo->cc ? msginfo->cc : "",
3399 COMPOSE_CC, PREF_NONE);
3404 if (to_sender || (compose->followup_to &&
3405 !strncmp(compose->followup_to, "poster", 6)))
3406 compose_entry_append
3408 (compose->replyto ? compose->replyto :
3409 msginfo->from ? msginfo->from : ""),
3410 COMPOSE_TO, PREF_NONE);
3412 else if (followup_and_reply_to || to_all) {
3413 compose_entry_append
3415 (compose->replyto ? compose->replyto :
3416 msginfo->from ? msginfo->from : ""),
3417 COMPOSE_TO, PREF_NONE);
3419 compose_entry_append
3421 compose->followup_to ? compose->followup_to :
3422 compose->newsgroups ? compose->newsgroups : "",
3423 COMPOSE_NEWSGROUPS, PREF_NONE);
3425 compose_entry_append
3427 msginfo->cc ? msginfo->cc : "",
3428 COMPOSE_CC, 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_reply_set_subject(compose, msginfo);
3439 if (to_ml && compose->ml_post) return;
3440 if (!to_all || compose->account->protocol == A_NNTP) return;
3442 if (compose->replyto) {
3443 Xstrdup_a(replyto, compose->replyto, return);
3444 extract_address(replyto);
3446 if (msginfo->from) {
3447 Xstrdup_a(from, msginfo->from, return);
3448 extract_address(from);
3451 if (replyto && from)
3452 cc_list = address_list_append_with_comments(cc_list, from);
3453 if (to_all && msginfo->folder &&
3454 msginfo->folder->prefs->enable_default_reply_to)
3455 cc_list = address_list_append_with_comments(cc_list,
3456 msginfo->folder->prefs->default_reply_to);
3457 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3458 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3460 ac_email = g_utf8_strdown(compose->account->address, -1);
3463 for (cur = cc_list; cur != NULL; cur = cur->next) {
3464 gchar *addr = g_utf8_strdown(cur->data, -1);
3465 extract_address(addr);
3467 if (strcmp(ac_email, addr))
3468 compose_entry_append(compose, (gchar *)cur->data,
3469 COMPOSE_CC, PREF_NONE);
3471 debug_print("Cc address same as compose account's, ignoring\n");
3476 slist_free_strings_full(cc_list);
3482 #define SET_ENTRY(entry, str) \
3485 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3488 #define SET_ADDRESS(type, str) \
3491 compose_entry_append(compose, str, type, PREF_NONE); \
3494 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3496 cm_return_if_fail(msginfo != NULL);
3498 SET_ENTRY(subject_entry, msginfo->subject);
3499 SET_ENTRY(from_name, msginfo->from);
3500 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3501 SET_ADDRESS(COMPOSE_CC, compose->cc);
3502 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3503 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3504 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3505 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3507 compose_update_priority_menu_item(compose);
3508 compose_update_privacy_system_menu_item(compose, FALSE);
3509 compose_show_first_last_header(compose, TRUE);
3515 static void compose_insert_sig(Compose *compose, gboolean replace)
3517 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3518 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3520 GtkTextIter iter, iter_end;
3521 gint cur_pos, ins_pos;
3522 gboolean prev_autowrap;
3523 gboolean found = FALSE;
3524 gboolean exists = FALSE;
3526 cm_return_if_fail(compose->account != NULL);
3530 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3531 G_CALLBACK(compose_changed_cb),
3534 mark = gtk_text_buffer_get_insert(buffer);
3535 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3536 cur_pos = gtk_text_iter_get_offset (&iter);
3539 gtk_text_buffer_get_end_iter(buffer, &iter);
3541 exists = (compose->sig_str != NULL);
3544 GtkTextIter first_iter, start_iter, end_iter;
3546 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3548 if (!exists || compose->sig_str[0] == '\0')
3551 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3552 compose->signature_tag);
3555 /* include previous \n\n */
3556 gtk_text_iter_backward_chars(&first_iter, 1);
3557 start_iter = first_iter;
3558 end_iter = first_iter;
3560 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3561 compose->signature_tag);
3562 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3563 compose->signature_tag);
3565 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3571 g_free(compose->sig_str);
3572 compose->sig_str = account_get_signature_str(compose->account);
3574 cur_pos = gtk_text_iter_get_offset(&iter);
3576 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3577 g_free(compose->sig_str);
3578 compose->sig_str = NULL;
3580 if (compose->sig_inserted == FALSE)
3581 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3582 compose->sig_inserted = TRUE;
3584 cur_pos = gtk_text_iter_get_offset(&iter);
3585 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3587 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3588 gtk_text_iter_forward_chars(&iter, 1);
3589 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3590 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3592 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3593 cur_pos = gtk_text_buffer_get_char_count (buffer);
3596 /* put the cursor where it should be
3597 * either where the quote_fmt says, either where it was */
3598 if (compose->set_cursor_pos < 0)
3599 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3601 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3602 compose->set_cursor_pos);
3604 compose->set_cursor_pos = -1;
3605 gtk_text_buffer_place_cursor(buffer, &iter);
3606 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3607 G_CALLBACK(compose_changed_cb),
3613 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3616 GtkTextBuffer *buffer;
3619 const gchar *cur_encoding;
3620 gchar buf[BUFFSIZE];
3623 gboolean prev_autowrap;
3626 GString *file_contents = NULL;
3627 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3629 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3631 /* get the size of the file we are about to insert */
3632 ret = g_stat(file, &file_stat);
3634 gchar *shortfile = g_path_get_basename(file);
3635 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3637 return COMPOSE_INSERT_NO_FILE;
3638 } else if (prefs_common.warn_large_insert == TRUE) {
3640 /* ask user for confirmation if the file is large */
3641 if (prefs_common.warn_large_insert_size < 0 ||
3642 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3646 msg = g_strdup_printf(_("You are about to insert a file of %s "
3647 "in the message body. Are you sure you want to do that?"),
3648 to_human_readable(file_stat.st_size));
3649 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3650 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3653 /* do we ask for confirmation next time? */
3654 if (aval & G_ALERTDISABLE) {
3655 /* no confirmation next time, disable feature in preferences */
3656 aval &= ~G_ALERTDISABLE;
3657 prefs_common.warn_large_insert = FALSE;
3660 /* abort file insertion if user canceled action */
3661 if (aval != G_ALERTALTERNATE) {
3662 return COMPOSE_INSERT_NO_FILE;
3668 if ((fp = g_fopen(file, "rb")) == NULL) {
3669 FILE_OP_ERROR(file, "fopen");
3670 return COMPOSE_INSERT_READ_ERROR;
3673 prev_autowrap = compose->autowrap;
3674 compose->autowrap = FALSE;
3676 text = GTK_TEXT_VIEW(compose->text);
3677 buffer = gtk_text_view_get_buffer(text);
3678 mark = gtk_text_buffer_get_insert(buffer);
3679 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3681 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3682 G_CALLBACK(text_inserted),
3685 cur_encoding = conv_get_locale_charset_str_no_utf8();
3687 file_contents = g_string_new("");
3688 while (fgets(buf, sizeof(buf), fp) != NULL) {
3691 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3692 str = g_strdup(buf);
3694 codeconv_set_strict(TRUE);
3695 str = conv_codeset_strdup
3696 (buf, cur_encoding, CS_INTERNAL);
3697 codeconv_set_strict(FALSE);
3700 result = COMPOSE_INSERT_INVALID_CHARACTER;
3706 /* strip <CR> if DOS/Windows file,
3707 replace <CR> with <LF> if Macintosh file. */
3710 if (len > 0 && str[len - 1] != '\n') {
3712 if (str[len] == '\r') str[len] = '\n';
3715 file_contents = g_string_append(file_contents, str);
3719 if (result == COMPOSE_INSERT_SUCCESS) {
3720 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3722 compose_changed_cb(NULL, compose);
3723 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3724 G_CALLBACK(text_inserted),
3726 compose->autowrap = prev_autowrap;
3727 if (compose->autowrap)
3728 compose_wrap_all(compose);
3731 g_string_free(file_contents, TRUE);
3737 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3738 const gchar *filename,
3739 const gchar *content_type,
3740 const gchar *charset)
3748 GtkListStore *store;
3750 gboolean has_binary = FALSE;
3752 if (!is_file_exist(file)) {
3753 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3754 gboolean result = FALSE;
3755 if (file_from_uri && is_file_exist(file_from_uri)) {
3756 result = compose_attach_append(
3757 compose, file_from_uri,
3758 filename, content_type,
3761 g_free(file_from_uri);
3764 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3767 if ((size = get_file_size(file)) < 0) {
3768 alertpanel_error("Can't get file size of %s\n", filename);
3772 /* In batch mode, we allow 0-length files to be attached no questions asked */
3773 if (size == 0 && !compose->batch) {
3774 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3775 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3776 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3777 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3780 if (aval != G_ALERTALTERNATE) {
3784 if ((fp = g_fopen(file, "rb")) == NULL) {
3785 alertpanel_error(_("Can't read %s."), filename);
3790 ainfo = g_new0(AttachInfo, 1);
3791 auto_ainfo = g_auto_pointer_new_with_free
3792 (ainfo, (GFreeFunc) compose_attach_info_free);
3793 ainfo->file = g_strdup(file);
3796 ainfo->content_type = g_strdup(content_type);
3797 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3799 MsgFlags flags = {0, 0};
3801 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3802 ainfo->encoding = ENC_7BIT;
3804 ainfo->encoding = ENC_8BIT;
3806 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3807 if (msginfo && msginfo->subject)
3808 name = g_strdup(msginfo->subject);
3810 name = g_path_get_basename(filename ? filename : file);
3812 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3814 procmsg_msginfo_free(&msginfo);
3816 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3817 ainfo->charset = g_strdup(charset);
3818 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3820 ainfo->encoding = ENC_BASE64;
3822 name = g_path_get_basename(filename ? filename : file);
3823 ainfo->name = g_strdup(name);
3827 ainfo->content_type = procmime_get_mime_type(file);
3828 if (!ainfo->content_type) {
3829 ainfo->content_type =
3830 g_strdup("application/octet-stream");
3831 ainfo->encoding = ENC_BASE64;
3832 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3834 procmime_get_encoding_for_text_file(file, &has_binary);
3836 ainfo->encoding = ENC_BASE64;
3837 name = g_path_get_basename(filename ? filename : file);
3838 ainfo->name = g_strdup(name);
3842 if (ainfo->name != NULL
3843 && !strcmp(ainfo->name, ".")) {
3844 g_free(ainfo->name);
3848 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3849 g_free(ainfo->content_type);
3850 ainfo->content_type = g_strdup("application/octet-stream");
3851 g_free(ainfo->charset);
3852 ainfo->charset = NULL;
3855 ainfo->size = (goffset)size;
3856 size_text = to_human_readable((goffset)size);
3858 store = GTK_LIST_STORE(gtk_tree_view_get_model
3859 (GTK_TREE_VIEW(compose->attach_clist)));
3861 gtk_list_store_append(store, &iter);
3862 gtk_list_store_set(store, &iter,
3863 COL_MIMETYPE, ainfo->content_type,
3864 COL_SIZE, size_text,
3865 COL_NAME, ainfo->name,
3866 COL_CHARSET, ainfo->charset,
3868 COL_AUTODATA, auto_ainfo,
3871 g_auto_pointer_free(auto_ainfo);
3872 compose_attach_update_label(compose);
3876 void compose_use_signing(Compose *compose, gboolean use_signing)
3878 compose->use_signing = use_signing;
3879 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3882 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3884 compose->use_encryption = use_encryption;
3885 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3888 #define NEXT_PART_NOT_CHILD(info) \
3890 node = info->node; \
3891 while (node->children) \
3892 node = g_node_last_child(node); \
3893 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3896 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3900 MimeInfo *firsttext = NULL;
3901 MimeInfo *encrypted = NULL;
3904 const gchar *partname = NULL;
3906 mimeinfo = procmime_scan_message(msginfo);
3907 if (!mimeinfo) return;
3909 if (mimeinfo->node->children == NULL) {
3910 procmime_mimeinfo_free_all(&mimeinfo);
3914 /* find first content part */
3915 child = (MimeInfo *) mimeinfo->node->children->data;
3916 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3917 child = (MimeInfo *)child->node->children->data;
3920 if (child->type == MIMETYPE_TEXT) {
3922 debug_print("First text part found\n");
3923 } else if (compose->mode == COMPOSE_REEDIT &&
3924 child->type == MIMETYPE_APPLICATION &&
3925 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3926 encrypted = (MimeInfo *)child->node->parent->data;
3929 child = (MimeInfo *) mimeinfo->node->children->data;
3930 while (child != NULL) {
3933 if (child == encrypted) {
3934 /* skip this part of tree */
3935 NEXT_PART_NOT_CHILD(child);
3939 if (child->type == MIMETYPE_MULTIPART) {
3940 /* get the actual content */
3941 child = procmime_mimeinfo_next(child);
3945 if (child == firsttext) {
3946 child = procmime_mimeinfo_next(child);
3950 outfile = procmime_get_tmp_file_name(child);
3951 if ((err = procmime_get_part(outfile, child)) < 0)
3952 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3954 gchar *content_type;
3956 content_type = procmime_get_content_type_str(child->type, child->subtype);
3958 /* if we meet a pgp signature, we don't attach it, but
3959 * we force signing. */
3960 if ((strcmp(content_type, "application/pgp-signature") &&
3961 strcmp(content_type, "application/pkcs7-signature") &&
3962 strcmp(content_type, "application/x-pkcs7-signature"))
3963 || compose->mode == COMPOSE_REDIRECT) {
3964 partname = procmime_mimeinfo_get_parameter(child, "filename");
3965 if (partname == NULL)
3966 partname = procmime_mimeinfo_get_parameter(child, "name");
3967 if (partname == NULL)
3969 compose_attach_append(compose, outfile,
3970 partname, content_type,
3971 procmime_mimeinfo_get_parameter(child, "charset"));
3973 compose_force_signing(compose, compose->account, NULL);
3975 g_free(content_type);
3978 NEXT_PART_NOT_CHILD(child);
3980 procmime_mimeinfo_free_all(&mimeinfo);
3983 #undef NEXT_PART_NOT_CHILD
3988 WAIT_FOR_INDENT_CHAR,
3989 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3992 /* return indent length, we allow:
3993 indent characters followed by indent characters or spaces/tabs,
3994 alphabets and numbers immediately followed by indent characters,
3995 and the repeating sequences of the above
3996 If quote ends with multiple spaces, only the first one is included. */
3997 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3998 const GtkTextIter *start, gint *len)
4000 GtkTextIter iter = *start;
4004 IndentState state = WAIT_FOR_INDENT_CHAR;
4007 gint alnum_count = 0;
4008 gint space_count = 0;
4011 if (prefs_common.quote_chars == NULL) {
4015 while (!gtk_text_iter_ends_line(&iter)) {
4016 wc = gtk_text_iter_get_char(&iter);
4017 if (g_unichar_iswide(wc))
4019 clen = g_unichar_to_utf8(wc, ch);
4023 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4024 is_space = g_unichar_isspace(wc);
4026 if (state == WAIT_FOR_INDENT_CHAR) {
4027 if (!is_indent && !g_unichar_isalnum(wc))
4030 quote_len += alnum_count + space_count + 1;
4031 alnum_count = space_count = 0;
4032 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4035 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4036 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4040 else if (is_indent) {
4041 quote_len += alnum_count + space_count + 1;
4042 alnum_count = space_count = 0;
4045 state = WAIT_FOR_INDENT_CHAR;
4049 gtk_text_iter_forward_char(&iter);
4052 if (quote_len > 0 && space_count > 0)
4058 if (quote_len > 0) {
4060 gtk_text_iter_forward_chars(&iter, quote_len);
4061 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4067 /* return >0 if the line is itemized */
4068 static int compose_itemized_length(GtkTextBuffer *buffer,
4069 const GtkTextIter *start)
4071 GtkTextIter iter = *start;
4076 if (gtk_text_iter_ends_line(&iter))
4081 wc = gtk_text_iter_get_char(&iter);
4082 if (!g_unichar_isspace(wc))
4084 gtk_text_iter_forward_char(&iter);
4085 if (gtk_text_iter_ends_line(&iter))
4089 clen = g_unichar_to_utf8(wc, ch);
4090 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4092 wc == 0x2022 || /* BULLET */
4093 wc == 0x2023 || /* TRIANGULAR BULLET */
4094 wc == 0x2043 || /* HYPHEN BULLET */
4095 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4096 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4097 wc == 0x2219 || /* BULLET OPERATOR */
4098 wc == 0x25d8 || /* INVERSE BULLET */
4099 wc == 0x25e6 || /* WHITE BULLET */
4100 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4101 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4102 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4103 wc == 0x29be || /* CIRCLED WHITE BULLET */
4104 wc == 0x29bf /* CIRCLED BULLET */
4108 gtk_text_iter_forward_char(&iter);
4109 if (gtk_text_iter_ends_line(&iter))
4111 wc = gtk_text_iter_get_char(&iter);
4112 if (g_unichar_isspace(wc)) {
4118 /* return the string at the start of the itemization */
4119 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4120 const GtkTextIter *start)
4122 GtkTextIter iter = *start;
4125 GString *item_chars = g_string_new("");
4128 if (gtk_text_iter_ends_line(&iter))
4133 wc = gtk_text_iter_get_char(&iter);
4134 if (!g_unichar_isspace(wc))
4136 gtk_text_iter_forward_char(&iter);
4137 if (gtk_text_iter_ends_line(&iter))
4139 g_string_append_unichar(item_chars, wc);
4142 str = item_chars->str;
4143 g_string_free(item_chars, FALSE);
4147 /* return the number of spaces at a line's start */
4148 static int compose_left_offset_length(GtkTextBuffer *buffer,
4149 const GtkTextIter *start)
4151 GtkTextIter iter = *start;
4154 if (gtk_text_iter_ends_line(&iter))
4158 wc = gtk_text_iter_get_char(&iter);
4159 if (!g_unichar_isspace(wc))
4162 gtk_text_iter_forward_char(&iter);
4163 if (gtk_text_iter_ends_line(&iter))
4167 gtk_text_iter_forward_char(&iter);
4168 if (gtk_text_iter_ends_line(&iter))
4173 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4174 const GtkTextIter *start,
4175 GtkTextIter *break_pos,
4179 GtkTextIter iter = *start, line_end = *start;
4180 PangoLogAttr *attrs;
4187 gboolean can_break = FALSE;
4188 gboolean do_break = FALSE;
4189 gboolean was_white = FALSE;
4190 gboolean prev_dont_break = FALSE;
4192 gtk_text_iter_forward_to_line_end(&line_end);
4193 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4194 len = g_utf8_strlen(str, -1);
4198 g_warning("compose_get_line_break_pos: len = 0!");
4202 /* g_print("breaking line: %d: %s (len = %d)\n",
4203 gtk_text_iter_get_line(&iter), str, len); */
4205 attrs = g_new(PangoLogAttr, len + 1);
4207 pango_default_break(str, -1, NULL, attrs, len + 1);
4211 /* skip quote and leading spaces */
4212 for (i = 0; *p != '\0' && i < len; i++) {
4215 wc = g_utf8_get_char(p);
4216 if (i >= quote_len && !g_unichar_isspace(wc))
4218 if (g_unichar_iswide(wc))
4220 else if (*p == '\t')
4224 p = g_utf8_next_char(p);
4227 for (; *p != '\0' && i < len; i++) {
4228 PangoLogAttr *attr = attrs + i;
4232 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4235 was_white = attr->is_white;
4237 /* don't wrap URI */
4238 if ((uri_len = get_uri_len(p)) > 0) {
4240 if (pos > 0 && col > max_col) {
4250 wc = g_utf8_get_char(p);
4251 if (g_unichar_iswide(wc)) {
4253 if (prev_dont_break && can_break && attr->is_line_break)
4255 } else if (*p == '\t')
4259 if (pos > 0 && col > max_col) {
4264 if (*p == '-' || *p == '/')
4265 prev_dont_break = TRUE;
4267 prev_dont_break = FALSE;
4269 p = g_utf8_next_char(p);
4273 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4278 *break_pos = *start;
4279 gtk_text_iter_set_line_offset(break_pos, pos);
4284 static gboolean compose_join_next_line(Compose *compose,
4285 GtkTextBuffer *buffer,
4287 const gchar *quote_str)
4289 GtkTextIter iter_ = *iter, cur, prev, next, end;
4290 PangoLogAttr attrs[3];
4292 gchar *next_quote_str;
4295 gboolean keep_cursor = FALSE;
4297 if (!gtk_text_iter_forward_line(&iter_) ||
4298 gtk_text_iter_ends_line(&iter_)) {
4301 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4303 if ((quote_str || next_quote_str) &&
4304 strcmp2(quote_str, next_quote_str) != 0) {
4305 g_free(next_quote_str);
4308 g_free(next_quote_str);
4311 if (quote_len > 0) {
4312 gtk_text_iter_forward_chars(&end, quote_len);
4313 if (gtk_text_iter_ends_line(&end)) {
4318 /* don't join itemized lines */
4319 if (compose_itemized_length(buffer, &end) > 0) {
4323 /* don't join signature separator */
4324 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4327 /* delete quote str */
4329 gtk_text_buffer_delete(buffer, &iter_, &end);
4331 /* don't join line breaks put by the user */
4333 gtk_text_iter_backward_char(&cur);
4334 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4335 gtk_text_iter_forward_char(&cur);
4339 gtk_text_iter_forward_char(&cur);
4340 /* delete linebreak and extra spaces */
4341 while (gtk_text_iter_backward_char(&cur)) {
4342 wc1 = gtk_text_iter_get_char(&cur);
4343 if (!g_unichar_isspace(wc1))
4348 while (!gtk_text_iter_ends_line(&cur)) {
4349 wc1 = gtk_text_iter_get_char(&cur);
4350 if (!g_unichar_isspace(wc1))
4352 gtk_text_iter_forward_char(&cur);
4355 if (!gtk_text_iter_equal(&prev, &next)) {
4358 mark = gtk_text_buffer_get_insert(buffer);
4359 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4360 if (gtk_text_iter_equal(&prev, &cur))
4362 gtk_text_buffer_delete(buffer, &prev, &next);
4366 /* insert space if required */
4367 gtk_text_iter_backward_char(&prev);
4368 wc1 = gtk_text_iter_get_char(&prev);
4369 wc2 = gtk_text_iter_get_char(&next);
4370 gtk_text_iter_forward_char(&next);
4371 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4372 pango_default_break(str, -1, NULL, attrs, 3);
4373 if (!attrs[1].is_line_break ||
4374 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4375 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4377 gtk_text_iter_backward_char(&iter_);
4378 gtk_text_buffer_place_cursor(buffer, &iter_);
4387 #define ADD_TXT_POS(bp_, ep_, pti_) \
4388 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4389 last = last->next; \
4390 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4391 last->next = NULL; \
4393 g_warning("alloc error scanning URIs"); \
4396 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4398 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4399 GtkTextBuffer *buffer;
4400 GtkTextIter iter, break_pos, end_of_line;
4401 gchar *quote_str = NULL;
4403 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4404 gboolean prev_autowrap = compose->autowrap;
4405 gint startq_offset = -1, noq_offset = -1;
4406 gint uri_start = -1, uri_stop = -1;
4407 gint nouri_start = -1, nouri_stop = -1;
4408 gint num_blocks = 0;
4409 gint quotelevel = -1;
4410 gboolean modified = force;
4411 gboolean removed = FALSE;
4412 gboolean modified_before_remove = FALSE;
4414 gboolean start = TRUE;
4415 gint itemized_len = 0, rem_item_len = 0;
4416 gchar *itemized_chars = NULL;
4417 gboolean item_continuation = FALSE;
4422 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4426 compose->autowrap = FALSE;
4428 buffer = gtk_text_view_get_buffer(text);
4429 undo_wrapping(compose->undostruct, TRUE);
4434 mark = gtk_text_buffer_get_insert(buffer);
4435 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4439 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4440 if (gtk_text_iter_ends_line(&iter)) {
4441 while (gtk_text_iter_ends_line(&iter) &&
4442 gtk_text_iter_forward_line(&iter))
4445 while (gtk_text_iter_backward_line(&iter)) {
4446 if (gtk_text_iter_ends_line(&iter)) {
4447 gtk_text_iter_forward_line(&iter);
4453 /* move to line start */
4454 gtk_text_iter_set_line_offset(&iter, 0);
4457 itemized_len = compose_itemized_length(buffer, &iter);
4459 if (!itemized_len) {
4460 itemized_len = compose_left_offset_length(buffer, &iter);
4461 item_continuation = TRUE;
4465 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4467 /* go until paragraph end (empty line) */
4468 while (start || !gtk_text_iter_ends_line(&iter)) {
4469 gchar *scanpos = NULL;
4470 /* parse table - in order of priority */
4472 const gchar *needle; /* token */
4474 /* token search function */
4475 gchar *(*search) (const gchar *haystack,
4476 const gchar *needle);
4477 /* part parsing function */
4478 gboolean (*parse) (const gchar *start,
4479 const gchar *scanpos,
4483 /* part to URI function */
4484 gchar *(*build_uri) (const gchar *bp,
4488 static struct table parser[] = {
4489 {"http://", strcasestr, get_uri_part, make_uri_string},
4490 {"https://", strcasestr, get_uri_part, make_uri_string},
4491 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4492 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4493 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4494 {"www.", strcasestr, get_uri_part, make_http_string},
4495 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4496 {"@", strcasestr, get_email_part, make_email_string}
4498 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4499 gint last_index = PARSE_ELEMS;
4501 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4505 if (!prev_autowrap && num_blocks == 0) {
4507 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4508 G_CALLBACK(text_inserted),
4511 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4514 uri_start = uri_stop = -1;
4516 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4519 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4520 if (startq_offset == -1)
4521 startq_offset = gtk_text_iter_get_offset(&iter);
4522 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4523 if (quotelevel > 2) {
4524 /* recycle colors */
4525 if (prefs_common.recycle_quote_colors)
4534 if (startq_offset == -1)
4535 noq_offset = gtk_text_iter_get_offset(&iter);
4539 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4542 if (gtk_text_iter_ends_line(&iter)) {
4544 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4545 prefs_common.linewrap_len,
4547 GtkTextIter prev, next, cur;
4548 if (prev_autowrap != FALSE || force) {
4549 compose->automatic_break = TRUE;
4551 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4552 compose->automatic_break = FALSE;
4553 if (itemized_len && compose->autoindent) {
4554 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4555 if (!item_continuation)
4556 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4558 } else if (quote_str && wrap_quote) {
4559 compose->automatic_break = TRUE;
4561 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4562 compose->automatic_break = FALSE;
4563 if (itemized_len && compose->autoindent) {
4564 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4565 if (!item_continuation)
4566 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4570 /* remove trailing spaces */
4572 rem_item_len = itemized_len;
4573 while (compose->autoindent && rem_item_len-- > 0)
4574 gtk_text_iter_backward_char(&cur);
4575 gtk_text_iter_backward_char(&cur);
4578 while (!gtk_text_iter_starts_line(&cur)) {
4581 gtk_text_iter_backward_char(&cur);
4582 wc = gtk_text_iter_get_char(&cur);
4583 if (!g_unichar_isspace(wc))
4587 if (!gtk_text_iter_equal(&prev, &next)) {
4588 gtk_text_buffer_delete(buffer, &prev, &next);
4590 gtk_text_iter_forward_char(&break_pos);
4594 gtk_text_buffer_insert(buffer, &break_pos,
4598 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4600 /* move iter to current line start */
4601 gtk_text_iter_set_line_offset(&iter, 0);
4608 /* move iter to next line start */
4614 if (!prev_autowrap && num_blocks > 0) {
4616 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4617 G_CALLBACK(text_inserted),
4621 while (!gtk_text_iter_ends_line(&end_of_line)) {
4622 gtk_text_iter_forward_char(&end_of_line);
4624 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4626 nouri_start = gtk_text_iter_get_offset(&iter);
4627 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4629 walk_pos = gtk_text_iter_get_offset(&iter);
4630 /* FIXME: this looks phony. scanning for anything in the parse table */
4631 for (n = 0; n < PARSE_ELEMS; n++) {
4634 tmp = parser[n].search(walk, parser[n].needle);
4636 if (scanpos == NULL || tmp < scanpos) {
4645 /* check if URI can be parsed */
4646 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4647 (const gchar **)&ep, FALSE)
4648 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4652 strlen(parser[last_index].needle);
4655 uri_start = walk_pos + (bp - o_walk);
4656 uri_stop = walk_pos + (ep - o_walk);
4660 gtk_text_iter_forward_line(&iter);
4663 if (startq_offset != -1) {
4664 GtkTextIter startquote, endquote;
4665 gtk_text_buffer_get_iter_at_offset(
4666 buffer, &startquote, startq_offset);
4669 switch (quotelevel) {
4671 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4672 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4673 gtk_text_buffer_apply_tag_by_name(
4674 buffer, "quote0", &startquote, &endquote);
4675 gtk_text_buffer_remove_tag_by_name(
4676 buffer, "quote1", &startquote, &endquote);
4677 gtk_text_buffer_remove_tag_by_name(
4678 buffer, "quote2", &startquote, &endquote);
4683 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4684 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4685 gtk_text_buffer_apply_tag_by_name(
4686 buffer, "quote1", &startquote, &endquote);
4687 gtk_text_buffer_remove_tag_by_name(
4688 buffer, "quote0", &startquote, &endquote);
4689 gtk_text_buffer_remove_tag_by_name(
4690 buffer, "quote2", &startquote, &endquote);
4695 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4696 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4697 gtk_text_buffer_apply_tag_by_name(
4698 buffer, "quote2", &startquote, &endquote);
4699 gtk_text_buffer_remove_tag_by_name(
4700 buffer, "quote0", &startquote, &endquote);
4701 gtk_text_buffer_remove_tag_by_name(
4702 buffer, "quote1", &startquote, &endquote);
4708 } else if (noq_offset != -1) {
4709 GtkTextIter startnoquote, endnoquote;
4710 gtk_text_buffer_get_iter_at_offset(
4711 buffer, &startnoquote, noq_offset);
4714 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4715 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4716 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4717 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4718 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4719 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4720 gtk_text_buffer_remove_tag_by_name(
4721 buffer, "quote0", &startnoquote, &endnoquote);
4722 gtk_text_buffer_remove_tag_by_name(
4723 buffer, "quote1", &startnoquote, &endnoquote);
4724 gtk_text_buffer_remove_tag_by_name(
4725 buffer, "quote2", &startnoquote, &endnoquote);
4731 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4732 GtkTextIter nouri_start_iter, nouri_end_iter;
4733 gtk_text_buffer_get_iter_at_offset(
4734 buffer, &nouri_start_iter, nouri_start);
4735 gtk_text_buffer_get_iter_at_offset(
4736 buffer, &nouri_end_iter, nouri_stop);
4737 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4738 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4739 gtk_text_buffer_remove_tag_by_name(
4740 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4741 modified_before_remove = modified;
4746 if (uri_start >= 0 && uri_stop > 0) {
4747 GtkTextIter uri_start_iter, uri_end_iter, back;
4748 gtk_text_buffer_get_iter_at_offset(
4749 buffer, &uri_start_iter, uri_start);
4750 gtk_text_buffer_get_iter_at_offset(
4751 buffer, &uri_end_iter, uri_stop);
4752 back = uri_end_iter;
4753 gtk_text_iter_backward_char(&back);
4754 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4755 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4756 gtk_text_buffer_apply_tag_by_name(
4757 buffer, "link", &uri_start_iter, &uri_end_iter);
4759 if (removed && !modified_before_remove) {
4765 /* debug_print("not modified, out after %d lines\n", lines); */
4769 /* debug_print("modified, out after %d lines\n", lines); */
4771 g_free(itemized_chars);
4774 undo_wrapping(compose->undostruct, FALSE);
4775 compose->autowrap = prev_autowrap;
4780 void compose_action_cb(void *data)
4782 Compose *compose = (Compose *)data;
4783 compose_wrap_all(compose);
4786 static void compose_wrap_all(Compose *compose)
4788 compose_wrap_all_full(compose, FALSE);
4791 static void compose_wrap_all_full(Compose *compose, gboolean force)
4793 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4794 GtkTextBuffer *buffer;
4796 gboolean modified = TRUE;
4798 buffer = gtk_text_view_get_buffer(text);
4800 gtk_text_buffer_get_start_iter(buffer, &iter);
4802 undo_wrapping(compose->undostruct, TRUE);
4804 while (!gtk_text_iter_is_end(&iter) && modified)
4805 modified = compose_beautify_paragraph(compose, &iter, force);
4807 undo_wrapping(compose->undostruct, FALSE);
4811 static void compose_set_title(Compose *compose)
4817 edited = compose->modified ? _(" [Edited]") : "";
4819 subject = gtk_editable_get_chars(
4820 GTK_EDITABLE(compose->subject_entry), 0, -1);
4822 #ifndef GENERIC_UMPC
4823 if (subject && strlen(subject))
4824 str = g_strdup_printf(_("%s - Compose message%s"),
4827 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4829 str = g_strdup(_("Compose message"));
4832 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4838 * compose_current_mail_account:
4840 * Find a current mail account (the currently selected account, or the
4841 * default account, if a news account is currently selected). If a
4842 * mail account cannot be found, display an error message.
4844 * Return value: Mail account, or NULL if not found.
4846 static PrefsAccount *
4847 compose_current_mail_account(void)
4851 if (cur_account && cur_account->protocol != A_NNTP)
4854 ac = account_get_default();
4855 if (!ac || ac->protocol == A_NNTP) {
4856 alertpanel_error(_("Account for sending mail is not specified.\n"
4857 "Please select a mail account before sending."));
4864 #define QUOTE_IF_REQUIRED(out, str) \
4866 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4870 len = strlen(str) + 3; \
4871 if ((__tmp = alloca(len)) == NULL) { \
4872 g_warning("can't allocate memory"); \
4873 g_string_free(header, TRUE); \
4876 g_snprintf(__tmp, len, "\"%s\"", str); \
4881 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4882 g_warning("can't allocate memory"); \
4883 g_string_free(header, TRUE); \
4886 strcpy(__tmp, str); \
4892 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4894 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4898 len = strlen(str) + 3; \
4899 if ((__tmp = alloca(len)) == NULL) { \
4900 g_warning("can't allocate memory"); \
4903 g_snprintf(__tmp, len, "\"%s\"", str); \
4908 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4909 g_warning("can't allocate memory"); \
4912 strcpy(__tmp, str); \
4918 static void compose_select_account(Compose *compose, PrefsAccount *account,
4921 gchar *from = NULL, *header = NULL;
4922 ComposeHeaderEntry *header_entry;
4923 #if GTK_CHECK_VERSION(2, 24, 0)
4927 cm_return_if_fail(account != NULL);
4929 compose->account = account;
4930 if (account->name && *account->name) {
4932 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4933 qbuf = escape_internal_quotes(buf, '"');
4934 from = g_strdup_printf("%s <%s>",
4935 qbuf, account->address);
4938 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4940 from = g_strdup_printf("<%s>",
4942 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4947 compose_set_title(compose);
4949 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4950 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4952 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4953 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4954 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4956 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4958 activate_privacy_system(compose, account, FALSE);
4960 if (!init && compose->mode != COMPOSE_REDIRECT) {
4961 undo_block(compose->undostruct);
4962 compose_insert_sig(compose, TRUE);
4963 undo_unblock(compose->undostruct);
4966 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4967 #if !GTK_CHECK_VERSION(2, 24, 0)
4968 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4970 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4971 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4972 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4975 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4976 if (account->protocol == A_NNTP) {
4977 if (!strcmp(header, _("To:")))
4978 combobox_select_by_text(
4979 GTK_COMBO_BOX(header_entry->combo),
4982 if (!strcmp(header, _("Newsgroups:")))
4983 combobox_select_by_text(
4984 GTK_COMBO_BOX(header_entry->combo),
4992 /* use account's dict info if set */
4993 if (compose->gtkaspell) {
4994 if (account->enable_default_dictionary)
4995 gtkaspell_change_dict(compose->gtkaspell,
4996 account->default_dictionary, FALSE);
4997 if (account->enable_default_alt_dictionary)
4998 gtkaspell_change_alt_dict(compose->gtkaspell,
4999 account->default_alt_dictionary);
5000 if (account->enable_default_dictionary
5001 || account->enable_default_alt_dictionary)
5002 compose_spell_menu_changed(compose);
5007 gboolean compose_check_for_valid_recipient(Compose *compose) {
5008 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5009 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5010 gboolean recipient_found = FALSE;
5014 /* free to and newsgroup list */
5015 slist_free_strings_full(compose->to_list);
5016 compose->to_list = NULL;
5018 slist_free_strings_full(compose->newsgroup_list);
5019 compose->newsgroup_list = NULL;
5021 /* search header entries for to and newsgroup entries */
5022 for (list = compose->header_list; list; list = list->next) {
5025 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5026 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5029 if (entry[0] != '\0') {
5030 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5031 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5032 compose->to_list = address_list_append(compose->to_list, entry);
5033 recipient_found = TRUE;
5036 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5037 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5038 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5039 recipient_found = TRUE;
5046 return recipient_found;
5049 static gboolean compose_check_for_set_recipients(Compose *compose)
5051 if (compose->account->set_autocc && compose->account->auto_cc) {
5052 gboolean found_other = FALSE;
5054 /* search header entries for to and newsgroup entries */
5055 for (list = compose->header_list; list; list = list->next) {
5058 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5059 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5062 if (strcmp(entry, compose->account->auto_cc)
5063 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5074 if (compose->batch) {
5075 gtk_widget_show_all(compose->window);
5077 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5078 prefs_common_translated_header_name("Cc"));
5079 aval = alertpanel(_("Send"),
5081 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5083 if (aval != G_ALERTALTERNATE)
5087 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5088 gboolean found_other = FALSE;
5090 /* search header entries for to and newsgroup entries */
5091 for (list = compose->header_list; list; list = list->next) {
5094 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5095 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5098 if (strcmp(entry, compose->account->auto_bcc)
5099 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5111 if (compose->batch) {
5112 gtk_widget_show_all(compose->window);
5114 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5115 prefs_common_translated_header_name("Bcc"));
5116 aval = alertpanel(_("Send"),
5118 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5120 if (aval != G_ALERTALTERNATE)
5127 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5131 if (compose_check_for_valid_recipient(compose) == FALSE) {
5132 if (compose->batch) {
5133 gtk_widget_show_all(compose->window);
5135 alertpanel_error(_("Recipient is not specified."));
5139 if (compose_check_for_set_recipients(compose) == FALSE) {
5143 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5144 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5145 if (*str == '\0' && check_everything == TRUE &&
5146 compose->mode != COMPOSE_REDIRECT) {
5148 gchar *button_label;
5151 if (compose->sending)
5152 button_label = g_strconcat("+", _("_Send"), NULL);
5154 button_label = g_strconcat("+", _("_Queue"), NULL);
5155 message = g_strdup_printf(_("Subject is empty. %s"),
5156 compose->sending?_("Send it anyway?"):
5157 _("Queue it anyway?"));
5159 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5160 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5161 ALERT_QUESTION, G_ALERTDEFAULT);
5163 if (aval & G_ALERTDISABLE) {
5164 aval &= ~G_ALERTDISABLE;
5165 prefs_common.warn_empty_subj = FALSE;
5167 if (aval != G_ALERTALTERNATE)
5172 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5173 && check_everything == TRUE) {
5177 /* count To and Cc recipients */
5178 for (list = compose->header_list; list; list = list->next) {
5182 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5183 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5186 if ((entry[0] != '\0')
5187 && (strcmp(header, prefs_common_translated_header_name("To:"))
5188 || strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5194 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5196 gchar *button_label;
5199 if (compose->sending)
5200 button_label = g_strconcat("+", _("_Send"), NULL);
5202 button_label = g_strconcat("+", _("_Queue"), NULL);
5203 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5204 compose->sending?_("Send it anyway?"):
5205 _("Queue it anyway?"));
5207 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5208 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5209 ALERT_QUESTION, G_ALERTDEFAULT);
5211 if (aval & G_ALERTDISABLE) {
5212 aval &= ~G_ALERTDISABLE;
5213 prefs_common.warn_sending_many_recipients_num = 0;
5215 if (aval != G_ALERTALTERNATE)
5220 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5226 gint compose_send(Compose *compose)
5229 FolderItem *folder = NULL;
5231 gchar *msgpath = NULL;
5232 gboolean discard_window = FALSE;
5233 gchar *errstr = NULL;
5234 gchar *tmsgid = NULL;
5235 MainWindow *mainwin = mainwindow_get_mainwindow();
5236 gboolean queued_removed = FALSE;
5238 if (prefs_common.send_dialog_invisible
5239 || compose->batch == TRUE)
5240 discard_window = TRUE;
5242 compose_allow_user_actions (compose, FALSE);
5243 compose->sending = TRUE;
5245 if (compose_check_entries(compose, TRUE) == FALSE) {
5246 if (compose->batch) {
5247 gtk_widget_show_all(compose->window);
5253 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5256 if (compose->batch) {
5257 gtk_widget_show_all(compose->window);
5260 alertpanel_error(_("Could not queue message for sending:\n\n"
5261 "Charset conversion failed."));
5262 } else if (val == -5) {
5263 alertpanel_error(_("Could not queue message for sending:\n\n"
5264 "Couldn't get recipient encryption key."));
5265 } else if (val == -6) {
5267 } else if (val == -3) {
5268 if (privacy_peek_error())
5269 alertpanel_error(_("Could not queue message for sending:\n\n"
5270 "Signature failed: %s"), privacy_get_error());
5271 } else if (val == -2 && errno != 0) {
5272 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5274 alertpanel_error(_("Could not queue message for sending."));
5279 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5280 if (discard_window) {
5281 compose->sending = FALSE;
5282 compose_close(compose);
5283 /* No more compose access in the normal codepath
5284 * after this point! */
5289 alertpanel_error(_("The message was queued but could not be "
5290 "sent.\nUse \"Send queued messages\" from "
5291 "the main window to retry."));
5292 if (!discard_window) {
5299 if (msgpath == NULL) {
5300 msgpath = folder_item_fetch_msg(folder, msgnum);
5301 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5304 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5305 claws_unlink(msgpath);
5308 if (!discard_window) {
5310 if (!queued_removed)
5311 folder_item_remove_msg(folder, msgnum);
5312 folder_item_scan(folder);
5314 /* make sure we delete that */
5315 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5317 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5318 folder_item_remove_msg(folder, tmp->msgnum);
5319 procmsg_msginfo_free(&tmp);
5326 if (!queued_removed)
5327 folder_item_remove_msg(folder, msgnum);
5328 folder_item_scan(folder);
5330 /* make sure we delete that */
5331 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5333 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5334 folder_item_remove_msg(folder, tmp->msgnum);
5335 procmsg_msginfo_free(&tmp);
5338 if (!discard_window) {
5339 compose->sending = FALSE;
5340 compose_allow_user_actions (compose, TRUE);
5341 compose_close(compose);
5345 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5346 "the main window to retry."), errstr);
5349 alertpanel_error_log(_("The message was queued but could not be "
5350 "sent.\nUse \"Send queued messages\" from "
5351 "the main window to retry."));
5353 if (!discard_window) {
5362 toolbar_main_set_sensitive(mainwin);
5363 main_window_set_menu_sensitive(mainwin);
5369 compose_allow_user_actions (compose, TRUE);
5370 compose->sending = FALSE;
5371 compose->modified = TRUE;
5372 toolbar_main_set_sensitive(mainwin);
5373 main_window_set_menu_sensitive(mainwin);
5378 static gboolean compose_use_attach(Compose *compose)
5380 GtkTreeModel *model = gtk_tree_view_get_model
5381 (GTK_TREE_VIEW(compose->attach_clist));
5382 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5385 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5388 gchar buf[BUFFSIZE];
5390 gboolean first_to_address;
5391 gboolean first_cc_address;
5393 ComposeHeaderEntry *headerentry;
5394 const gchar *headerentryname;
5395 const gchar *cc_hdr;
5396 const gchar *to_hdr;
5397 gboolean err = FALSE;
5399 debug_print("Writing redirect header\n");
5401 cc_hdr = prefs_common_translated_header_name("Cc:");
5402 to_hdr = prefs_common_translated_header_name("To:");
5404 first_to_address = TRUE;
5405 for (list = compose->header_list; list; list = list->next) {
5406 headerentry = ((ComposeHeaderEntry *)list->data);
5407 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5409 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5410 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5411 Xstrdup_a(str, entstr, return -1);
5413 if (str[0] != '\0') {
5414 compose_convert_header
5415 (compose, buf, sizeof(buf), str,
5416 strlen("Resent-To") + 2, TRUE);
5418 if (first_to_address) {
5419 err |= (fprintf(fp, "Resent-To: ") < 0);
5420 first_to_address = FALSE;
5422 err |= (fprintf(fp, ",") < 0);
5424 err |= (fprintf(fp, "%s", buf) < 0);
5428 if (!first_to_address) {
5429 err |= (fprintf(fp, "\n") < 0);
5432 first_cc_address = TRUE;
5433 for (list = compose->header_list; list; list = list->next) {
5434 headerentry = ((ComposeHeaderEntry *)list->data);
5435 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5437 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5438 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5439 Xstrdup_a(str, strg, return -1);
5441 if (str[0] != '\0') {
5442 compose_convert_header
5443 (compose, buf, sizeof(buf), str,
5444 strlen("Resent-Cc") + 2, TRUE);
5446 if (first_cc_address) {
5447 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5448 first_cc_address = FALSE;
5450 err |= (fprintf(fp, ",") < 0);
5452 err |= (fprintf(fp, "%s", buf) < 0);
5456 if (!first_cc_address) {
5457 err |= (fprintf(fp, "\n") < 0);
5460 return (err ? -1:0);
5463 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5465 gchar date[RFC822_DATE_BUFFSIZE];
5466 gchar buf[BUFFSIZE];
5468 const gchar *entstr;
5469 /* struct utsname utsbuf; */
5470 gboolean err = FALSE;
5472 cm_return_val_if_fail(fp != NULL, -1);
5473 cm_return_val_if_fail(compose->account != NULL, -1);
5474 cm_return_val_if_fail(compose->account->address != NULL, -1);
5477 if (prefs_common.hide_timezone)
5478 get_rfc822_date_hide_tz(date, sizeof(date));
5480 get_rfc822_date(date, sizeof(date));
5481 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5484 if (compose->account->name && *compose->account->name) {
5485 compose_convert_header
5486 (compose, buf, sizeof(buf), compose->account->name,
5487 strlen("From: "), TRUE);
5488 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5489 buf, compose->account->address) < 0);
5491 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5494 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5495 if (*entstr != '\0') {
5496 Xstrdup_a(str, entstr, return -1);
5499 compose_convert_header(compose, buf, sizeof(buf), str,
5500 strlen("Subject: "), FALSE);
5501 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5505 /* Resent-Message-ID */
5506 if (compose->account->gen_msgid) {
5507 gchar *addr = prefs_account_generate_msgid(compose->account);
5508 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5510 g_free(compose->msgid);
5511 compose->msgid = addr;
5513 compose->msgid = NULL;
5516 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5519 /* separator between header and body */
5520 err |= (fputs("\n", fp) == EOF);
5522 return (err ? -1:0);
5525 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5530 gchar rewrite_buf[BUFFSIZE];
5532 gboolean skip = FALSE;
5533 gboolean err = FALSE;
5534 gchar *not_included[]={
5535 "Return-Path:", "Delivered-To:", "Received:",
5536 "Subject:", "X-UIDL:", "AF:",
5537 "NF:", "PS:", "SRH:",
5538 "SFN:", "DSR:", "MID:",
5539 "CFG:", "PT:", "S:",
5540 "RQ:", "SSV:", "NSV:",
5541 "SSH:", "R:", "MAID:",
5542 "NAID:", "RMID:", "FMID:",
5543 "SCF:", "RRCPT:", "NG:",
5544 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5545 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5546 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5547 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5548 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5553 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5554 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5558 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5560 for (i = 0; not_included[i] != NULL; i++) {
5561 if (g_ascii_strncasecmp(buf, not_included[i],
5562 strlen(not_included[i])) == 0) {
5572 if (fputs(buf, fdest) == -1) {
5578 if (!prefs_common.redirect_keep_from) {
5579 if (g_ascii_strncasecmp(buf, "From:",
5580 strlen("From:")) == 0) {
5581 err |= (fputs(" (by way of ", fdest) == EOF);
5582 if (compose->account->name
5583 && *compose->account->name) {
5584 gchar buffer[BUFFSIZE];
5586 compose_convert_header
5587 (compose, buffer, sizeof(buffer),
5588 compose->account->name,
5591 err |= (fprintf(fdest, "%s <%s>",
5593 compose->account->address) < 0);
5595 err |= (fprintf(fdest, "%s",
5596 compose->account->address) < 0);
5597 err |= (fputs(")", fdest) == EOF);
5603 if (fputs("\n", fdest) == -1)
5610 if (compose_redirect_write_headers(compose, fdest))
5613 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5614 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5628 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5630 GtkTextBuffer *buffer;
5631 GtkTextIter start, end, tmp;
5632 gchar *chars, *tmp_enc_file, *content;
5634 const gchar *out_codeset;
5635 EncodingType encoding = ENC_UNKNOWN;
5636 MimeInfo *mimemsg, *mimetext;
5638 const gchar *src_codeset = CS_INTERNAL;
5639 gchar *from_addr = NULL;
5640 gchar *from_name = NULL;
5643 if (action == COMPOSE_WRITE_FOR_SEND) {
5644 attach_parts = TRUE;
5646 /* We're sending the message, generate a Message-ID
5648 if (compose->msgid == NULL &&
5649 compose->account->gen_msgid) {
5650 compose->msgid = prefs_account_generate_msgid(compose->account);
5654 /* create message MimeInfo */
5655 mimemsg = procmime_mimeinfo_new();
5656 mimemsg->type = MIMETYPE_MESSAGE;
5657 mimemsg->subtype = g_strdup("rfc822");
5658 mimemsg->content = MIMECONTENT_MEM;
5659 mimemsg->tmp = TRUE; /* must free content later */
5660 mimemsg->data.mem = compose_get_header(compose);
5662 /* Create text part MimeInfo */
5663 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5664 gtk_text_buffer_get_end_iter(buffer, &end);
5667 /* We make sure that there is a newline at the end. */
5668 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5669 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5670 if (*chars != '\n') {
5671 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5675 /* get all composed text */
5676 gtk_text_buffer_get_start_iter(buffer, &start);
5677 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5679 out_codeset = conv_get_charset_str(compose->out_encoding);
5681 if (!out_codeset && is_ascii_str(chars)) {
5682 out_codeset = CS_US_ASCII;
5683 } else if (prefs_common.outgoing_fallback_to_ascii &&
5684 is_ascii_str(chars)) {
5685 out_codeset = CS_US_ASCII;
5686 encoding = ENC_7BIT;
5690 gchar *test_conv_global_out = NULL;
5691 gchar *test_conv_reply = NULL;
5693 /* automatic mode. be automatic. */
5694 codeconv_set_strict(TRUE);
5696 out_codeset = conv_get_outgoing_charset_str();
5698 debug_print("trying to convert to %s\n", out_codeset);
5699 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5702 if (!test_conv_global_out && compose->orig_charset
5703 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5704 out_codeset = compose->orig_charset;
5705 debug_print("failure; trying to convert to %s\n", out_codeset);
5706 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5709 if (!test_conv_global_out && !test_conv_reply) {
5711 out_codeset = CS_INTERNAL;
5712 debug_print("failure; finally using %s\n", out_codeset);
5714 g_free(test_conv_global_out);
5715 g_free(test_conv_reply);
5716 codeconv_set_strict(FALSE);
5719 if (encoding == ENC_UNKNOWN) {
5720 if (prefs_common.encoding_method == CTE_BASE64)
5721 encoding = ENC_BASE64;
5722 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5723 encoding = ENC_QUOTED_PRINTABLE;
5724 else if (prefs_common.encoding_method == CTE_8BIT)
5725 encoding = ENC_8BIT;
5727 encoding = procmime_get_encoding_for_charset(out_codeset);
5730 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5731 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5733 if (action == COMPOSE_WRITE_FOR_SEND) {
5734 codeconv_set_strict(TRUE);
5735 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5736 codeconv_set_strict(FALSE);
5741 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5742 "to the specified %s charset.\n"
5743 "Send it as %s?"), out_codeset, src_codeset);
5744 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5745 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5746 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5749 if (aval != G_ALERTALTERNATE) {
5754 out_codeset = src_codeset;
5760 out_codeset = src_codeset;
5765 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5766 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5767 strstr(buf, "\nFrom ") != NULL) {
5768 encoding = ENC_QUOTED_PRINTABLE;
5772 mimetext = procmime_mimeinfo_new();
5773 mimetext->content = MIMECONTENT_MEM;
5774 mimetext->tmp = TRUE; /* must free content later */
5775 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5776 * and free the data, which we need later. */
5777 mimetext->data.mem = g_strdup(buf);
5778 mimetext->type = MIMETYPE_TEXT;
5779 mimetext->subtype = g_strdup("plain");
5780 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5781 g_strdup(out_codeset));
5783 /* protect trailing spaces when signing message */
5784 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5785 privacy_system_can_sign(compose->privacy_system)) {
5786 encoding = ENC_QUOTED_PRINTABLE;
5790 debug_print("main text: %Id bytes encoded as %s in %d\n",
5792 debug_print("main text: %zd bytes encoded as %s in %d\n",
5794 strlen(buf), out_codeset, encoding);
5796 /* check for line length limit */
5797 if (action == COMPOSE_WRITE_FOR_SEND &&
5798 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5799 check_line_length(buf, 1000, &line) < 0) {
5802 msg = g_strdup_printf
5803 (_("Line %d exceeds the line length limit (998 bytes).\n"
5804 "The contents of the message might be broken on the way to the delivery.\n"
5806 "Send it anyway?"), line + 1);
5807 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5809 if (aval != G_ALERTALTERNATE) {
5815 if (encoding != ENC_UNKNOWN)
5816 procmime_encode_content(mimetext, encoding);
5818 /* append attachment parts */
5819 if (compose_use_attach(compose) && attach_parts) {
5820 MimeInfo *mimempart;
5821 gchar *boundary = NULL;
5822 mimempart = procmime_mimeinfo_new();
5823 mimempart->content = MIMECONTENT_EMPTY;
5824 mimempart->type = MIMETYPE_MULTIPART;
5825 mimempart->subtype = g_strdup("mixed");
5829 boundary = generate_mime_boundary(NULL);
5830 } while (strstr(buf, boundary) != NULL);
5832 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5835 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5837 g_node_append(mimempart->node, mimetext->node);
5838 g_node_append(mimemsg->node, mimempart->node);
5840 if (compose_add_attachments(compose, mimempart) < 0)
5843 g_node_append(mimemsg->node, mimetext->node);
5847 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5848 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5849 /* extract name and address */
5850 if (strstr(spec, " <") && strstr(spec, ">")) {
5851 from_addr = g_strdup(strrchr(spec, '<')+1);
5852 *(strrchr(from_addr, '>')) = '\0';
5853 from_name = g_strdup(spec);
5854 *(strrchr(from_name, '<')) = '\0';
5861 /* sign message if sending */
5862 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5863 privacy_system_can_sign(compose->privacy_system))
5864 if (!privacy_sign(compose->privacy_system, mimemsg,
5865 compose->account, from_addr)) {
5873 if (compose->use_encryption) {
5874 if (compose->encdata != NULL &&
5875 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5877 /* First, write an unencrypted copy and save it to outbox, if
5878 * user wants that. */
5879 if (compose->account->save_encrypted_as_clear_text) {
5880 debug_print("saving sent message unencrypted...\n");
5881 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5885 /* fp now points to a file with headers written,
5886 * let's make a copy. */
5888 content = file_read_stream_to_str(fp);
5890 str_write_to_file(content, tmp_enc_file);
5893 /* Now write the unencrypted body. */
5894 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5895 procmime_write_mimeinfo(mimemsg, tmpfp);
5898 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5900 outbox = folder_get_default_outbox();
5902 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5903 claws_unlink(tmp_enc_file);
5905 g_warning("Can't open file '%s'", tmp_enc_file);
5908 g_warning("couldn't get tempfile");
5911 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5912 debug_print("Couldn't encrypt mime structure: %s.\n",
5913 privacy_get_error());
5914 alertpanel_error(_("Couldn't encrypt the email: %s"),
5915 privacy_get_error());
5920 procmime_write_mimeinfo(mimemsg, fp);
5922 procmime_mimeinfo_free_all(&mimemsg);
5927 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5929 GtkTextBuffer *buffer;
5930 GtkTextIter start, end;
5935 if ((fp = g_fopen(file, "wb")) == NULL) {
5936 FILE_OP_ERROR(file, "fopen");
5940 /* chmod for security */
5941 if (change_file_mode_rw(fp, file) < 0) {
5942 FILE_OP_ERROR(file, "chmod");
5943 g_warning("can't change file mode");
5946 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5947 gtk_text_buffer_get_start_iter(buffer, &start);
5948 gtk_text_buffer_get_end_iter(buffer, &end);
5949 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5951 chars = conv_codeset_strdup
5952 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5961 len = strlen(chars);
5962 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5963 FILE_OP_ERROR(file, "fwrite");
5972 if (fclose(fp) == EOF) {
5973 FILE_OP_ERROR(file, "fclose");
5980 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5983 MsgInfo *msginfo = compose->targetinfo;
5985 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5986 if (!msginfo) return -1;
5988 if (!force && MSG_IS_LOCKED(msginfo->flags))
5991 item = msginfo->folder;
5992 cm_return_val_if_fail(item != NULL, -1);
5994 if (procmsg_msg_exist(msginfo) &&
5995 (folder_has_parent_of_type(item, F_QUEUE) ||
5996 folder_has_parent_of_type(item, F_DRAFT)
5997 || msginfo == compose->autosaved_draft)) {
5998 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5999 g_warning("can't remove the old message");
6002 debug_print("removed reedit target %d\n", msginfo->msgnum);
6009 static void compose_remove_draft(Compose *compose)
6012 MsgInfo *msginfo = compose->targetinfo;
6013 drafts = account_get_special_folder(compose->account, F_DRAFT);
6015 if (procmsg_msg_exist(msginfo)) {
6016 folder_item_remove_msg(drafts, msginfo->msgnum);
6021 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6022 gboolean remove_reedit_target)
6024 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6027 static gboolean compose_warn_encryption(Compose *compose)
6029 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6030 AlertValue val = G_ALERTALTERNATE;
6032 if (warning == NULL)
6035 val = alertpanel_full(_("Encryption warning"), warning,
6036 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6037 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6038 if (val & G_ALERTDISABLE) {
6039 val &= ~G_ALERTDISABLE;
6040 if (val == G_ALERTALTERNATE)
6041 privacy_inhibit_encrypt_warning(compose->privacy_system,
6045 if (val == G_ALERTALTERNATE) {
6052 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6053 gchar **msgpath, gboolean perform_checks,
6054 gboolean remove_reedit_target)
6061 PrefsAccount *mailac = NULL, *newsac = NULL;
6062 gboolean err = FALSE;
6064 debug_print("queueing message...\n");
6065 cm_return_val_if_fail(compose->account != NULL, -1);
6067 if (compose_check_entries(compose, perform_checks) == FALSE) {
6068 if (compose->batch) {
6069 gtk_widget_show_all(compose->window);
6074 if (!compose->to_list && !compose->newsgroup_list) {
6075 g_warning("can't get recipient list.");
6079 if (compose->to_list) {
6080 if (compose->account->protocol != A_NNTP)
6081 mailac = compose->account;
6082 else if (cur_account && cur_account->protocol != A_NNTP)
6083 mailac = cur_account;
6084 else if (!(mailac = compose_current_mail_account())) {
6085 alertpanel_error(_("No account for sending mails available!"));
6090 if (compose->newsgroup_list) {
6091 if (compose->account->protocol == A_NNTP)
6092 newsac = compose->account;
6094 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6099 /* write queue header */
6100 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6101 G_DIR_SEPARATOR, compose, (guint) rand());
6102 debug_print("queuing to %s\n", tmp);
6103 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6104 FILE_OP_ERROR(tmp, "fopen");
6109 if (change_file_mode_rw(fp, tmp) < 0) {
6110 FILE_OP_ERROR(tmp, "chmod");
6111 g_warning("can't change file mode");
6114 /* queueing variables */
6115 err |= (fprintf(fp, "AF:\n") < 0);
6116 err |= (fprintf(fp, "NF:0\n") < 0);
6117 err |= (fprintf(fp, "PS:10\n") < 0);
6118 err |= (fprintf(fp, "SRH:1\n") < 0);
6119 err |= (fprintf(fp, "SFN:\n") < 0);
6120 err |= (fprintf(fp, "DSR:\n") < 0);
6122 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6124 err |= (fprintf(fp, "MID:\n") < 0);
6125 err |= (fprintf(fp, "CFG:\n") < 0);
6126 err |= (fprintf(fp, "PT:0\n") < 0);
6127 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6128 err |= (fprintf(fp, "RQ:\n") < 0);
6130 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6132 err |= (fprintf(fp, "SSV:\n") < 0);
6134 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6136 err |= (fprintf(fp, "NSV:\n") < 0);
6137 err |= (fprintf(fp, "SSH:\n") < 0);
6138 /* write recepient list */
6139 if (compose->to_list) {
6140 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6141 for (cur = compose->to_list->next; cur != NULL;
6143 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6144 err |= (fprintf(fp, "\n") < 0);
6146 /* write newsgroup list */
6147 if (compose->newsgroup_list) {
6148 err |= (fprintf(fp, "NG:") < 0);
6149 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6150 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6151 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6152 err |= (fprintf(fp, "\n") < 0);
6154 /* Sylpheed account IDs */
6156 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6158 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6161 if (compose->privacy_system != NULL) {
6162 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6163 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6164 if (compose->use_encryption) {
6165 if (!compose_warn_encryption(compose)) {
6171 if (mailac && mailac->encrypt_to_self) {
6172 GSList *tmp_list = g_slist_copy(compose->to_list);
6173 tmp_list = g_slist_append(tmp_list, compose->account->address);
6174 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6175 g_slist_free(tmp_list);
6177 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6179 if (compose->encdata != NULL) {
6180 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6181 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6182 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6183 compose->encdata) < 0);
6184 } /* else we finally dont want to encrypt */
6186 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6187 /* and if encdata was null, it means there's been a problem in
6190 g_warning("failed to write queue message");
6199 /* Save copy folder */
6200 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6201 gchar *savefolderid;
6203 savefolderid = compose_get_save_to(compose);
6204 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6205 g_free(savefolderid);
6207 /* Save copy folder */
6208 if (compose->return_receipt) {
6209 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6211 /* Message-ID of message replying to */
6212 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6213 gchar *folderid = NULL;
6215 if (compose->replyinfo->folder)
6216 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6217 if (folderid == NULL)
6218 folderid = g_strdup("NULL");
6220 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6223 /* Message-ID of message forwarding to */
6224 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6225 gchar *folderid = NULL;
6227 if (compose->fwdinfo->folder)
6228 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6229 if (folderid == NULL)
6230 folderid = g_strdup("NULL");
6232 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6236 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6237 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6239 /* end of headers */
6240 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6242 if (compose->redirect_filename != NULL) {
6243 if (compose_redirect_write_to_file(compose, fp) < 0) {
6251 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6255 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6259 g_warning("failed to write queue message");
6265 if (fclose(fp) == EOF) {
6266 FILE_OP_ERROR(tmp, "fclose");
6272 if (item && *item) {
6275 queue = account_get_special_folder(compose->account, F_QUEUE);
6278 g_warning("can't find queue folder");
6283 folder_item_scan(queue);
6284 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6285 g_warning("can't queue the message");
6291 if (msgpath == NULL) {
6297 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6298 compose_remove_reedit_target(compose, FALSE);
6301 if ((msgnum != NULL) && (item != NULL)) {
6309 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6312 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6315 gchar *type, *subtype;
6316 GtkTreeModel *model;
6319 model = gtk_tree_view_get_model(tree_view);
6321 if (!gtk_tree_model_get_iter_first(model, &iter))
6324 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6326 if (!is_file_exist(ainfo->file)) {
6327 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6328 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6329 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6331 if (val == G_ALERTDEFAULT) {
6336 if (g_stat(ainfo->file, &statbuf) < 0)
6339 mimepart = procmime_mimeinfo_new();
6340 mimepart->content = MIMECONTENT_FILE;
6341 mimepart->data.filename = g_strdup(ainfo->file);
6342 mimepart->tmp = FALSE; /* or we destroy our attachment */
6343 mimepart->offset = 0;
6344 mimepart->length = statbuf.st_size;
6346 type = g_strdup(ainfo->content_type);
6348 if (!strchr(type, '/')) {
6350 type = g_strdup("application/octet-stream");
6353 subtype = strchr(type, '/') + 1;
6354 *(subtype - 1) = '\0';
6355 mimepart->type = procmime_get_media_type(type);
6356 mimepart->subtype = g_strdup(subtype);
6359 if (mimepart->type == MIMETYPE_MESSAGE &&
6360 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6361 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6362 } else if (mimepart->type == MIMETYPE_TEXT) {
6363 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6364 /* Text parts with no name come from multipart/alternative
6365 * forwards. Make sure the recipient won't look at the
6366 * original HTML part by mistake. */
6367 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6368 ainfo->name = g_strdup_printf(_("Original %s part"),
6372 g_hash_table_insert(mimepart->typeparameters,
6373 g_strdup("charset"), g_strdup(ainfo->charset));
6375 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6376 if (mimepart->type == MIMETYPE_APPLICATION &&
6377 !strcmp2(mimepart->subtype, "octet-stream"))
6378 g_hash_table_insert(mimepart->typeparameters,
6379 g_strdup("name"), g_strdup(ainfo->name));
6380 g_hash_table_insert(mimepart->dispositionparameters,
6381 g_strdup("filename"), g_strdup(ainfo->name));
6382 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6385 if (mimepart->type == MIMETYPE_MESSAGE
6386 || mimepart->type == MIMETYPE_MULTIPART)
6387 ainfo->encoding = ENC_BINARY;
6388 else if (compose->use_signing) {
6389 if (ainfo->encoding == ENC_7BIT)
6390 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6391 else if (ainfo->encoding == ENC_8BIT)
6392 ainfo->encoding = ENC_BASE64;
6395 procmime_encode_content(mimepart, ainfo->encoding);
6397 g_node_append(parent->node, mimepart->node);
6398 } while (gtk_tree_model_iter_next(model, &iter));
6403 static gchar *compose_quote_list_of_addresses(gchar *str)
6405 GSList *list = NULL, *item = NULL;
6406 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6408 list = address_list_append_with_comments(list, str);
6409 for (item = list; item != NULL; item = item->next) {
6410 gchar *spec = item->data;
6411 gchar *endofname = strstr(spec, " <");
6412 if (endofname != NULL) {
6415 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6416 qqname = escape_internal_quotes(qname, '"');
6418 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6419 gchar *addr = g_strdup(endofname);
6420 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6421 faddr = g_strconcat(name, addr, NULL);
6424 debug_print("new auto-quoted address: '%s'\n", faddr);
6428 result = g_strdup((faddr != NULL)? faddr: spec);
6430 result = g_strconcat(result,
6432 (faddr != NULL)? faddr: spec,
6435 if (faddr != NULL) {
6440 slist_free_strings_full(list);
6445 #define IS_IN_CUSTOM_HEADER(header) \
6446 (compose->account->add_customhdr && \
6447 custom_header_find(compose->account->customhdr_list, header) != NULL)
6449 static const gchar *compose_untranslated_header_name(gchar *header_name)
6451 /* return the untranslated header name, if header_name is a known
6452 header name, in either its translated or untranslated form, with
6453 or without trailing colon. otherwise, returns header_name. */
6454 gchar *translated_header_name;
6455 gchar *translated_header_name_wcolon;
6456 const gchar *untranslated_header_name;
6457 const gchar *untranslated_header_name_wcolon;
6460 cm_return_val_if_fail(header_name != NULL, NULL);
6462 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6463 untranslated_header_name = HEADERS[i].header_name;
6464 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6466 translated_header_name = gettext(untranslated_header_name);
6467 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6469 if (!strcmp(header_name, untranslated_header_name) ||
6470 !strcmp(header_name, translated_header_name)) {
6471 return untranslated_header_name;
6473 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6474 !strcmp(header_name, translated_header_name_wcolon)) {
6475 return untranslated_header_name_wcolon;
6479 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6483 static void compose_add_headerfield_from_headerlist(Compose *compose,
6485 const gchar *fieldname,
6486 const gchar *seperator)
6488 gchar *str, *fieldname_w_colon;
6489 gboolean add_field = FALSE;
6491 ComposeHeaderEntry *headerentry;
6492 const gchar *headerentryname;
6493 const gchar *trans_fieldname;
6496 if (IS_IN_CUSTOM_HEADER(fieldname))
6499 debug_print("Adding %s-fields\n", fieldname);
6501 fieldstr = g_string_sized_new(64);
6503 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6504 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6506 for (list = compose->header_list; list; list = list->next) {
6507 headerentry = ((ComposeHeaderEntry *)list->data);
6508 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6510 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6511 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6513 str = compose_quote_list_of_addresses(ustr);
6515 if (str != NULL && str[0] != '\0') {
6517 g_string_append(fieldstr, seperator);
6518 g_string_append(fieldstr, str);
6527 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6528 compose_convert_header
6529 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6530 strlen(fieldname) + 2, TRUE);
6531 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6535 g_free(fieldname_w_colon);
6536 g_string_free(fieldstr, TRUE);
6541 static gchar *compose_get_manual_headers_info(Compose *compose)
6543 GString *sh_header = g_string_new(" ");
6545 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6547 for (list = compose->header_list; list; list = list->next) {
6548 ComposeHeaderEntry *headerentry;
6551 gchar *headername_wcolon;
6552 const gchar *headername_trans;
6554 gboolean standard_header = FALSE;
6556 headerentry = ((ComposeHeaderEntry *)list->data);
6558 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6560 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6565 if (!strstr(tmp, ":")) {
6566 headername_wcolon = g_strconcat(tmp, ":", NULL);
6567 headername = g_strdup(tmp);
6569 headername_wcolon = g_strdup(tmp);
6570 headername = g_strdup(strtok(tmp, ":"));
6574 string = std_headers;
6575 while (*string != NULL) {
6576 headername_trans = prefs_common_translated_header_name(*string);
6577 if (!strcmp(headername_trans, headername_wcolon))
6578 standard_header = TRUE;
6581 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6582 g_string_append_printf(sh_header, "%s ", headername);
6584 g_free(headername_wcolon);
6586 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6587 return g_string_free(sh_header, FALSE);
6590 static gchar *compose_get_header(Compose *compose)
6592 gchar date[RFC822_DATE_BUFFSIZE];
6593 gchar buf[BUFFSIZE];
6594 const gchar *entry_str;
6598 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6600 gchar *from_name = NULL, *from_address = NULL;
6603 cm_return_val_if_fail(compose->account != NULL, NULL);
6604 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6606 header = g_string_sized_new(64);
6609 if (prefs_common.hide_timezone)
6610 get_rfc822_date_hide_tz(date, sizeof(date));
6612 get_rfc822_date(date, sizeof(date));
6613 g_string_append_printf(header, "Date: %s\n", date);
6617 if (compose->account->name && *compose->account->name) {
6619 QUOTE_IF_REQUIRED(buf, compose->account->name);
6620 tmp = g_strdup_printf("%s <%s>",
6621 buf, compose->account->address);
6623 tmp = g_strdup_printf("%s",
6624 compose->account->address);
6626 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6627 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6629 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6630 from_address = g_strdup(compose->account->address);
6632 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6633 /* extract name and address */
6634 if (strstr(spec, " <") && strstr(spec, ">")) {
6635 from_address = g_strdup(strrchr(spec, '<')+1);
6636 *(strrchr(from_address, '>')) = '\0';
6637 from_name = g_strdup(spec);
6638 *(strrchr(from_name, '<')) = '\0';
6641 from_address = g_strdup(spec);
6648 if (from_name && *from_name) {
6650 compose_convert_header
6651 (compose, buf, sizeof(buf), from_name,
6652 strlen("From: "), TRUE);
6653 QUOTE_IF_REQUIRED(name, buf);
6654 qname = escape_internal_quotes(name, '"');
6656 g_string_append_printf(header, "From: %s <%s>\n",
6657 qname, from_address);
6658 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6659 compose->return_receipt) {
6660 compose_convert_header(compose, buf, sizeof(buf), from_name,
6661 strlen("Disposition-Notification-To: "),
6663 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6668 g_string_append_printf(header, "From: %s\n", from_address);
6669 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6670 compose->return_receipt)
6671 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6675 g_free(from_address);
6678 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6681 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6684 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6688 * If this account is a NNTP account remove Bcc header from
6689 * message body since it otherwise will be publicly shown
6691 if (compose->account->protocol != A_NNTP)
6692 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6695 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6697 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6700 compose_convert_header(compose, buf, sizeof(buf), str,
6701 strlen("Subject: "), FALSE);
6702 g_string_append_printf(header, "Subject: %s\n", buf);
6708 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6709 g_string_append_printf(header, "Message-ID: <%s>\n",
6713 if (compose->remove_references == FALSE) {
6715 if (compose->inreplyto && compose->to_list)
6716 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6719 if (compose->references)
6720 g_string_append_printf(header, "References: %s\n", compose->references);
6724 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6727 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6730 if (compose->account->organization &&
6731 strlen(compose->account->organization) &&
6732 !IS_IN_CUSTOM_HEADER("Organization")) {
6733 compose_convert_header(compose, buf, sizeof(buf),
6734 compose->account->organization,
6735 strlen("Organization: "), FALSE);
6736 g_string_append_printf(header, "Organization: %s\n", buf);
6739 /* Program version and system info */
6740 if (compose->account->gen_xmailer &&
6741 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6742 !compose->newsgroup_list) {
6743 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6745 gtk_major_version, gtk_minor_version, gtk_micro_version,
6748 if (compose->account->gen_xmailer &&
6749 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6750 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6752 gtk_major_version, gtk_minor_version, gtk_micro_version,
6756 /* custom headers */
6757 if (compose->account->add_customhdr) {
6760 for (cur = compose->account->customhdr_list; cur != NULL;
6762 CustomHeader *chdr = (CustomHeader *)cur->data;
6764 if (custom_header_is_allowed(chdr->name)
6765 && chdr->value != NULL
6766 && *(chdr->value) != '\0') {
6767 compose_convert_header
6768 (compose, buf, sizeof(buf),
6770 strlen(chdr->name) + 2, FALSE);
6771 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6776 /* Automatic Faces and X-Faces */
6777 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6778 g_string_append_printf(header, "X-Face: %s\n", buf);
6780 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6781 g_string_append_printf(header, "X-Face: %s\n", buf);
6783 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6784 g_string_append_printf(header, "Face: %s\n", buf);
6786 else if (get_default_face (buf, sizeof(buf)) == 0) {
6787 g_string_append_printf(header, "Face: %s\n", buf);
6791 switch (compose->priority) {
6792 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6793 "X-Priority: 1 (Highest)\n");
6795 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6796 "X-Priority: 2 (High)\n");
6798 case PRIORITY_NORMAL: break;
6799 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6800 "X-Priority: 4 (Low)\n");
6802 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6803 "X-Priority: 5 (Lowest)\n");
6805 default: debug_print("compose: priority unknown : %d\n",
6809 /* get special headers */
6810 for (list = compose->header_list; list; list = list->next) {
6811 ComposeHeaderEntry *headerentry;
6814 gchar *headername_wcolon;
6815 const gchar *headername_trans;
6818 gboolean standard_header = FALSE;
6820 headerentry = ((ComposeHeaderEntry *)list->data);
6822 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6824 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6829 if (!strstr(tmp, ":")) {
6830 headername_wcolon = g_strconcat(tmp, ":", NULL);
6831 headername = g_strdup(tmp);
6833 headername_wcolon = g_strdup(tmp);
6834 headername = g_strdup(strtok(tmp, ":"));
6838 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6839 Xstrdup_a(headervalue, entry_str, return NULL);
6840 subst_char(headervalue, '\r', ' ');
6841 subst_char(headervalue, '\n', ' ');
6842 g_strstrip(headervalue);
6843 if (*headervalue != '\0') {
6844 string = std_headers;
6845 while (*string != NULL && !standard_header) {
6846 headername_trans = prefs_common_translated_header_name(*string);
6847 /* support mixed translated and untranslated headers */
6848 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6849 standard_header = TRUE;
6852 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6853 /* store untranslated header name */
6854 g_string_append_printf(header, "%s %s\n",
6855 compose_untranslated_header_name(headername_wcolon), headervalue);
6859 g_free(headername_wcolon);
6863 g_string_free(header, FALSE);
6868 #undef IS_IN_CUSTOM_HEADER
6870 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6871 gint header_len, gboolean addr_field)
6873 gchar *tmpstr = NULL;
6874 const gchar *out_codeset = NULL;
6876 cm_return_if_fail(src != NULL);
6877 cm_return_if_fail(dest != NULL);
6879 if (len < 1) return;
6881 tmpstr = g_strdup(src);
6883 subst_char(tmpstr, '\n', ' ');
6884 subst_char(tmpstr, '\r', ' ');
6887 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6888 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6889 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6894 codeconv_set_strict(TRUE);
6895 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6896 conv_get_charset_str(compose->out_encoding));
6897 codeconv_set_strict(FALSE);
6899 if (!dest || *dest == '\0') {
6900 gchar *test_conv_global_out = NULL;
6901 gchar *test_conv_reply = NULL;
6903 /* automatic mode. be automatic. */
6904 codeconv_set_strict(TRUE);
6906 out_codeset = conv_get_outgoing_charset_str();
6908 debug_print("trying to convert to %s\n", out_codeset);
6909 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6912 if (!test_conv_global_out && compose->orig_charset
6913 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6914 out_codeset = compose->orig_charset;
6915 debug_print("failure; trying to convert to %s\n", out_codeset);
6916 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6919 if (!test_conv_global_out && !test_conv_reply) {
6921 out_codeset = CS_INTERNAL;
6922 debug_print("finally using %s\n", out_codeset);
6924 g_free(test_conv_global_out);
6925 g_free(test_conv_reply);
6926 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6928 codeconv_set_strict(FALSE);
6933 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6937 cm_return_if_fail(user_data != NULL);
6939 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6940 g_strstrip(address);
6941 if (*address != '\0') {
6942 gchar *name = procheader_get_fromname(address);
6943 extract_address(address);
6944 #ifndef USE_ALT_ADDRBOOK
6945 addressbook_add_contact(name, address, NULL, NULL);
6947 debug_print("%s: %s\n", name, address);
6948 if (addressadd_selection(name, address, NULL, NULL)) {
6949 debug_print( "addressbook_add_contact - added\n" );
6956 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6958 GtkWidget *menuitem;
6961 cm_return_if_fail(menu != NULL);
6962 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6964 menuitem = gtk_separator_menu_item_new();
6965 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6966 gtk_widget_show(menuitem);
6968 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6969 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6971 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6972 g_strstrip(address);
6973 if (*address == '\0') {
6974 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6977 g_signal_connect(G_OBJECT(menuitem), "activate",
6978 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6979 gtk_widget_show(menuitem);
6982 void compose_add_extra_header(gchar *header, GtkListStore *model)
6985 if (strcmp(header, "")) {
6986 COMBOBOX_ADD(model, header, COMPOSE_TO);
6990 void compose_add_extra_header_entries(GtkListStore *model)
6994 gchar buf[BUFFSIZE];
6997 if (extra_headers == NULL) {
6998 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6999 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7000 debug_print("extra headers file not found\n");
7001 goto extra_headers_done;
7003 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7004 lastc = strlen(buf) - 1; /* remove trailing control chars */
7005 while (lastc >= 0 && buf[lastc] != ':')
7006 buf[lastc--] = '\0';
7007 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7008 buf[lastc] = '\0'; /* remove trailing : for comparison */
7009 if (custom_header_is_allowed(buf)) {
7011 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7014 g_message("disallowed extra header line: %s\n", buf);
7018 g_message("invalid extra header line: %s\n", buf);
7024 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7025 extra_headers = g_slist_reverse(extra_headers);
7027 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7030 static void compose_create_header_entry(Compose *compose)
7032 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7039 const gchar *header = NULL;
7040 ComposeHeaderEntry *headerentry;
7041 gboolean standard_header = FALSE;
7042 GtkListStore *model;
7045 headerentry = g_new0(ComposeHeaderEntry, 1);
7047 /* Combo box model */
7048 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7049 #if !GTK_CHECK_VERSION(2, 24, 0)
7050 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7052 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7054 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7056 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7058 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7059 COMPOSE_NEWSGROUPS);
7060 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7062 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7063 COMPOSE_FOLLOWUPTO);
7064 compose_add_extra_header_entries(model);
7067 #if GTK_CHECK_VERSION(2, 24, 0)
7068 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7069 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7070 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7071 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7072 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7074 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7075 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7076 G_CALLBACK(compose_grab_focus_cb), compose);
7077 gtk_widget_show(combo);
7079 /* Putting only the combobox child into focus chain of its parent causes
7080 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7081 * This eliminates need to pres Tab twice in order to really get from the
7082 * combobox to next widget. */
7084 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7085 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7088 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7089 compose->header_nextrow, compose->header_nextrow+1,
7090 GTK_SHRINK, GTK_FILL, 0, 0);
7091 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7092 const gchar *last_header_entry = gtk_entry_get_text(
7093 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7095 while (*string != NULL) {
7096 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7097 standard_header = TRUE;
7100 if (standard_header)
7101 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7103 if (!compose->header_last || !standard_header) {
7104 switch(compose->account->protocol) {
7106 header = prefs_common_translated_header_name("Newsgroups:");
7109 header = prefs_common_translated_header_name("To:");
7114 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7116 gtk_editable_set_editable(
7117 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7118 prefs_common.type_any_header);
7120 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7121 G_CALLBACK(compose_grab_focus_cb), compose);
7123 /* Entry field with cleanup button */
7124 button = gtk_button_new();
7125 gtk_button_set_image(GTK_BUTTON(button),
7126 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7127 gtk_widget_show(button);
7128 CLAWS_SET_TIP(button,
7129 _("Delete entry contents"));
7130 entry = gtk_entry_new();
7131 gtk_widget_show(entry);
7132 CLAWS_SET_TIP(entry,
7133 _("Use <tab> to autocomplete from addressbook"));
7134 hbox = gtk_hbox_new (FALSE, 0);
7135 gtk_widget_show(hbox);
7136 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7137 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7138 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7139 compose->header_nextrow, compose->header_nextrow+1,
7140 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7142 g_signal_connect(G_OBJECT(entry), "key-press-event",
7143 G_CALLBACK(compose_headerentry_key_press_event_cb),
7145 g_signal_connect(G_OBJECT(entry), "changed",
7146 G_CALLBACK(compose_headerentry_changed_cb),
7148 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7149 G_CALLBACK(compose_grab_focus_cb), compose);
7151 g_signal_connect(G_OBJECT(button), "clicked",
7152 G_CALLBACK(compose_headerentry_button_clicked_cb),
7156 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7157 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7158 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7159 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7160 G_CALLBACK(compose_header_drag_received_cb),
7162 g_signal_connect(G_OBJECT(entry), "drag-drop",
7163 G_CALLBACK(compose_drag_drop),
7165 g_signal_connect(G_OBJECT(entry), "populate-popup",
7166 G_CALLBACK(compose_entry_popup_extend),
7169 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7171 headerentry->compose = compose;
7172 headerentry->combo = combo;
7173 headerentry->entry = entry;
7174 headerentry->button = button;
7175 headerentry->hbox = hbox;
7176 headerentry->headernum = compose->header_nextrow;
7177 headerentry->type = PREF_NONE;
7179 compose->header_nextrow++;
7180 compose->header_last = headerentry;
7181 compose->header_list =
7182 g_slist_append(compose->header_list,
7186 static void compose_add_header_entry(Compose *compose, const gchar *header,
7187 gchar *text, ComposePrefType pref_type)
7189 ComposeHeaderEntry *last_header = compose->header_last;
7190 gchar *tmp = g_strdup(text), *email;
7191 gboolean replyto_hdr;
7193 replyto_hdr = (!strcasecmp(header,
7194 prefs_common_translated_header_name("Reply-To:")) ||
7196 prefs_common_translated_header_name("Followup-To:")) ||
7198 prefs_common_translated_header_name("In-Reply-To:")));
7200 extract_address(tmp);
7201 email = g_utf8_strdown(tmp, -1);
7203 if (replyto_hdr == FALSE &&
7204 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7206 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7207 header, text, (gint) pref_type);
7213 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7214 gtk_entry_set_text(GTK_ENTRY(
7215 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7217 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7218 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7219 last_header->type = pref_type;
7221 if (replyto_hdr == FALSE)
7222 g_hash_table_insert(compose->email_hashtable, email,
7223 GUINT_TO_POINTER(1));
7230 static void compose_destroy_headerentry(Compose *compose,
7231 ComposeHeaderEntry *headerentry)
7233 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7236 extract_address(text);
7237 email = g_utf8_strdown(text, -1);
7238 g_hash_table_remove(compose->email_hashtable, email);
7242 gtk_widget_destroy(headerentry->combo);
7243 gtk_widget_destroy(headerentry->entry);
7244 gtk_widget_destroy(headerentry->button);
7245 gtk_widget_destroy(headerentry->hbox);
7246 g_free(headerentry);
7249 static void compose_remove_header_entries(Compose *compose)
7252 for (list = compose->header_list; list; list = list->next)
7253 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7255 compose->header_last = NULL;
7256 g_slist_free(compose->header_list);
7257 compose->header_list = NULL;
7258 compose->header_nextrow = 1;
7259 compose_create_header_entry(compose);
7262 static GtkWidget *compose_create_header(Compose *compose)
7264 GtkWidget *from_optmenu_hbox;
7265 GtkWidget *header_table_main;
7266 GtkWidget *header_scrolledwin;
7267 GtkWidget *header_table;
7269 /* parent with account selection and from header */
7270 header_table_main = gtk_table_new(2, 2, FALSE);
7271 gtk_widget_show(header_table_main);
7272 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7274 from_optmenu_hbox = compose_account_option_menu_create(compose);
7275 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7276 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7278 /* child with header labels and entries */
7279 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7280 gtk_widget_show(header_scrolledwin);
7281 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7283 header_table = gtk_table_new(2, 2, FALSE);
7284 gtk_widget_show(header_table);
7285 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7286 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7287 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7288 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7289 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7291 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7292 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7294 compose->header_table = header_table;
7295 compose->header_list = NULL;
7296 compose->header_nextrow = 0;
7298 compose_create_header_entry(compose);
7300 compose->table = NULL;
7302 return header_table_main;
7305 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7307 Compose *compose = (Compose *)data;
7308 GdkEventButton event;
7311 event.time = gtk_get_current_event_time();
7313 return attach_button_pressed(compose->attach_clist, &event, compose);
7316 static GtkWidget *compose_create_attach(Compose *compose)
7318 GtkWidget *attach_scrwin;
7319 GtkWidget *attach_clist;
7321 GtkListStore *store;
7322 GtkCellRenderer *renderer;
7323 GtkTreeViewColumn *column;
7324 GtkTreeSelection *selection;
7326 /* attachment list */
7327 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7328 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7329 GTK_POLICY_AUTOMATIC,
7330 GTK_POLICY_AUTOMATIC);
7331 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7333 store = gtk_list_store_new(N_ATTACH_COLS,
7339 G_TYPE_AUTO_POINTER,
7341 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7342 (GTK_TREE_MODEL(store)));
7343 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7344 g_object_unref(store);
7346 renderer = gtk_cell_renderer_text_new();
7347 column = gtk_tree_view_column_new_with_attributes
7348 (_("Mime type"), renderer, "text",
7349 COL_MIMETYPE, NULL);
7350 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7352 renderer = gtk_cell_renderer_text_new();
7353 column = gtk_tree_view_column_new_with_attributes
7354 (_("Size"), renderer, "text",
7356 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7358 renderer = gtk_cell_renderer_text_new();
7359 column = gtk_tree_view_column_new_with_attributes
7360 (_("Name"), renderer, "text",
7362 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7364 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7365 prefs_common.use_stripes_everywhere);
7366 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7367 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7369 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7370 G_CALLBACK(attach_selected), compose);
7371 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7372 G_CALLBACK(attach_button_pressed), compose);
7373 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7374 G_CALLBACK(popup_attach_button_pressed), compose);
7375 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7376 G_CALLBACK(attach_key_pressed), compose);
7379 gtk_drag_dest_set(attach_clist,
7380 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7381 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7382 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7383 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7384 G_CALLBACK(compose_attach_drag_received_cb),
7386 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7387 G_CALLBACK(compose_drag_drop),
7390 compose->attach_scrwin = attach_scrwin;
7391 compose->attach_clist = attach_clist;
7393 return attach_scrwin;
7396 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7397 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7399 static GtkWidget *compose_create_others(Compose *compose)
7402 GtkWidget *savemsg_checkbtn;
7403 GtkWidget *savemsg_combo;
7404 GtkWidget *savemsg_select;
7407 gchar *folderidentifier;
7409 /* Table for settings */
7410 table = gtk_table_new(3, 1, FALSE);
7411 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7412 gtk_widget_show(table);
7413 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7416 /* Save Message to folder */
7417 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7418 gtk_widget_show(savemsg_checkbtn);
7419 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7420 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7421 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7423 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7424 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7426 #if !GTK_CHECK_VERSION(2, 24, 0)
7427 savemsg_combo = gtk_combo_box_entry_new_text();
7429 savemsg_combo = gtk_combo_box_text_new_with_entry();
7431 compose->savemsg_checkbtn = savemsg_checkbtn;
7432 compose->savemsg_combo = savemsg_combo;
7433 gtk_widget_show(savemsg_combo);
7435 if (prefs_common.compose_save_to_history)
7436 #if !GTK_CHECK_VERSION(2, 24, 0)
7437 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7438 prefs_common.compose_save_to_history);
7440 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7441 prefs_common.compose_save_to_history);
7443 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7444 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7445 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7446 G_CALLBACK(compose_grab_focus_cb), compose);
7447 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7448 folderidentifier = folder_item_get_identifier(account_get_special_folder
7449 (compose->account, F_OUTBOX));
7450 compose_set_save_to(compose, folderidentifier);
7451 g_free(folderidentifier);
7454 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7455 gtk_widget_show(savemsg_select);
7456 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7457 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7458 G_CALLBACK(compose_savemsg_select_cb),
7464 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7466 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7467 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7470 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7475 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7476 _("Select folder to save message to"));
7479 path = folder_item_get_identifier(dest);
7481 compose_set_save_to(compose, path);
7485 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7486 GdkAtom clip, GtkTextIter *insert_place);
7489 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7493 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7495 if (event->button == 3) {
7497 GtkTextIter sel_start, sel_end;
7498 gboolean stuff_selected;
7500 /* move the cursor to allow GtkAspell to check the word
7501 * under the mouse */
7502 if (event->x && event->y) {
7503 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7504 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7506 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7509 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7510 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7513 stuff_selected = gtk_text_buffer_get_selection_bounds(
7515 &sel_start, &sel_end);
7517 gtk_text_buffer_place_cursor (buffer, &iter);
7518 /* reselect stuff */
7520 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7521 gtk_text_buffer_select_range(buffer,
7522 &sel_start, &sel_end);
7524 return FALSE; /* pass the event so that the right-click goes through */
7527 if (event->button == 2) {
7532 /* get the middle-click position to paste at the correct place */
7533 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7534 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7536 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7539 entry_paste_clipboard(compose, text,
7540 prefs_common.linewrap_pastes,
7541 GDK_SELECTION_PRIMARY, &iter);
7549 static void compose_spell_menu_changed(void *data)
7551 Compose *compose = (Compose *)data;
7553 GtkWidget *menuitem;
7554 GtkWidget *parent_item;
7555 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7558 if (compose->gtkaspell == NULL)
7561 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7562 "/Menu/Spelling/Options");
7564 /* setting the submenu removes /Spelling/Options from the factory
7565 * so we need to save it */
7567 if (parent_item == NULL) {
7568 parent_item = compose->aspell_options_menu;
7569 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7571 compose->aspell_options_menu = parent_item;
7573 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7575 spell_menu = g_slist_reverse(spell_menu);
7576 for (items = spell_menu;
7577 items; items = items->next) {
7578 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7579 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7580 gtk_widget_show(GTK_WIDGET(menuitem));
7582 g_slist_free(spell_menu);
7584 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7585 gtk_widget_show(parent_item);
7588 static void compose_dict_changed(void *data)
7590 Compose *compose = (Compose *) data;
7592 if(!compose->gtkaspell)
7594 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7597 gtkaspell_highlight_all(compose->gtkaspell);
7598 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7602 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7604 Compose *compose = (Compose *)data;
7605 GdkEventButton event;
7608 event.time = gtk_get_current_event_time();
7612 return text_clicked(compose->text, &event, compose);
7615 static gboolean compose_force_window_origin = TRUE;
7616 static Compose *compose_create(PrefsAccount *account,
7625 GtkWidget *handlebox;
7627 GtkWidget *notebook;
7629 GtkWidget *attach_hbox;
7630 GtkWidget *attach_lab1;
7631 GtkWidget *attach_lab2;
7636 GtkWidget *subject_hbox;
7637 GtkWidget *subject_frame;
7638 GtkWidget *subject_entry;
7642 GtkWidget *edit_vbox;
7643 GtkWidget *ruler_hbox;
7645 GtkWidget *scrolledwin;
7647 GtkTextBuffer *buffer;
7648 GtkClipboard *clipboard;
7650 UndoMain *undostruct;
7652 GtkWidget *popupmenu;
7653 GtkWidget *tmpl_menu;
7654 GtkActionGroup *action_group = NULL;
7657 GtkAspell * gtkaspell = NULL;
7660 static GdkGeometry geometry;
7662 cm_return_val_if_fail(account != NULL, NULL);
7664 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7665 &default_header_bgcolor);
7666 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7667 &default_header_color);
7669 debug_print("Creating compose window...\n");
7670 compose = g_new0(Compose, 1);
7672 compose->batch = batch;
7673 compose->account = account;
7674 compose->folder = folder;
7676 compose->mutex = cm_mutex_new();
7677 compose->set_cursor_pos = -1;
7679 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7681 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7682 gtk_widget_set_size_request(window, prefs_common.compose_width,
7683 prefs_common.compose_height);
7685 if (!geometry.max_width) {
7686 geometry.max_width = gdk_screen_width();
7687 geometry.max_height = gdk_screen_height();
7690 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7691 &geometry, GDK_HINT_MAX_SIZE);
7692 if (!geometry.min_width) {
7693 geometry.min_width = 600;
7694 geometry.min_height = 440;
7696 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7697 &geometry, GDK_HINT_MIN_SIZE);
7699 #ifndef GENERIC_UMPC
7700 if (compose_force_window_origin)
7701 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7702 prefs_common.compose_y);
7704 g_signal_connect(G_OBJECT(window), "delete_event",
7705 G_CALLBACK(compose_delete_cb), compose);
7706 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7707 gtk_widget_realize(window);
7709 gtkut_widget_set_composer_icon(window);
7711 vbox = gtk_vbox_new(FALSE, 0);
7712 gtk_container_add(GTK_CONTAINER(window), vbox);
7714 compose->ui_manager = gtk_ui_manager_new();
7715 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7716 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7717 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7718 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7719 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7720 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7721 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7722 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7723 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7724 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7762 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7763 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7766 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7768 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7781 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7843 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)
7844 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)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7850 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)
7851 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)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7856 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)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7860 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)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7866 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)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7873 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)
7874 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)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7887 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)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7905 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7906 gtk_widget_show_all(menubar);
7908 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7909 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7911 if (prefs_common.toolbar_detachable) {
7912 handlebox = gtk_handle_box_new();
7914 handlebox = gtk_hbox_new(FALSE, 0);
7916 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7918 gtk_widget_realize(handlebox);
7919 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7922 vbox2 = gtk_vbox_new(FALSE, 2);
7923 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7924 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7927 notebook = gtk_notebook_new();
7928 gtk_widget_show(notebook);
7930 /* header labels and entries */
7931 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7932 compose_create_header(compose),
7933 gtk_label_new_with_mnemonic(_("Hea_der")));
7934 /* attachment list */
7935 attach_hbox = gtk_hbox_new(FALSE, 0);
7936 gtk_widget_show(attach_hbox);
7938 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7939 gtk_widget_show(attach_lab1);
7940 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7942 attach_lab2 = gtk_label_new("");
7943 gtk_widget_show(attach_lab2);
7944 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7946 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7947 compose_create_attach(compose),
7950 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7951 compose_create_others(compose),
7952 gtk_label_new_with_mnemonic(_("Othe_rs")));
7955 subject_hbox = gtk_hbox_new(FALSE, 0);
7956 gtk_widget_show(subject_hbox);
7958 subject_frame = gtk_frame_new(NULL);
7959 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7960 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7961 gtk_widget_show(subject_frame);
7963 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7964 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7965 gtk_widget_show(subject);
7967 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7968 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7969 gtk_widget_show(label);
7972 subject_entry = claws_spell_entry_new();
7974 subject_entry = gtk_entry_new();
7976 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7977 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7978 G_CALLBACK(compose_grab_focus_cb), compose);
7979 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7980 gtk_widget_show(subject_entry);
7981 compose->subject_entry = subject_entry;
7982 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7984 edit_vbox = gtk_vbox_new(FALSE, 0);
7986 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7989 ruler_hbox = gtk_hbox_new(FALSE, 0);
7990 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7992 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7993 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7994 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7998 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7999 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8000 GTK_POLICY_AUTOMATIC,
8001 GTK_POLICY_AUTOMATIC);
8002 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8004 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8006 text = gtk_text_view_new();
8007 if (prefs_common.show_compose_margin) {
8008 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8009 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8011 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8012 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8013 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8014 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8015 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8017 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8018 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8019 G_CALLBACK(compose_edit_size_alloc),
8021 g_signal_connect(G_OBJECT(buffer), "changed",
8022 G_CALLBACK(compose_changed_cb), compose);
8023 g_signal_connect(G_OBJECT(text), "grab_focus",
8024 G_CALLBACK(compose_grab_focus_cb), compose);
8025 g_signal_connect(G_OBJECT(buffer), "insert_text",
8026 G_CALLBACK(text_inserted), compose);
8027 g_signal_connect(G_OBJECT(text), "button_press_event",
8028 G_CALLBACK(text_clicked), compose);
8029 g_signal_connect(G_OBJECT(text), "popup-menu",
8030 G_CALLBACK(compose_popup_menu), compose);
8031 g_signal_connect(G_OBJECT(subject_entry), "changed",
8032 G_CALLBACK(compose_changed_cb), compose);
8033 g_signal_connect(G_OBJECT(subject_entry), "activate",
8034 G_CALLBACK(compose_subject_entry_activated), compose);
8037 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8038 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8039 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8040 g_signal_connect(G_OBJECT(text), "drag_data_received",
8041 G_CALLBACK(compose_insert_drag_received_cb),
8043 g_signal_connect(G_OBJECT(text), "drag-drop",
8044 G_CALLBACK(compose_drag_drop),
8046 g_signal_connect(G_OBJECT(text), "key-press-event",
8047 G_CALLBACK(completion_set_focus_to_subject),
8049 gtk_widget_show_all(vbox);
8051 /* pane between attach clist and text */
8052 paned = gtk_vpaned_new();
8053 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8054 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8055 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8056 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8057 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8058 G_CALLBACK(compose_notebook_size_alloc), paned);
8060 gtk_widget_show_all(paned);
8063 if (prefs_common.textfont) {
8064 PangoFontDescription *font_desc;
8066 font_desc = pango_font_description_from_string
8067 (prefs_common.textfont);
8069 gtk_widget_modify_font(text, font_desc);
8070 pango_font_description_free(font_desc);
8074 gtk_action_group_add_actions(action_group, compose_popup_entries,
8075 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8076 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8077 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8078 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8079 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8080 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8081 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8083 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8085 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8086 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8087 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8089 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8091 undostruct = undo_init(text);
8092 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8095 address_completion_start(window);
8097 compose->window = window;
8098 compose->vbox = vbox;
8099 compose->menubar = menubar;
8100 compose->handlebox = handlebox;
8102 compose->vbox2 = vbox2;
8104 compose->paned = paned;
8106 compose->attach_label = attach_lab2;
8108 compose->notebook = notebook;
8109 compose->edit_vbox = edit_vbox;
8110 compose->ruler_hbox = ruler_hbox;
8111 compose->ruler = ruler;
8112 compose->scrolledwin = scrolledwin;
8113 compose->text = text;
8115 compose->focused_editable = NULL;
8117 compose->popupmenu = popupmenu;
8119 compose->tmpl_menu = tmpl_menu;
8121 compose->mode = mode;
8122 compose->rmode = mode;
8124 compose->targetinfo = NULL;
8125 compose->replyinfo = NULL;
8126 compose->fwdinfo = NULL;
8128 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8129 g_str_equal, (GDestroyNotify) g_free, NULL);
8131 compose->replyto = NULL;
8133 compose->bcc = NULL;
8134 compose->followup_to = NULL;
8136 compose->ml_post = NULL;
8138 compose->inreplyto = NULL;
8139 compose->references = NULL;
8140 compose->msgid = NULL;
8141 compose->boundary = NULL;
8143 compose->autowrap = prefs_common.autowrap;
8144 compose->autoindent = prefs_common.auto_indent;
8145 compose->use_signing = FALSE;
8146 compose->use_encryption = FALSE;
8147 compose->privacy_system = NULL;
8148 compose->encdata = NULL;
8150 compose->modified = FALSE;
8152 compose->return_receipt = FALSE;
8154 compose->to_list = NULL;
8155 compose->newsgroup_list = NULL;
8157 compose->undostruct = undostruct;
8159 compose->sig_str = NULL;
8161 compose->exteditor_file = NULL;
8162 compose->exteditor_pid = -1;
8163 compose->exteditor_tag = -1;
8164 compose->exteditor_socket = NULL;
8165 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8167 compose->folder_update_callback_id =
8168 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8169 compose_update_folder_hook,
8170 (gpointer) compose);
8173 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8174 if (mode != COMPOSE_REDIRECT) {
8175 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8176 strcmp(prefs_common.dictionary, "")) {
8177 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8178 prefs_common.alt_dictionary,
8179 conv_get_locale_charset_str(),
8180 prefs_common.misspelled_col,
8181 prefs_common.check_while_typing,
8182 prefs_common.recheck_when_changing_dict,
8183 prefs_common.use_alternate,
8184 prefs_common.use_both_dicts,
8185 GTK_TEXT_VIEW(text),
8186 GTK_WINDOW(compose->window),
8187 compose_dict_changed,
8188 compose_spell_menu_changed,
8191 alertpanel_error(_("Spell checker could not "
8193 gtkaspell_checkers_strerror());
8194 gtkaspell_checkers_reset_error();
8196 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8200 compose->gtkaspell = gtkaspell;
8201 compose_spell_menu_changed(compose);
8202 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8205 compose_select_account(compose, account, TRUE);
8207 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8208 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8210 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8211 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8213 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8214 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8216 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8217 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8219 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8220 if (account->protocol != A_NNTP)
8221 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8222 prefs_common_translated_header_name("To:"));
8224 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8225 prefs_common_translated_header_name("Newsgroups:"));
8227 #ifndef USE_ALT_ADDRBOOK
8228 addressbook_set_target_compose(compose);
8230 if (mode != COMPOSE_REDIRECT)
8231 compose_set_template_menu(compose);
8233 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8236 compose_list = g_list_append(compose_list, compose);
8238 if (!prefs_common.show_ruler)
8239 gtk_widget_hide(ruler_hbox);
8241 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8244 compose->priority = PRIORITY_NORMAL;
8245 compose_update_priority_menu_item(compose);
8247 compose_set_out_encoding(compose);
8250 compose_update_actions_menu(compose);
8252 /* Privacy Systems menu */
8253 compose_update_privacy_systems_menu(compose);
8255 activate_privacy_system(compose, account, TRUE);
8256 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8258 gtk_widget_realize(window);
8260 gtk_widget_show(window);
8266 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8271 GtkWidget *optmenubox;
8272 GtkWidget *fromlabel;
8275 GtkWidget *from_name = NULL;
8277 gint num = 0, def_menu = 0;
8279 accounts = account_get_list();
8280 cm_return_val_if_fail(accounts != NULL, NULL);
8282 optmenubox = gtk_event_box_new();
8283 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8284 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8286 hbox = gtk_hbox_new(FALSE, 4);
8287 from_name = gtk_entry_new();
8289 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8290 G_CALLBACK(compose_grab_focus_cb), compose);
8291 g_signal_connect_after(G_OBJECT(from_name), "activate",
8292 G_CALLBACK(from_name_activate_cb), optmenu);
8294 for (; accounts != NULL; accounts = accounts->next, num++) {
8295 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8296 gchar *name, *from = NULL;
8298 if (ac == compose->account) def_menu = num;
8300 name = g_markup_printf_escaped("<i>%s</i>",
8303 if (ac == compose->account) {
8304 if (ac->name && *ac->name) {
8306 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8307 from = g_strdup_printf("%s <%s>",
8309 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8311 from = g_strdup_printf("%s",
8313 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8315 if (cur_account != compose->account) {
8316 gtk_widget_modify_base(
8317 GTK_WIDGET(from_name),
8318 GTK_STATE_NORMAL, &default_header_bgcolor);
8319 gtk_widget_modify_text(
8320 GTK_WIDGET(from_name),
8321 GTK_STATE_NORMAL, &default_header_color);
8324 COMBOBOX_ADD(menu, name, ac->account_id);
8329 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8331 g_signal_connect(G_OBJECT(optmenu), "changed",
8332 G_CALLBACK(account_activated),
8334 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8335 G_CALLBACK(compose_entry_popup_extend),
8338 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8339 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8341 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8342 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8343 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8345 /* Putting only the GtkEntry into focus chain of parent hbox causes
8346 * the account selector combobox next to it to be unreachable when
8347 * navigating widgets in GtkTable with up/down arrow keys.
8348 * Note: gtk_widget_set_can_focus() was not enough. */
8350 l = g_list_prepend(l, from_name);
8351 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8354 CLAWS_SET_TIP(optmenubox,
8355 _("Account to use for this email"));
8356 CLAWS_SET_TIP(from_name,
8357 _("Sender address to be used"));
8359 compose->account_combo = optmenu;
8360 compose->from_name = from_name;
8365 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8367 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8368 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8369 Compose *compose = (Compose *) data;
8371 compose->priority = value;
8375 static void compose_reply_change_mode(Compose *compose,
8378 gboolean was_modified = compose->modified;
8380 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8382 cm_return_if_fail(compose->replyinfo != NULL);
8384 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8386 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8388 if (action == COMPOSE_REPLY_TO_ALL)
8390 if (action == COMPOSE_REPLY_TO_SENDER)
8392 if (action == COMPOSE_REPLY_TO_LIST)
8395 compose_remove_header_entries(compose);
8396 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8397 if (compose->account->set_autocc && compose->account->auto_cc)
8398 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8400 if (compose->account->set_autobcc && compose->account->auto_bcc)
8401 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8403 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8404 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8405 compose_show_first_last_header(compose, TRUE);
8406 compose->modified = was_modified;
8407 compose_set_title(compose);
8410 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8412 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8413 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8414 Compose *compose = (Compose *) data;
8417 compose_reply_change_mode(compose, value);
8420 static void compose_update_priority_menu_item(Compose * compose)
8422 GtkWidget *menuitem = NULL;
8423 switch (compose->priority) {
8424 case PRIORITY_HIGHEST:
8425 menuitem = gtk_ui_manager_get_widget
8426 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8429 menuitem = gtk_ui_manager_get_widget
8430 (compose->ui_manager, "/Menu/Options/Priority/High");
8432 case PRIORITY_NORMAL:
8433 menuitem = gtk_ui_manager_get_widget
8434 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8437 menuitem = gtk_ui_manager_get_widget
8438 (compose->ui_manager, "/Menu/Options/Priority/Low");
8440 case PRIORITY_LOWEST:
8441 menuitem = gtk_ui_manager_get_widget
8442 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8445 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8448 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8450 Compose *compose = (Compose *) data;
8452 gboolean can_sign = FALSE, can_encrypt = FALSE;
8454 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8456 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8459 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8460 g_free(compose->privacy_system);
8461 compose->privacy_system = NULL;
8462 g_free(compose->encdata);
8463 compose->encdata = NULL;
8464 if (systemid != NULL) {
8465 compose->privacy_system = g_strdup(systemid);
8467 can_sign = privacy_system_can_sign(systemid);
8468 can_encrypt = privacy_system_can_encrypt(systemid);
8471 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8474 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8475 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, can_sign);
8476 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, can_encrypt);
8477 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), can_sign ? compose->use_signing : FALSE);
8478 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), can_encrypt ? compose->use_encryption : FALSE);
8481 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8483 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8484 GtkWidget *menuitem = NULL;
8485 GList *children, *amenu;
8486 gboolean can_sign = FALSE, can_encrypt = FALSE;
8487 gboolean found = FALSE;
8489 if (compose->privacy_system != NULL) {
8491 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8492 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8493 cm_return_if_fail(menuitem != NULL);
8495 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8498 while (amenu != NULL) {
8499 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8500 if (systemid != NULL) {
8501 if (strcmp(systemid, compose->privacy_system) == 0 &&
8502 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8503 menuitem = GTK_WIDGET(amenu->data);
8505 can_sign = privacy_system_can_sign(systemid);
8506 can_encrypt = privacy_system_can_encrypt(systemid);
8510 } else if (strlen(compose->privacy_system) == 0 &&
8511 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8512 menuitem = GTK_WIDGET(amenu->data);
8515 can_encrypt = FALSE;
8520 amenu = amenu->next;
8522 g_list_free(children);
8523 if (menuitem != NULL)
8524 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8526 if (warn && !found && strlen(compose->privacy_system)) {
8527 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8528 "will not be able to sign or encrypt this message."),
8529 compose->privacy_system);
8533 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8534 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8537 static void compose_set_out_encoding(Compose *compose)
8539 CharSet out_encoding;
8540 const gchar *branch = NULL;
8541 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8543 switch(out_encoding) {
8544 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8545 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8546 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8547 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8548 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8549 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8550 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8551 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8552 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8553 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8554 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8555 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8556 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8557 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8558 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8559 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8560 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8561 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8562 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8563 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8564 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8565 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8566 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8567 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8568 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8569 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8570 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8571 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8572 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8573 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8574 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8575 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8576 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8577 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8579 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8582 static void compose_set_template_menu(Compose *compose)
8584 GSList *tmpl_list, *cur;
8588 tmpl_list = template_get_config();
8590 menu = gtk_menu_new();
8592 gtk_menu_set_accel_group (GTK_MENU (menu),
8593 gtk_ui_manager_get_accel_group(compose->ui_manager));
8594 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8595 Template *tmpl = (Template *)cur->data;
8596 gchar *accel_path = NULL;
8597 item = gtk_menu_item_new_with_label(tmpl->name);
8598 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8599 g_signal_connect(G_OBJECT(item), "activate",
8600 G_CALLBACK(compose_template_activate_cb),
8602 g_object_set_data(G_OBJECT(item), "template", tmpl);
8603 gtk_widget_show(item);
8604 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8605 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8609 gtk_widget_show(menu);
8610 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8613 void compose_update_actions_menu(Compose *compose)
8615 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8618 static void compose_update_privacy_systems_menu(Compose *compose)
8620 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8621 GSList *systems, *cur;
8623 GtkWidget *system_none;
8625 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8626 GtkWidget *privacy_menu = gtk_menu_new();
8628 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8629 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8631 g_signal_connect(G_OBJECT(system_none), "activate",
8632 G_CALLBACK(compose_set_privacy_system_cb), compose);
8634 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8635 gtk_widget_show(system_none);
8637 systems = privacy_get_system_ids();
8638 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8639 gchar *systemid = cur->data;
8641 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8642 widget = gtk_radio_menu_item_new_with_label(group,
8643 privacy_system_get_name(systemid));
8644 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8645 g_strdup(systemid), g_free);
8646 g_signal_connect(G_OBJECT(widget), "activate",
8647 G_CALLBACK(compose_set_privacy_system_cb), compose);
8649 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8650 gtk_widget_show(widget);
8653 g_slist_free(systems);
8654 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8655 gtk_widget_show_all(privacy_menu);
8656 gtk_widget_show_all(privacy_menuitem);
8659 void compose_reflect_prefs_all(void)
8664 for (cur = compose_list; cur != NULL; cur = cur->next) {
8665 compose = (Compose *)cur->data;
8666 compose_set_template_menu(compose);
8670 void compose_reflect_prefs_pixmap_theme(void)
8675 for (cur = compose_list; cur != NULL; cur = cur->next) {
8676 compose = (Compose *)cur->data;
8677 toolbar_update(TOOLBAR_COMPOSE, compose);
8681 static const gchar *compose_quote_char_from_context(Compose *compose)
8683 const gchar *qmark = NULL;
8685 cm_return_val_if_fail(compose != NULL, NULL);
8687 switch (compose->mode) {
8688 /* use forward-specific quote char */
8689 case COMPOSE_FORWARD:
8690 case COMPOSE_FORWARD_AS_ATTACH:
8691 case COMPOSE_FORWARD_INLINE:
8692 if (compose->folder && compose->folder->prefs &&
8693 compose->folder->prefs->forward_with_format)
8694 qmark = compose->folder->prefs->forward_quotemark;
8695 else if (compose->account->forward_with_format)
8696 qmark = compose->account->forward_quotemark;
8698 qmark = prefs_common.fw_quotemark;
8701 /* use reply-specific quote char in all other modes */
8703 if (compose->folder && compose->folder->prefs &&
8704 compose->folder->prefs->reply_with_format)
8705 qmark = compose->folder->prefs->reply_quotemark;
8706 else if (compose->account->reply_with_format)
8707 qmark = compose->account->reply_quotemark;
8709 qmark = prefs_common.quotemark;
8713 if (qmark == NULL || *qmark == '\0')
8719 static void compose_template_apply(Compose *compose, Template *tmpl,
8723 GtkTextBuffer *buffer;
8727 gchar *parsed_str = NULL;
8728 gint cursor_pos = 0;
8729 const gchar *err_msg = _("The body of the template has an error at line %d.");
8732 /* process the body */
8734 text = GTK_TEXT_VIEW(compose->text);
8735 buffer = gtk_text_view_get_buffer(text);
8738 qmark = compose_quote_char_from_context(compose);
8740 if (compose->replyinfo != NULL) {
8743 gtk_text_buffer_set_text(buffer, "", -1);
8744 mark = gtk_text_buffer_get_insert(buffer);
8745 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8747 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8748 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8750 } else if (compose->fwdinfo != NULL) {
8753 gtk_text_buffer_set_text(buffer, "", -1);
8754 mark = gtk_text_buffer_get_insert(buffer);
8755 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8757 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8758 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8761 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8763 GtkTextIter start, end;
8766 gtk_text_buffer_get_start_iter(buffer, &start);
8767 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8768 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8770 /* clear the buffer now */
8772 gtk_text_buffer_set_text(buffer, "", -1);
8774 parsed_str = compose_quote_fmt(compose, dummyinfo,
8775 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8776 procmsg_msginfo_free( &dummyinfo );
8782 gtk_text_buffer_set_text(buffer, "", -1);
8783 mark = gtk_text_buffer_get_insert(buffer);
8784 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8787 if (replace && parsed_str && compose->account->auto_sig)
8788 compose_insert_sig(compose, FALSE);
8790 if (replace && parsed_str) {
8791 gtk_text_buffer_get_start_iter(buffer, &iter);
8792 gtk_text_buffer_place_cursor(buffer, &iter);
8796 cursor_pos = quote_fmt_get_cursor_pos();
8797 compose->set_cursor_pos = cursor_pos;
8798 if (cursor_pos == -1)
8800 gtk_text_buffer_get_start_iter(buffer, &iter);
8801 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8802 gtk_text_buffer_place_cursor(buffer, &iter);
8805 /* process the other fields */
8807 compose_template_apply_fields(compose, tmpl);
8808 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8809 quote_fmt_reset_vartable();
8810 compose_changed_cb(NULL, compose);
8813 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8814 gtkaspell_highlight_all(compose->gtkaspell);
8818 static void compose_template_apply_fields_error(const gchar *header)
8823 tr = g_strdup(C_("'%s' stands for a header name",
8824 "Template '%s' format error."));
8825 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8826 alertpanel_error("%s", text);
8832 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8834 MsgInfo* dummyinfo = NULL;
8835 MsgInfo *msginfo = NULL;
8838 if (compose->replyinfo != NULL)
8839 msginfo = compose->replyinfo;
8840 else if (compose->fwdinfo != NULL)
8841 msginfo = compose->fwdinfo;
8843 dummyinfo = compose_msginfo_new_from_compose(compose);
8844 msginfo = dummyinfo;
8847 if (tmpl->from && *tmpl->from != '\0') {
8849 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8850 compose->gtkaspell);
8852 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8854 quote_fmt_scan_string(tmpl->from);
8857 buf = quote_fmt_get_buffer();
8859 compose_template_apply_fields_error("From");
8861 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8865 if (tmpl->to && *tmpl->to != '\0') {
8867 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8868 compose->gtkaspell);
8870 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8872 quote_fmt_scan_string(tmpl->to);
8875 buf = quote_fmt_get_buffer();
8877 compose_template_apply_fields_error("To");
8879 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8883 if (tmpl->cc && *tmpl->cc != '\0') {
8885 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8886 compose->gtkaspell);
8888 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8890 quote_fmt_scan_string(tmpl->cc);
8893 buf = quote_fmt_get_buffer();
8895 compose_template_apply_fields_error("Cc");
8897 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8901 if (tmpl->bcc && *tmpl->bcc != '\0') {
8903 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8904 compose->gtkaspell);
8906 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8908 quote_fmt_scan_string(tmpl->bcc);
8911 buf = quote_fmt_get_buffer();
8913 compose_template_apply_fields_error("Bcc");
8915 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8919 if (tmpl->replyto && *tmpl->replyto != '\0') {
8921 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8922 compose->gtkaspell);
8924 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8926 quote_fmt_scan_string(tmpl->replyto);
8929 buf = quote_fmt_get_buffer();
8931 compose_template_apply_fields_error("Reply-To");
8933 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8937 /* process the subject */
8938 if (tmpl->subject && *tmpl->subject != '\0') {
8940 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8941 compose->gtkaspell);
8943 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8945 quote_fmt_scan_string(tmpl->subject);
8948 buf = quote_fmt_get_buffer();
8950 compose_template_apply_fields_error("Subject");
8952 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8956 procmsg_msginfo_free( &dummyinfo );
8959 static void compose_destroy(Compose *compose)
8961 GtkAllocation allocation;
8962 GtkTextBuffer *buffer;
8963 GtkClipboard *clipboard;
8965 compose_list = g_list_remove(compose_list, compose);
8967 if (compose->updating) {
8968 debug_print("danger, not destroying anything now\n");
8969 compose->deferred_destroy = TRUE;
8973 /* NOTE: address_completion_end() does nothing with the window
8974 * however this may change. */
8975 address_completion_end(compose->window);
8977 slist_free_strings_full(compose->to_list);
8978 slist_free_strings_full(compose->newsgroup_list);
8979 slist_free_strings_full(compose->header_list);
8981 slist_free_strings_full(extra_headers);
8982 extra_headers = NULL;
8984 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8986 g_hash_table_destroy(compose->email_hashtable);
8988 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8989 compose->folder_update_callback_id);
8991 procmsg_msginfo_free(&(compose->targetinfo));
8992 procmsg_msginfo_free(&(compose->replyinfo));
8993 procmsg_msginfo_free(&(compose->fwdinfo));
8995 g_free(compose->replyto);
8996 g_free(compose->cc);
8997 g_free(compose->bcc);
8998 g_free(compose->newsgroups);
8999 g_free(compose->followup_to);
9001 g_free(compose->ml_post);
9003 g_free(compose->inreplyto);
9004 g_free(compose->references);
9005 g_free(compose->msgid);
9006 g_free(compose->boundary);
9008 g_free(compose->redirect_filename);
9009 if (compose->undostruct)
9010 undo_destroy(compose->undostruct);
9012 g_free(compose->sig_str);
9014 g_free(compose->exteditor_file);
9016 g_free(compose->orig_charset);
9018 g_free(compose->privacy_system);
9019 g_free(compose->encdata);
9021 #ifndef USE_ALT_ADDRBOOK
9022 if (addressbook_get_target_compose() == compose)
9023 addressbook_set_target_compose(NULL);
9026 if (compose->gtkaspell) {
9027 gtkaspell_delete(compose->gtkaspell);
9028 compose->gtkaspell = NULL;
9032 if (!compose->batch) {
9033 gtk_widget_get_allocation(compose->window, &allocation);
9034 prefs_common.compose_width = allocation.width;
9035 prefs_common.compose_height = allocation.height;
9038 if (!gtk_widget_get_parent(compose->paned))
9039 gtk_widget_destroy(compose->paned);
9040 gtk_widget_destroy(compose->popupmenu);
9042 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9043 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9044 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9046 gtk_widget_destroy(compose->window);
9047 toolbar_destroy(compose->toolbar);
9048 g_free(compose->toolbar);
9049 cm_mutex_free(compose->mutex);
9053 static void compose_attach_info_free(AttachInfo *ainfo)
9055 g_free(ainfo->file);
9056 g_free(ainfo->content_type);
9057 g_free(ainfo->name);
9058 g_free(ainfo->charset);
9062 static void compose_attach_update_label(Compose *compose)
9067 GtkTreeModel *model;
9071 if (compose == NULL)
9074 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9075 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9076 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9080 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9081 total_size = ainfo->size;
9082 while(gtk_tree_model_iter_next(model, &iter)) {
9083 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9084 total_size += ainfo->size;
9087 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9088 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9092 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9094 Compose *compose = (Compose *)data;
9095 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9096 GtkTreeSelection *selection;
9098 GtkTreeModel *model;
9100 selection = gtk_tree_view_get_selection(tree_view);
9101 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9106 for (cur = sel; cur != NULL; cur = cur->next) {
9107 GtkTreePath *path = cur->data;
9108 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9111 gtk_tree_path_free(path);
9114 for (cur = sel; cur != NULL; cur = cur->next) {
9115 GtkTreeRowReference *ref = cur->data;
9116 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9119 if (gtk_tree_model_get_iter(model, &iter, path))
9120 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9122 gtk_tree_path_free(path);
9123 gtk_tree_row_reference_free(ref);
9127 compose_attach_update_label(compose);
9130 static struct _AttachProperty
9133 GtkWidget *mimetype_entry;
9134 GtkWidget *encoding_optmenu;
9135 GtkWidget *path_entry;
9136 GtkWidget *filename_entry;
9138 GtkWidget *cancel_btn;
9141 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9143 gtk_tree_path_free((GtkTreePath *)ptr);
9146 static void compose_attach_property(GtkAction *action, gpointer data)
9148 Compose *compose = (Compose *)data;
9149 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9151 GtkComboBox *optmenu;
9152 GtkTreeSelection *selection;
9154 GtkTreeModel *model;
9157 static gboolean cancelled;
9159 /* only if one selected */
9160 selection = gtk_tree_view_get_selection(tree_view);
9161 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9164 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9168 path = (GtkTreePath *) sel->data;
9169 gtk_tree_model_get_iter(model, &iter, path);
9170 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9173 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9179 if (!attach_prop.window)
9180 compose_attach_property_create(&cancelled);
9181 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9182 gtk_widget_grab_focus(attach_prop.ok_btn);
9183 gtk_widget_show(attach_prop.window);
9184 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9185 GTK_WINDOW(compose->window));
9187 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9188 if (ainfo->encoding == ENC_UNKNOWN)
9189 combobox_select_by_data(optmenu, ENC_BASE64);
9191 combobox_select_by_data(optmenu, ainfo->encoding);
9193 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9194 ainfo->content_type ? ainfo->content_type : "");
9195 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9196 ainfo->file ? ainfo->file : "");
9197 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9198 ainfo->name ? ainfo->name : "");
9201 const gchar *entry_text;
9203 gchar *cnttype = NULL;
9210 gtk_widget_hide(attach_prop.window);
9211 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9216 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9217 if (*entry_text != '\0') {
9220 text = g_strstrip(g_strdup(entry_text));
9221 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9222 cnttype = g_strdup(text);
9225 alertpanel_error(_("Invalid MIME type."));
9231 ainfo->encoding = combobox_get_active_data(optmenu);
9233 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9234 if (*entry_text != '\0') {
9235 if (is_file_exist(entry_text) &&
9236 (size = get_file_size(entry_text)) > 0)
9237 file = g_strdup(entry_text);
9240 (_("File doesn't exist or is empty."));
9246 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9247 if (*entry_text != '\0') {
9248 g_free(ainfo->name);
9249 ainfo->name = g_strdup(entry_text);
9253 g_free(ainfo->content_type);
9254 ainfo->content_type = cnttype;
9257 g_free(ainfo->file);
9261 ainfo->size = (goffset)size;
9263 /* update tree store */
9264 text = to_human_readable(ainfo->size);
9265 gtk_tree_model_get_iter(model, &iter, path);
9266 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9267 COL_MIMETYPE, ainfo->content_type,
9269 COL_NAME, ainfo->name,
9270 COL_CHARSET, ainfo->charset,
9276 gtk_tree_path_free(path);
9279 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9281 label = gtk_label_new(str); \
9282 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9283 GTK_FILL, 0, 0, 0); \
9284 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9286 entry = gtk_entry_new(); \
9287 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9288 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9291 static void compose_attach_property_create(gboolean *cancelled)
9297 GtkWidget *mimetype_entry;
9300 GtkListStore *optmenu_menu;
9301 GtkWidget *path_entry;
9302 GtkWidget *filename_entry;
9305 GtkWidget *cancel_btn;
9306 GList *mime_type_list, *strlist;
9309 debug_print("Creating attach_property window...\n");
9311 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9312 gtk_widget_set_size_request(window, 480, -1);
9313 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9314 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9315 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9316 g_signal_connect(G_OBJECT(window), "delete_event",
9317 G_CALLBACK(attach_property_delete_event),
9319 g_signal_connect(G_OBJECT(window), "key_press_event",
9320 G_CALLBACK(attach_property_key_pressed),
9323 vbox = gtk_vbox_new(FALSE, 8);
9324 gtk_container_add(GTK_CONTAINER(window), vbox);
9326 table = gtk_table_new(4, 2, FALSE);
9327 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9328 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9329 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9331 label = gtk_label_new(_("MIME type"));
9332 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9334 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9335 #if !GTK_CHECK_VERSION(2, 24, 0)
9336 mimetype_entry = gtk_combo_box_entry_new_text();
9338 mimetype_entry = gtk_combo_box_text_new_with_entry();
9340 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9341 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9343 /* stuff with list */
9344 mime_type_list = procmime_get_mime_type_list();
9346 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9347 MimeType *type = (MimeType *) mime_type_list->data;
9350 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9352 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9355 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9356 (GCompareFunc)strcmp2);
9359 for (mime_type_list = strlist; mime_type_list != NULL;
9360 mime_type_list = mime_type_list->next) {
9361 #if !GTK_CHECK_VERSION(2, 24, 0)
9362 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9364 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9366 g_free(mime_type_list->data);
9368 g_list_free(strlist);
9369 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9370 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9372 label = gtk_label_new(_("Encoding"));
9373 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9375 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9377 hbox = gtk_hbox_new(FALSE, 0);
9378 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9379 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9381 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9382 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9384 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9385 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9386 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9387 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9388 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9390 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9392 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9393 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9395 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9396 &ok_btn, GTK_STOCK_OK,
9398 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9399 gtk_widget_grab_default(ok_btn);
9401 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9402 G_CALLBACK(attach_property_ok),
9404 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9405 G_CALLBACK(attach_property_cancel),
9408 gtk_widget_show_all(vbox);
9410 attach_prop.window = window;
9411 attach_prop.mimetype_entry = mimetype_entry;
9412 attach_prop.encoding_optmenu = optmenu;
9413 attach_prop.path_entry = path_entry;
9414 attach_prop.filename_entry = filename_entry;
9415 attach_prop.ok_btn = ok_btn;
9416 attach_prop.cancel_btn = cancel_btn;
9419 #undef SET_LABEL_AND_ENTRY
9421 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9427 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9433 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9434 gboolean *cancelled)
9442 static gboolean attach_property_key_pressed(GtkWidget *widget,
9444 gboolean *cancelled)
9446 if (event && event->keyval == GDK_KEY_Escape) {
9450 if (event && event->keyval == GDK_KEY_Return) {
9458 static void compose_exec_ext_editor(Compose *compose)
9463 GdkNativeWindow socket_wid = 0;
9467 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9468 G_DIR_SEPARATOR, compose);
9470 if (compose_get_ext_editor_uses_socket()) {
9471 /* Only allow one socket */
9472 if (compose->exteditor_socket != NULL) {
9473 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9474 /* Move the focus off of the socket */
9475 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9480 /* Create the receiving GtkSocket */
9481 socket = gtk_socket_new ();
9482 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9483 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9485 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9486 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9487 /* Realize the socket so that we can use its ID */
9488 gtk_widget_realize(socket);
9489 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9490 compose->exteditor_socket = socket;
9493 if (pipe(pipe_fds) < 0) {
9499 if ((pid = fork()) < 0) {
9506 /* close the write side of the pipe */
9509 compose->exteditor_file = g_strdup(tmp);
9510 compose->exteditor_pid = pid;
9512 compose_set_ext_editor_sensitive(compose, FALSE);
9515 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9517 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9519 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9523 } else { /* process-monitoring process */
9529 /* close the read side of the pipe */
9532 if (compose_write_body_to_file(compose, tmp) < 0) {
9533 fd_write_all(pipe_fds[1], "2\n", 2);
9537 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9539 fd_write_all(pipe_fds[1], "1\n", 2);
9543 /* wait until editor is terminated */
9544 waitpid(pid_ed, NULL, 0);
9546 fd_write_all(pipe_fds[1], "0\n", 2);
9553 #endif /* G_OS_UNIX */
9556 static gboolean compose_can_autosave(Compose *compose)
9558 if (compose->privacy_system && compose->use_encryption)
9559 return prefs_common.autosave && prefs_common.autosave_encrypted;
9561 return prefs_common.autosave;
9565 static gboolean compose_get_ext_editor_cmd_valid()
9567 gboolean has_s = FALSE;
9568 gboolean has_w = FALSE;
9569 const gchar *p = prefs_common_get_ext_editor_cmd();
9572 while ((p = strchr(p, '%'))) {
9578 } else if (*p == 'w') {
9589 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9596 cm_return_val_if_fail(file != NULL, -1);
9598 if ((pid = fork()) < 0) {
9603 if (pid != 0) return pid;
9605 /* grandchild process */
9607 if (setpgid(0, getppid()))
9610 if (compose_get_ext_editor_cmd_valid()) {
9611 if (compose_get_ext_editor_uses_socket()) {
9612 p = g_strdup(prefs_common_get_ext_editor_cmd());
9613 s = strstr(p, "%w");
9615 if (strstr(p, "%s") < s)
9616 buf = g_strdup_printf(p, file, socket_wid);
9618 buf = g_strdup_printf(p, socket_wid, file);
9621 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9624 if (prefs_common_get_ext_editor_cmd())
9625 g_warning("External editor command-line is invalid: '%s'",
9626 prefs_common_get_ext_editor_cmd());
9627 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9630 cmdline = strsplit_with_quote(buf, " ", 0);
9632 execvp(cmdline[0], cmdline);
9635 g_strfreev(cmdline);
9640 static gboolean compose_ext_editor_kill(Compose *compose)
9642 pid_t pgid = compose->exteditor_pid * -1;
9645 ret = kill(pgid, 0);
9647 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9651 msg = g_strdup_printf
9652 (_("The external editor is still working.\n"
9653 "Force terminating the process?\n"
9654 "process group id: %d"), -pgid);
9655 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9656 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9660 if (val == G_ALERTALTERNATE) {
9661 g_source_remove(compose->exteditor_tag);
9662 g_io_channel_shutdown(compose->exteditor_ch,
9664 g_io_channel_unref(compose->exteditor_ch);
9666 if (kill(pgid, SIGTERM) < 0) perror("kill");
9667 waitpid(compose->exteditor_pid, NULL, 0);
9669 g_warning("Terminated process group id: %d. "
9670 "Temporary file: %s", -pgid, compose->exteditor_file);
9672 compose_set_ext_editor_sensitive(compose, TRUE);
9674 g_free(compose->exteditor_file);
9675 compose->exteditor_file = NULL;
9676 compose->exteditor_pid = -1;
9677 compose->exteditor_ch = NULL;
9678 compose->exteditor_tag = -1;
9686 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9690 Compose *compose = (Compose *)data;
9693 debug_print("Compose: input from monitoring process\n");
9695 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9700 g_io_channel_shutdown(source, FALSE, NULL);
9701 g_io_channel_unref(source);
9703 waitpid(compose->exteditor_pid, NULL, 0);
9705 if (buf[0] == '0') { /* success */
9706 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9707 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9708 GtkTextIter start, end;
9711 gtk_text_buffer_set_text(buffer, "", -1);
9712 compose_insert_file(compose, compose->exteditor_file);
9713 compose_changed_cb(NULL, compose);
9715 /* Check if we should save the draft or not */
9716 if (compose_can_autosave(compose))
9717 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9719 if (claws_unlink(compose->exteditor_file) < 0)
9720 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9722 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9723 gtk_text_buffer_get_start_iter(buffer, &start);
9724 gtk_text_buffer_get_end_iter(buffer, &end);
9725 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9726 if (chars && strlen(chars) > 0)
9727 compose->modified = TRUE;
9729 } else if (buf[0] == '1') { /* failed */
9730 g_warning("Couldn't exec external editor");
9731 if (claws_unlink(compose->exteditor_file) < 0)
9732 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9733 } else if (buf[0] == '2') {
9734 g_warning("Couldn't write to file");
9735 } else if (buf[0] == '3') {
9736 g_warning("Pipe read failed");
9739 compose_set_ext_editor_sensitive(compose, TRUE);
9741 g_free(compose->exteditor_file);
9742 compose->exteditor_file = NULL;
9743 compose->exteditor_pid = -1;
9744 compose->exteditor_ch = NULL;
9745 compose->exteditor_tag = -1;
9746 if (compose->exteditor_socket) {
9747 gtk_widget_destroy(compose->exteditor_socket);
9748 compose->exteditor_socket = NULL;
9755 static char *ext_editor_menu_entries[] = {
9756 "Menu/Message/Send",
9757 "Menu/Message/SendLater",
9758 "Menu/Message/InsertFile",
9759 "Menu/Message/InsertSig",
9760 "Menu/Message/ReplaceSig",
9761 "Menu/Message/Save",
9762 "Menu/Message/Print",
9767 "Menu/Tools/ShowRuler",
9768 "Menu/Tools/Actions",
9773 static void compose_set_ext_editor_sensitive(Compose *compose,
9778 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9779 cm_menu_set_sensitive_full(compose->ui_manager,
9780 ext_editor_menu_entries[i], sensitive);
9783 if (compose_get_ext_editor_uses_socket()) {
9785 if (compose->exteditor_socket)
9786 gtk_widget_hide(compose->exteditor_socket);
9787 gtk_widget_show(compose->scrolledwin);
9788 if (prefs_common.show_ruler)
9789 gtk_widget_show(compose->ruler_hbox);
9790 /* Fix the focus, as it doesn't go anywhere when the
9791 * socket is hidden or destroyed */
9792 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9794 g_assert (compose->exteditor_socket != NULL);
9795 /* Fix the focus, as it doesn't go anywhere when the
9796 * edit box is hidden */
9797 if (gtk_widget_is_focus(compose->text))
9798 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9799 gtk_widget_hide(compose->scrolledwin);
9800 gtk_widget_hide(compose->ruler_hbox);
9801 gtk_widget_show(compose->exteditor_socket);
9804 gtk_widget_set_sensitive(compose->text, sensitive);
9806 if (compose->toolbar->send_btn)
9807 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9808 if (compose->toolbar->sendl_btn)
9809 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9810 if (compose->toolbar->draft_btn)
9811 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9812 if (compose->toolbar->insert_btn)
9813 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9814 if (compose->toolbar->sig_btn)
9815 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9816 if (compose->toolbar->exteditor_btn)
9817 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9818 if (compose->toolbar->linewrap_current_btn)
9819 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9820 if (compose->toolbar->linewrap_all_btn)
9821 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9824 static gboolean compose_get_ext_editor_uses_socket()
9826 return (prefs_common_get_ext_editor_cmd() &&
9827 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9830 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9832 compose->exteditor_socket = NULL;
9833 /* returning FALSE allows destruction of the socket */
9836 #endif /* G_OS_UNIX */
9839 * compose_undo_state_changed:
9841 * Change the sensivity of the menuentries undo and redo
9843 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9844 gint redo_state, gpointer data)
9846 Compose *compose = (Compose *)data;
9848 switch (undo_state) {
9849 case UNDO_STATE_TRUE:
9850 if (!undostruct->undo_state) {
9851 undostruct->undo_state = TRUE;
9852 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9855 case UNDO_STATE_FALSE:
9856 if (undostruct->undo_state) {
9857 undostruct->undo_state = FALSE;
9858 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9861 case UNDO_STATE_UNCHANGED:
9863 case UNDO_STATE_REFRESH:
9864 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9867 g_warning("Undo state not recognized");
9871 switch (redo_state) {
9872 case UNDO_STATE_TRUE:
9873 if (!undostruct->redo_state) {
9874 undostruct->redo_state = TRUE;
9875 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9878 case UNDO_STATE_FALSE:
9879 if (undostruct->redo_state) {
9880 undostruct->redo_state = FALSE;
9881 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9884 case UNDO_STATE_UNCHANGED:
9886 case UNDO_STATE_REFRESH:
9887 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9890 g_warning("Redo state not recognized");
9895 /* callback functions */
9897 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9898 GtkAllocation *allocation,
9901 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9904 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9905 * includes "non-client" (windows-izm) in calculation, so this calculation
9906 * may not be accurate.
9908 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9909 GtkAllocation *allocation,
9910 GtkSHRuler *shruler)
9912 if (prefs_common.show_ruler) {
9913 gint char_width = 0, char_height = 0;
9914 gint line_width_in_chars;
9916 gtkut_get_font_size(GTK_WIDGET(widget),
9917 &char_width, &char_height);
9918 line_width_in_chars =
9919 (allocation->width - allocation->x) / char_width;
9921 /* got the maximum */
9922 gtk_shruler_set_range(GTK_SHRULER(shruler),
9923 0.0, line_width_in_chars, 0);
9932 ComposePrefType type;
9933 gboolean entry_marked;
9936 static void account_activated(GtkComboBox *optmenu, gpointer data)
9938 Compose *compose = (Compose *)data;
9941 gchar *folderidentifier;
9942 gint account_id = 0;
9945 GSList *list, *saved_list = NULL;
9946 HeaderEntryState *state;
9948 /* Get ID of active account in the combo box */
9949 menu = gtk_combo_box_get_model(optmenu);
9950 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9951 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9953 ac = account_find_from_id(account_id);
9954 cm_return_if_fail(ac != NULL);
9956 if (ac != compose->account) {
9957 compose_select_account(compose, ac, FALSE);
9959 for (list = compose->header_list; list; list = list->next) {
9960 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9962 if (hentry->type == PREF_ACCOUNT || !list->next) {
9963 compose_destroy_headerentry(compose, hentry);
9966 state = g_malloc0(sizeof(HeaderEntryState));
9967 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9968 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9969 state->entry = gtk_editable_get_chars(
9970 GTK_EDITABLE(hentry->entry), 0, -1);
9971 state->type = hentry->type;
9973 saved_list = g_slist_append(saved_list, state);
9974 compose_destroy_headerentry(compose, hentry);
9977 compose->header_last = NULL;
9978 g_slist_free(compose->header_list);
9979 compose->header_list = NULL;
9980 compose->header_nextrow = 1;
9981 compose_create_header_entry(compose);
9983 if (ac->set_autocc && ac->auto_cc)
9984 compose_entry_append(compose, ac->auto_cc,
9985 COMPOSE_CC, PREF_ACCOUNT);
9986 if (ac->set_autobcc && ac->auto_bcc)
9987 compose_entry_append(compose, ac->auto_bcc,
9988 COMPOSE_BCC, PREF_ACCOUNT);
9989 if (ac->set_autoreplyto && ac->auto_replyto)
9990 compose_entry_append(compose, ac->auto_replyto,
9991 COMPOSE_REPLYTO, PREF_ACCOUNT);
9993 for (list = saved_list; list; list = list->next) {
9994 state = (HeaderEntryState *) list->data;
9996 compose_add_header_entry(compose, state->header,
9997 state->entry, state->type);
9999 g_free(state->header);
10000 g_free(state->entry);
10003 g_slist_free(saved_list);
10005 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10006 (ac->protocol == A_NNTP) ?
10007 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10010 /* Set message save folder */
10011 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10012 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10014 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10015 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10017 compose_set_save_to(compose, NULL);
10018 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10019 folderidentifier = folder_item_get_identifier(account_get_special_folder
10020 (compose->account, F_OUTBOX));
10021 compose_set_save_to(compose, folderidentifier);
10022 g_free(folderidentifier);
10026 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10027 GtkTreeViewColumn *column, Compose *compose)
10029 compose_attach_property(NULL, compose);
10032 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10035 Compose *compose = (Compose *)data;
10036 GtkTreeSelection *attach_selection;
10037 gint attach_nr_selected;
10040 if (!event) return FALSE;
10042 if (event->button == 3) {
10043 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10044 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10046 /* If no rows, or just one row is selected, right-click should
10047 * open menu relevant to the row being right-clicked on. We
10048 * achieve that by selecting the clicked row first. If more
10049 * than one row is selected, we shouldn't modify the selection,
10050 * as user may want to remove selected rows (attachments). */
10051 if (attach_nr_selected < 2) {
10052 gtk_tree_selection_unselect_all(attach_selection);
10053 attach_nr_selected = 0;
10054 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10055 event->x, event->y, &path, NULL, NULL, NULL);
10056 if (path != NULL) {
10057 gtk_tree_selection_select_path(attach_selection, path);
10058 gtk_tree_path_free(path);
10059 attach_nr_selected++;
10063 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10064 /* Properties menu item makes no sense with more than one row
10065 * selected, the properties dialog can only edit one attachment. */
10066 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10068 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10069 NULL, NULL, event->button, event->time);
10076 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10079 Compose *compose = (Compose *)data;
10081 if (!event) return FALSE;
10083 switch (event->keyval) {
10084 case GDK_KEY_Delete:
10085 compose_attach_remove_selected(NULL, compose);
10091 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10093 toolbar_comp_set_sensitive(compose, allow);
10094 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10095 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10097 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10099 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10100 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10101 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10103 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10107 static void compose_send_cb(GtkAction *action, gpointer data)
10109 Compose *compose = (Compose *)data;
10112 if (compose->exteditor_tag != -1) {
10113 debug_print("ignoring send: external editor still open\n");
10117 if (prefs_common.work_offline &&
10118 !inc_offline_should_override(TRUE,
10119 _("Claws Mail needs network access in order "
10120 "to send this email.")))
10123 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10124 g_source_remove(compose->draft_timeout_tag);
10125 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10128 compose_send(compose);
10131 static void compose_send_later_cb(GtkAction *action, gpointer data)
10133 Compose *compose = (Compose *)data;
10137 compose_allow_user_actions(compose, FALSE);
10138 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10139 compose_allow_user_actions(compose, TRUE);
10143 compose_close(compose);
10144 } else if (val == -1) {
10145 alertpanel_error(_("Could not queue message."));
10146 } else if (val == -2) {
10147 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10148 } else if (val == -3) {
10149 if (privacy_peek_error())
10150 alertpanel_error(_("Could not queue message for sending:\n\n"
10151 "Signature failed: %s"), privacy_get_error());
10152 } else if (val == -4) {
10153 alertpanel_error(_("Could not queue message for sending:\n\n"
10154 "Charset conversion failed."));
10155 } else if (val == -5) {
10156 alertpanel_error(_("Could not queue message for sending:\n\n"
10157 "Couldn't get recipient encryption key."));
10158 } else if (val == -6) {
10161 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10164 #define DRAFTED_AT_EXIT "drafted_at_exit"
10165 static void compose_register_draft(MsgInfo *info)
10167 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10168 DRAFTED_AT_EXIT, NULL);
10169 FILE *fp = g_fopen(filepath, "ab");
10172 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10180 gboolean compose_draft (gpointer data, guint action)
10182 Compose *compose = (Compose *)data;
10187 MsgFlags flag = {0, 0};
10188 static gboolean lock = FALSE;
10189 MsgInfo *newmsginfo;
10191 gboolean target_locked = FALSE;
10192 gboolean err = FALSE;
10194 if (lock) return FALSE;
10196 if (compose->sending)
10199 draft = account_get_special_folder(compose->account, F_DRAFT);
10200 cm_return_val_if_fail(draft != NULL, FALSE);
10202 if (!g_mutex_trylock(compose->mutex)) {
10203 /* we don't want to lock the mutex once it's available,
10204 * because as the only other part of compose.c locking
10205 * it is compose_close - which means once unlocked,
10206 * the compose struct will be freed */
10207 debug_print("couldn't lock mutex, probably sending\n");
10213 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10214 G_DIR_SEPARATOR, compose);
10215 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10216 FILE_OP_ERROR(tmp, "fopen");
10220 /* chmod for security */
10221 if (change_file_mode_rw(fp, tmp) < 0) {
10222 FILE_OP_ERROR(tmp, "chmod");
10223 g_warning("can't change file mode");
10226 /* Save draft infos */
10227 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10228 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10230 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10231 gchar *savefolderid;
10233 savefolderid = compose_get_save_to(compose);
10234 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10235 g_free(savefolderid);
10237 if (compose->return_receipt) {
10238 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10240 if (compose->privacy_system) {
10241 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10242 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10243 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10246 /* Message-ID of message replying to */
10247 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10248 gchar *folderid = NULL;
10250 if (compose->replyinfo->folder)
10251 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10252 if (folderid == NULL)
10253 folderid = g_strdup("NULL");
10255 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10258 /* Message-ID of message forwarding to */
10259 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10260 gchar *folderid = NULL;
10262 if (compose->fwdinfo->folder)
10263 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10264 if (folderid == NULL)
10265 folderid = g_strdup("NULL");
10267 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10271 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10272 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10274 sheaders = compose_get_manual_headers_info(compose);
10275 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10278 /* end of headers */
10279 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10286 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10290 if (fclose(fp) == EOF) {
10294 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10295 if (compose->targetinfo) {
10296 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10298 flag.perm_flags |= MSG_LOCKED;
10300 flag.tmp_flags = MSG_DRAFT;
10302 folder_item_scan(draft);
10303 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10304 MsgInfo *tmpinfo = NULL;
10305 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10306 if (compose->msgid) {
10307 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10310 msgnum = tmpinfo->msgnum;
10311 procmsg_msginfo_free(&tmpinfo);
10312 debug_print("got draft msgnum %d from scanning\n", msgnum);
10314 debug_print("didn't get draft msgnum after scanning\n");
10317 debug_print("got draft msgnum %d from adding\n", msgnum);
10323 if (action != COMPOSE_AUTO_SAVE) {
10324 if (action != COMPOSE_DRAFT_FOR_EXIT)
10325 alertpanel_error(_("Could not save draft."));
10328 gtkut_window_popup(compose->window);
10329 val = alertpanel_full(_("Could not save draft"),
10330 _("Could not save draft.\n"
10331 "Do you want to cancel exit or discard this email?"),
10332 _("_Cancel exit"), _("_Discard email"), NULL,
10333 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10334 if (val == G_ALERTALTERNATE) {
10336 g_mutex_unlock(compose->mutex); /* must be done before closing */
10337 compose_close(compose);
10341 g_mutex_unlock(compose->mutex); /* must be done before closing */
10350 if (compose->mode == COMPOSE_REEDIT) {
10351 compose_remove_reedit_target(compose, TRUE);
10354 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10357 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10359 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10361 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10362 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10363 procmsg_msginfo_set_flags(newmsginfo, 0,
10364 MSG_HAS_ATTACHMENT);
10366 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10367 compose_register_draft(newmsginfo);
10369 procmsg_msginfo_free(&newmsginfo);
10372 folder_item_scan(draft);
10374 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10376 g_mutex_unlock(compose->mutex); /* must be done before closing */
10377 compose_close(compose);
10383 path = folder_item_fetch_msg(draft, msgnum);
10384 if (path == NULL) {
10385 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10388 if (g_stat(path, &s) < 0) {
10389 FILE_OP_ERROR(path, "stat");
10395 procmsg_msginfo_free(&(compose->targetinfo));
10396 compose->targetinfo = procmsg_msginfo_new();
10397 compose->targetinfo->msgnum = msgnum;
10398 compose->targetinfo->size = (goffset)s.st_size;
10399 compose->targetinfo->mtime = s.st_mtime;
10400 compose->targetinfo->folder = draft;
10402 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10403 compose->mode = COMPOSE_REEDIT;
10405 if (action == COMPOSE_AUTO_SAVE) {
10406 compose->autosaved_draft = compose->targetinfo;
10408 compose->modified = FALSE;
10409 compose_set_title(compose);
10413 g_mutex_unlock(compose->mutex);
10417 void compose_clear_exit_drafts(void)
10419 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10420 DRAFTED_AT_EXIT, NULL);
10421 if (is_file_exist(filepath))
10422 claws_unlink(filepath);
10427 void compose_reopen_exit_drafts(void)
10429 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10430 DRAFTED_AT_EXIT, NULL);
10431 FILE *fp = g_fopen(filepath, "rb");
10435 while (fgets(buf, sizeof(buf), fp)) {
10436 gchar **parts = g_strsplit(buf, "\t", 2);
10437 const gchar *folder = parts[0];
10438 int msgnum = parts[1] ? atoi(parts[1]):-1;
10440 if (folder && *folder && msgnum > -1) {
10441 FolderItem *item = folder_find_item_from_identifier(folder);
10442 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10444 compose_reedit(info, FALSE);
10451 compose_clear_exit_drafts();
10454 static void compose_save_cb(GtkAction *action, gpointer data)
10456 Compose *compose = (Compose *)data;
10457 compose_draft(compose, COMPOSE_KEEP_EDITING);
10458 compose->rmode = COMPOSE_REEDIT;
10461 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10463 if (compose && file_list) {
10466 for ( tmp = file_list; tmp; tmp = tmp->next) {
10467 gchar *file = (gchar *) tmp->data;
10468 gchar *utf8_filename = conv_filename_to_utf8(file);
10469 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10470 compose_changed_cb(NULL, compose);
10475 g_free(utf8_filename);
10480 static void compose_attach_cb(GtkAction *action, gpointer data)
10482 Compose *compose = (Compose *)data;
10485 if (compose->redirect_filename != NULL)
10488 /* Set focus_window properly, in case we were called via popup menu,
10489 * which unsets it (via focus_out_event callback on compose window). */
10490 manage_window_focus_in(compose->window, NULL, NULL);
10492 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10495 compose_attach_from_list(compose, file_list, TRUE);
10496 g_list_free(file_list);
10500 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10502 Compose *compose = (Compose *)data;
10504 gint files_inserted = 0;
10506 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10511 for ( tmp = file_list; tmp; tmp = tmp->next) {
10512 gchar *file = (gchar *) tmp->data;
10513 gchar *filedup = g_strdup(file);
10514 gchar *shortfile = g_path_get_basename(filedup);
10515 ComposeInsertResult res;
10516 /* insert the file if the file is short or if the user confirmed that
10517 he/she wants to insert the large file */
10518 res = compose_insert_file(compose, file);
10519 if (res == COMPOSE_INSERT_READ_ERROR) {
10520 alertpanel_error(_("File '%s' could not be read."), shortfile);
10521 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10522 alertpanel_error(_("File '%s' contained invalid characters\n"
10523 "for the current encoding, insertion may be incorrect."),
10525 } else if (res == COMPOSE_INSERT_SUCCESS)
10532 g_list_free(file_list);
10536 if (files_inserted > 0 && compose->gtkaspell &&
10537 compose->gtkaspell->check_while_typing)
10538 gtkaspell_highlight_all(compose->gtkaspell);
10542 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10544 Compose *compose = (Compose *)data;
10546 compose_insert_sig(compose, FALSE);
10549 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10551 Compose *compose = (Compose *)data;
10553 compose_insert_sig(compose, TRUE);
10556 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10560 Compose *compose = (Compose *)data;
10562 gtkut_widget_get_uposition(widget, &x, &y);
10563 if (!compose->batch) {
10564 prefs_common.compose_x = x;
10565 prefs_common.compose_y = y;
10567 if (compose->sending || compose->updating)
10569 compose_close_cb(NULL, compose);
10573 void compose_close_toolbar(Compose *compose)
10575 compose_close_cb(NULL, compose);
10578 static void compose_close_cb(GtkAction *action, gpointer data)
10580 Compose *compose = (Compose *)data;
10584 if (compose->exteditor_tag != -1) {
10585 if (!compose_ext_editor_kill(compose))
10590 if (compose->modified) {
10591 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10592 if (!g_mutex_trylock(compose->mutex)) {
10593 /* we don't want to lock the mutex once it's available,
10594 * because as the only other part of compose.c locking
10595 * it is compose_close - which means once unlocked,
10596 * the compose struct will be freed */
10597 debug_print("couldn't lock mutex, probably sending\n");
10601 val = alertpanel(_("Discard message"),
10602 _("This message has been modified. Discard it?"),
10603 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10605 val = alertpanel(_("Save changes"),
10606 _("This message has been modified. Save the latest changes?"),
10607 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10610 g_mutex_unlock(compose->mutex);
10612 case G_ALERTDEFAULT:
10613 if (compose_can_autosave(compose) && !reedit)
10614 compose_remove_draft(compose);
10616 case G_ALERTALTERNATE:
10617 compose_draft(data, COMPOSE_QUIT_EDITING);
10624 compose_close(compose);
10627 static void compose_print_cb(GtkAction *action, gpointer data)
10629 Compose *compose = (Compose *) data;
10631 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10632 if (compose->targetinfo)
10633 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10636 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10638 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10639 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10640 Compose *compose = (Compose *) data;
10643 compose->out_encoding = (CharSet)value;
10646 static void compose_address_cb(GtkAction *action, gpointer data)
10648 Compose *compose = (Compose *)data;
10650 #ifndef USE_ALT_ADDRBOOK
10651 addressbook_open(compose);
10653 GError* error = NULL;
10654 addressbook_connect_signals(compose);
10655 addressbook_dbus_open(TRUE, &error);
10657 g_warning("%s", error->message);
10658 g_error_free(error);
10663 static void about_show_cb(GtkAction *action, gpointer data)
10668 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10670 Compose *compose = (Compose *)data;
10675 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10676 cm_return_if_fail(tmpl != NULL);
10678 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10680 val = alertpanel(_("Apply template"), msg,
10681 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10684 if (val == G_ALERTDEFAULT)
10685 compose_template_apply(compose, tmpl, TRUE);
10686 else if (val == G_ALERTALTERNATE)
10687 compose_template_apply(compose, tmpl, FALSE);
10690 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10692 Compose *compose = (Compose *)data;
10695 if (compose->exteditor_tag != -1) {
10696 debug_print("ignoring open external editor: external editor still open\n");
10700 compose_exec_ext_editor(compose);
10703 static void compose_undo_cb(GtkAction *action, gpointer data)
10705 Compose *compose = (Compose *)data;
10706 gboolean prev_autowrap = compose->autowrap;
10708 compose->autowrap = FALSE;
10709 undo_undo(compose->undostruct);
10710 compose->autowrap = prev_autowrap;
10713 static void compose_redo_cb(GtkAction *action, gpointer data)
10715 Compose *compose = (Compose *)data;
10716 gboolean prev_autowrap = compose->autowrap;
10718 compose->autowrap = FALSE;
10719 undo_redo(compose->undostruct);
10720 compose->autowrap = prev_autowrap;
10723 static void entry_cut_clipboard(GtkWidget *entry)
10725 if (GTK_IS_EDITABLE(entry))
10726 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10727 else if (GTK_IS_TEXT_VIEW(entry))
10728 gtk_text_buffer_cut_clipboard(
10729 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10730 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10734 static void entry_copy_clipboard(GtkWidget *entry)
10736 if (GTK_IS_EDITABLE(entry))
10737 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10738 else if (GTK_IS_TEXT_VIEW(entry))
10739 gtk_text_buffer_copy_clipboard(
10740 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10741 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10744 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10745 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10747 if (GTK_IS_TEXT_VIEW(entry)) {
10748 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10749 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10750 GtkTextIter start_iter, end_iter;
10752 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10754 if (contents == NULL)
10757 /* we shouldn't delete the selection when middle-click-pasting, or we
10758 * can't mid-click-paste our own selection */
10759 if (clip != GDK_SELECTION_PRIMARY) {
10760 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10761 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10764 if (insert_place == NULL) {
10765 /* if insert_place isn't specified, insert at the cursor.
10766 * used for Ctrl-V pasting */
10767 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10768 start = gtk_text_iter_get_offset(&start_iter);
10769 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10771 /* if insert_place is specified, paste here.
10772 * used for mid-click-pasting */
10773 start = gtk_text_iter_get_offset(insert_place);
10774 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10775 if (prefs_common.primary_paste_unselects)
10776 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10780 /* paste unwrapped: mark the paste so it's not wrapped later */
10781 end = start + strlen(contents);
10782 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10783 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10784 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10785 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10786 /* rewrap paragraph now (after a mid-click-paste) */
10787 mark_start = gtk_text_buffer_get_insert(buffer);
10788 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10789 gtk_text_iter_backward_char(&start_iter);
10790 compose_beautify_paragraph(compose, &start_iter, TRUE);
10792 } else if (GTK_IS_EDITABLE(entry))
10793 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10795 compose->modified = TRUE;
10798 static void entry_allsel(GtkWidget *entry)
10800 if (GTK_IS_EDITABLE(entry))
10801 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10802 else if (GTK_IS_TEXT_VIEW(entry)) {
10803 GtkTextIter startiter, enditer;
10804 GtkTextBuffer *textbuf;
10806 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10807 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10808 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10810 gtk_text_buffer_move_mark_by_name(textbuf,
10811 "selection_bound", &startiter);
10812 gtk_text_buffer_move_mark_by_name(textbuf,
10813 "insert", &enditer);
10817 static void compose_cut_cb(GtkAction *action, gpointer data)
10819 Compose *compose = (Compose *)data;
10820 if (compose->focused_editable
10821 #ifndef GENERIC_UMPC
10822 && gtk_widget_has_focus(compose->focused_editable)
10825 entry_cut_clipboard(compose->focused_editable);
10828 static void compose_copy_cb(GtkAction *action, gpointer data)
10830 Compose *compose = (Compose *)data;
10831 if (compose->focused_editable
10832 #ifndef GENERIC_UMPC
10833 && gtk_widget_has_focus(compose->focused_editable)
10836 entry_copy_clipboard(compose->focused_editable);
10839 static void compose_paste_cb(GtkAction *action, gpointer data)
10841 Compose *compose = (Compose *)data;
10842 gint prev_autowrap;
10843 GtkTextBuffer *buffer;
10845 if (compose->focused_editable &&
10846 #ifndef GENERIC_UMPC
10847 gtk_widget_has_focus(compose->focused_editable)
10850 entry_paste_clipboard(compose, compose->focused_editable,
10851 prefs_common.linewrap_pastes,
10852 GDK_SELECTION_CLIPBOARD, NULL);
10857 #ifndef GENERIC_UMPC
10858 gtk_widget_has_focus(compose->text) &&
10860 compose->gtkaspell &&
10861 compose->gtkaspell->check_while_typing)
10862 gtkaspell_highlight_all(compose->gtkaspell);
10866 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10868 Compose *compose = (Compose *)data;
10869 gint wrap_quote = prefs_common.linewrap_quote;
10870 if (compose->focused_editable
10871 #ifndef GENERIC_UMPC
10872 && gtk_widget_has_focus(compose->focused_editable)
10875 /* let text_insert() (called directly or at a later time
10876 * after the gtk_editable_paste_clipboard) know that
10877 * text is to be inserted as a quotation. implemented
10878 * by using a simple refcount... */
10879 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10880 G_OBJECT(compose->focused_editable),
10881 "paste_as_quotation"));
10882 g_object_set_data(G_OBJECT(compose->focused_editable),
10883 "paste_as_quotation",
10884 GINT_TO_POINTER(paste_as_quotation + 1));
10885 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10886 entry_paste_clipboard(compose, compose->focused_editable,
10887 prefs_common.linewrap_pastes,
10888 GDK_SELECTION_CLIPBOARD, NULL);
10889 prefs_common.linewrap_quote = wrap_quote;
10893 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10895 Compose *compose = (Compose *)data;
10896 gint prev_autowrap;
10897 GtkTextBuffer *buffer;
10899 if (compose->focused_editable
10900 #ifndef GENERIC_UMPC
10901 && gtk_widget_has_focus(compose->focused_editable)
10904 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10905 GDK_SELECTION_CLIPBOARD, NULL);
10910 #ifndef GENERIC_UMPC
10911 gtk_widget_has_focus(compose->text) &&
10913 compose->gtkaspell &&
10914 compose->gtkaspell->check_while_typing)
10915 gtkaspell_highlight_all(compose->gtkaspell);
10919 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10921 Compose *compose = (Compose *)data;
10922 gint prev_autowrap;
10923 GtkTextBuffer *buffer;
10925 if (compose->focused_editable
10926 #ifndef GENERIC_UMPC
10927 && gtk_widget_has_focus(compose->focused_editable)
10930 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10931 GDK_SELECTION_CLIPBOARD, NULL);
10936 #ifndef GENERIC_UMPC
10937 gtk_widget_has_focus(compose->text) &&
10939 compose->gtkaspell &&
10940 compose->gtkaspell->check_while_typing)
10941 gtkaspell_highlight_all(compose->gtkaspell);
10945 static void compose_allsel_cb(GtkAction *action, gpointer data)
10947 Compose *compose = (Compose *)data;
10948 if (compose->focused_editable
10949 #ifndef GENERIC_UMPC
10950 && gtk_widget_has_focus(compose->focused_editable)
10953 entry_allsel(compose->focused_editable);
10956 static void textview_move_beginning_of_line (GtkTextView *text)
10958 GtkTextBuffer *buffer;
10962 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10964 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10965 mark = gtk_text_buffer_get_insert(buffer);
10966 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10967 gtk_text_iter_set_line_offset(&ins, 0);
10968 gtk_text_buffer_place_cursor(buffer, &ins);
10971 static void textview_move_forward_character (GtkTextView *text)
10973 GtkTextBuffer *buffer;
10977 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10979 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10980 mark = gtk_text_buffer_get_insert(buffer);
10981 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10982 if (gtk_text_iter_forward_cursor_position(&ins))
10983 gtk_text_buffer_place_cursor(buffer, &ins);
10986 static void textview_move_backward_character (GtkTextView *text)
10988 GtkTextBuffer *buffer;
10992 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10994 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10995 mark = gtk_text_buffer_get_insert(buffer);
10996 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10997 if (gtk_text_iter_backward_cursor_position(&ins))
10998 gtk_text_buffer_place_cursor(buffer, &ins);
11001 static void textview_move_forward_word (GtkTextView *text)
11003 GtkTextBuffer *buffer;
11008 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11010 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11011 mark = gtk_text_buffer_get_insert(buffer);
11012 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11013 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11014 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11015 gtk_text_iter_backward_word_start(&ins);
11016 gtk_text_buffer_place_cursor(buffer, &ins);
11020 static void textview_move_backward_word (GtkTextView *text)
11022 GtkTextBuffer *buffer;
11026 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11028 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11029 mark = gtk_text_buffer_get_insert(buffer);
11030 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11031 if (gtk_text_iter_backward_word_starts(&ins, 1))
11032 gtk_text_buffer_place_cursor(buffer, &ins);
11035 static void textview_move_end_of_line (GtkTextView *text)
11037 GtkTextBuffer *buffer;
11041 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11043 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11044 mark = gtk_text_buffer_get_insert(buffer);
11045 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11046 if (gtk_text_iter_forward_to_line_end(&ins))
11047 gtk_text_buffer_place_cursor(buffer, &ins);
11050 static void textview_move_next_line (GtkTextView *text)
11052 GtkTextBuffer *buffer;
11057 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11059 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11060 mark = gtk_text_buffer_get_insert(buffer);
11061 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11062 offset = gtk_text_iter_get_line_offset(&ins);
11063 if (gtk_text_iter_forward_line(&ins)) {
11064 gtk_text_iter_set_line_offset(&ins, offset);
11065 gtk_text_buffer_place_cursor(buffer, &ins);
11069 static void textview_move_previous_line (GtkTextView *text)
11071 GtkTextBuffer *buffer;
11076 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11078 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11079 mark = gtk_text_buffer_get_insert(buffer);
11080 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11081 offset = gtk_text_iter_get_line_offset(&ins);
11082 if (gtk_text_iter_backward_line(&ins)) {
11083 gtk_text_iter_set_line_offset(&ins, offset);
11084 gtk_text_buffer_place_cursor(buffer, &ins);
11088 static void textview_delete_forward_character (GtkTextView *text)
11090 GtkTextBuffer *buffer;
11092 GtkTextIter ins, end_iter;
11094 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11096 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11097 mark = gtk_text_buffer_get_insert(buffer);
11098 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11100 if (gtk_text_iter_forward_char(&end_iter)) {
11101 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11105 static void textview_delete_backward_character (GtkTextView *text)
11107 GtkTextBuffer *buffer;
11109 GtkTextIter ins, end_iter;
11111 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11113 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11114 mark = gtk_text_buffer_get_insert(buffer);
11115 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11117 if (gtk_text_iter_backward_char(&end_iter)) {
11118 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11122 static void textview_delete_forward_word (GtkTextView *text)
11124 GtkTextBuffer *buffer;
11126 GtkTextIter ins, end_iter;
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);
11134 if (gtk_text_iter_forward_word_end(&end_iter)) {
11135 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11139 static void textview_delete_backward_word (GtkTextView *text)
11141 GtkTextBuffer *buffer;
11143 GtkTextIter ins, end_iter;
11145 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11147 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11148 mark = gtk_text_buffer_get_insert(buffer);
11149 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11151 if (gtk_text_iter_backward_word_start(&end_iter)) {
11152 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11156 static void textview_delete_line (GtkTextView *text)
11158 GtkTextBuffer *buffer;
11160 GtkTextIter ins, start_iter, end_iter;
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);
11169 gtk_text_iter_set_line_offset(&start_iter, 0);
11172 if (gtk_text_iter_ends_line(&end_iter)){
11173 if (!gtk_text_iter_forward_char(&end_iter))
11174 gtk_text_iter_backward_char(&start_iter);
11177 gtk_text_iter_forward_to_line_end(&end_iter);
11178 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11181 static void textview_delete_to_line_end (GtkTextView *text)
11183 GtkTextBuffer *buffer;
11185 GtkTextIter ins, end_iter;
11187 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11189 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11190 mark = gtk_text_buffer_get_insert(buffer);
11191 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11193 if (gtk_text_iter_ends_line(&end_iter))
11194 gtk_text_iter_forward_char(&end_iter);
11196 gtk_text_iter_forward_to_line_end(&end_iter);
11197 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11200 #define DO_ACTION(name, act) { \
11201 if(!strcmp(name, a_name)) { \
11205 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11207 const gchar *a_name = gtk_action_get_name(action);
11208 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11209 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11210 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11211 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11212 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11213 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11214 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11215 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11216 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11217 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11218 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11219 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11220 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11221 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11222 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11225 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11227 Compose *compose = (Compose *)data;
11228 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11229 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11231 action = compose_call_advanced_action_from_path(gaction);
11234 void (*do_action) (GtkTextView *text);
11235 } action_table[] = {
11236 {textview_move_beginning_of_line},
11237 {textview_move_forward_character},
11238 {textview_move_backward_character},
11239 {textview_move_forward_word},
11240 {textview_move_backward_word},
11241 {textview_move_end_of_line},
11242 {textview_move_next_line},
11243 {textview_move_previous_line},
11244 {textview_delete_forward_character},
11245 {textview_delete_backward_character},
11246 {textview_delete_forward_word},
11247 {textview_delete_backward_word},
11248 {textview_delete_line},
11249 {textview_delete_to_line_end}
11252 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11254 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11255 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11256 if (action_table[action].do_action)
11257 action_table[action].do_action(text);
11259 g_warning("Not implemented yet.");
11263 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11265 GtkAllocation allocation;
11269 if (GTK_IS_EDITABLE(widget)) {
11270 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11271 gtk_editable_set_position(GTK_EDITABLE(widget),
11274 if ((parent = gtk_widget_get_parent(widget))
11275 && (parent = gtk_widget_get_parent(parent))
11276 && (parent = gtk_widget_get_parent(parent))) {
11277 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11278 gtk_widget_get_allocation(widget, &allocation);
11279 gint y = allocation.y;
11280 gint height = allocation.height;
11281 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11282 (GTK_SCROLLED_WINDOW(parent));
11284 gfloat value = gtk_adjustment_get_value(shown);
11285 gfloat upper = gtk_adjustment_get_upper(shown);
11286 gfloat page_size = gtk_adjustment_get_page_size(shown);
11287 if (y < (int)value) {
11288 gtk_adjustment_set_value(shown, y - 1);
11290 if ((y + height) > ((int)value + (int)page_size)) {
11291 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11292 gtk_adjustment_set_value(shown,
11293 y + height - (int)page_size - 1);
11295 gtk_adjustment_set_value(shown,
11296 (int)upper - (int)page_size - 1);
11303 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11304 compose->focused_editable = widget;
11306 #ifdef GENERIC_UMPC
11307 if (GTK_IS_TEXT_VIEW(widget)
11308 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11309 g_object_ref(compose->notebook);
11310 g_object_ref(compose->edit_vbox);
11311 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11312 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11313 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11314 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11315 g_object_unref(compose->notebook);
11316 g_object_unref(compose->edit_vbox);
11317 g_signal_handlers_block_by_func(G_OBJECT(widget),
11318 G_CALLBACK(compose_grab_focus_cb),
11320 gtk_widget_grab_focus(widget);
11321 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11322 G_CALLBACK(compose_grab_focus_cb),
11324 } else if (!GTK_IS_TEXT_VIEW(widget)
11325 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11326 g_object_ref(compose->notebook);
11327 g_object_ref(compose->edit_vbox);
11328 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11329 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11330 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11331 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11332 g_object_unref(compose->notebook);
11333 g_object_unref(compose->edit_vbox);
11334 g_signal_handlers_block_by_func(G_OBJECT(widget),
11335 G_CALLBACK(compose_grab_focus_cb),
11337 gtk_widget_grab_focus(widget);
11338 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11339 G_CALLBACK(compose_grab_focus_cb),
11345 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11347 compose->modified = TRUE;
11348 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11349 #ifndef GENERIC_UMPC
11350 compose_set_title(compose);
11354 static void compose_wrap_cb(GtkAction *action, gpointer data)
11356 Compose *compose = (Compose *)data;
11357 compose_beautify_paragraph(compose, NULL, TRUE);
11360 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11362 Compose *compose = (Compose *)data;
11363 compose_wrap_all_full(compose, TRUE);
11366 static void compose_find_cb(GtkAction *action, gpointer data)
11368 Compose *compose = (Compose *)data;
11370 message_search_compose(compose);
11373 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11376 Compose *compose = (Compose *)data;
11377 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11378 if (compose->autowrap)
11379 compose_wrap_all_full(compose, TRUE);
11380 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11383 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11386 Compose *compose = (Compose *)data;
11387 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11390 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11392 Compose *compose = (Compose *)data;
11394 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11395 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11398 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11400 Compose *compose = (Compose *)data;
11402 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11403 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11406 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11408 g_free(compose->privacy_system);
11409 g_free(compose->encdata);
11411 compose->privacy_system = g_strdup(account->default_privacy_system);
11412 compose_update_privacy_system_menu_item(compose, warn);
11415 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11417 Compose *compose = (Compose *)data;
11419 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11420 gtk_widget_show(compose->ruler_hbox);
11421 prefs_common.show_ruler = TRUE;
11423 gtk_widget_hide(compose->ruler_hbox);
11424 gtk_widget_queue_resize(compose->edit_vbox);
11425 prefs_common.show_ruler = FALSE;
11429 static void compose_attach_drag_received_cb (GtkWidget *widget,
11430 GdkDragContext *context,
11433 GtkSelectionData *data,
11436 gpointer user_data)
11438 Compose *compose = (Compose *)user_data;
11442 type = gtk_selection_data_get_data_type(data);
11443 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11444 && gtk_drag_get_source_widget(context) !=
11445 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11446 list = uri_list_extract_filenames(
11447 (const gchar *)gtk_selection_data_get_data(data));
11448 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11449 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11450 compose_attach_append
11451 (compose, (const gchar *)tmp->data,
11452 utf8_filename, NULL, NULL);
11453 g_free(utf8_filename);
11455 if (list) compose_changed_cb(NULL, compose);
11456 list_free_strings(list);
11458 } else if (gtk_drag_get_source_widget(context)
11459 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11460 /* comes from our summaryview */
11461 SummaryView * summaryview = NULL;
11462 GSList * list = NULL, *cur = NULL;
11464 if (mainwindow_get_mainwindow())
11465 summaryview = mainwindow_get_mainwindow()->summaryview;
11468 list = summary_get_selected_msg_list(summaryview);
11470 for (cur = list; cur; cur = cur->next) {
11471 MsgInfo *msginfo = (MsgInfo *)cur->data;
11472 gchar *file = NULL;
11474 file = procmsg_get_message_file_full(msginfo,
11477 compose_attach_append(compose, (const gchar *)file,
11478 (const gchar *)file, "message/rfc822", NULL);
11482 g_slist_free(list);
11486 static gboolean compose_drag_drop(GtkWidget *widget,
11487 GdkDragContext *drag_context,
11489 guint time, gpointer user_data)
11491 /* not handling this signal makes compose_insert_drag_received_cb
11496 static gboolean completion_set_focus_to_subject
11497 (GtkWidget *widget,
11498 GdkEventKey *event,
11501 cm_return_val_if_fail(compose != NULL, FALSE);
11503 /* make backtab move to subject field */
11504 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11505 gtk_widget_grab_focus(compose->subject_entry);
11511 static void compose_insert_drag_received_cb (GtkWidget *widget,
11512 GdkDragContext *drag_context,
11515 GtkSelectionData *data,
11518 gpointer user_data)
11520 Compose *compose = (Compose *)user_data;
11526 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11528 type = gtk_selection_data_get_data_type(data);
11529 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11530 AlertValue val = G_ALERTDEFAULT;
11531 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11533 list = uri_list_extract_filenames(ddata);
11534 num_files = g_list_length(list);
11535 if (list == NULL && strstr(ddata, "://")) {
11536 /* Assume a list of no files, and data has ://, is a remote link */
11537 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11538 gchar *tmpfile = get_tmp_file();
11539 str_write_to_file(tmpdata, tmpfile);
11541 compose_insert_file(compose, tmpfile);
11542 claws_unlink(tmpfile);
11544 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11545 compose_beautify_paragraph(compose, NULL, TRUE);
11548 switch (prefs_common.compose_dnd_mode) {
11549 case COMPOSE_DND_ASK:
11550 msg = g_strdup_printf(
11552 "Do you want to insert the contents of the file "
11553 "into the message body, or attach it to the email?",
11554 "Do you want to insert the contents of the %d files "
11555 "into the message body, or attach them to the email?",
11558 val = alertpanel_full(_("Insert or attach?"), msg,
11559 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11560 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11563 case COMPOSE_DND_INSERT:
11564 val = G_ALERTALTERNATE;
11566 case COMPOSE_DND_ATTACH:
11567 val = G_ALERTOTHER;
11570 /* unexpected case */
11571 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11574 if (val & G_ALERTDISABLE) {
11575 val &= ~G_ALERTDISABLE;
11576 /* remember what action to perform by default, only if we don't click Cancel */
11577 if (val == G_ALERTALTERNATE)
11578 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11579 else if (val == G_ALERTOTHER)
11580 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11583 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11584 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11585 list_free_strings(list);
11588 } else if (val == G_ALERTOTHER) {
11589 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11590 list_free_strings(list);
11595 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11596 compose_insert_file(compose, (const gchar *)tmp->data);
11598 list_free_strings(list);
11600 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11605 static void compose_header_drag_received_cb (GtkWidget *widget,
11606 GdkDragContext *drag_context,
11609 GtkSelectionData *data,
11612 gpointer user_data)
11614 GtkEditable *entry = (GtkEditable *)user_data;
11615 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11617 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11620 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11621 gchar *decoded=g_new(gchar, strlen(email));
11624 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11625 gtk_editable_delete_text(entry, 0, -1);
11626 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11627 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11631 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11634 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11636 Compose *compose = (Compose *)data;
11638 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11639 compose->return_receipt = TRUE;
11641 compose->return_receipt = FALSE;
11644 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11646 Compose *compose = (Compose *)data;
11648 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11649 compose->remove_references = TRUE;
11651 compose->remove_references = FALSE;
11654 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11655 ComposeHeaderEntry *headerentry)
11657 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11661 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11662 GdkEventKey *event,
11663 ComposeHeaderEntry *headerentry)
11665 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11666 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11667 !(event->state & GDK_MODIFIER_MASK) &&
11668 (event->keyval == GDK_KEY_BackSpace) &&
11669 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11670 gtk_container_remove
11671 (GTK_CONTAINER(headerentry->compose->header_table),
11672 headerentry->combo);
11673 gtk_container_remove
11674 (GTK_CONTAINER(headerentry->compose->header_table),
11675 headerentry->entry);
11676 headerentry->compose->header_list =
11677 g_slist_remove(headerentry->compose->header_list,
11679 g_free(headerentry);
11680 } else if (event->keyval == GDK_KEY_Tab) {
11681 if (headerentry->compose->header_last == headerentry) {
11682 /* Override default next focus, and give it to subject_entry
11683 * instead of notebook tabs
11685 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11686 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11693 static gboolean scroll_postpone(gpointer data)
11695 Compose *compose = (Compose *)data;
11697 if (compose->batch)
11700 GTK_EVENTS_FLUSH();
11701 compose_show_first_last_header(compose, FALSE);
11705 static void compose_headerentry_changed_cb(GtkWidget *entry,
11706 ComposeHeaderEntry *headerentry)
11708 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11709 compose_create_header_entry(headerentry->compose);
11710 g_signal_handlers_disconnect_matched
11711 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11712 0, 0, NULL, NULL, headerentry);
11714 if (!headerentry->compose->batch)
11715 g_timeout_add(0, scroll_postpone, headerentry->compose);
11719 static gboolean compose_defer_auto_save_draft(Compose *compose)
11721 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11722 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11726 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11728 GtkAdjustment *vadj;
11730 cm_return_if_fail(compose);
11735 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11736 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11737 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11738 gtk_widget_get_parent(compose->header_table)));
11739 gtk_adjustment_set_value(vadj, (show_first ?
11740 gtk_adjustment_get_lower(vadj) :
11741 (gtk_adjustment_get_upper(vadj) -
11742 gtk_adjustment_get_page_size(vadj))));
11743 gtk_adjustment_changed(vadj);
11746 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11747 const gchar *text, gint len, Compose *compose)
11749 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11750 (G_OBJECT(compose->text), "paste_as_quotation"));
11753 cm_return_if_fail(text != NULL);
11755 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11756 G_CALLBACK(text_inserted),
11758 if (paste_as_quotation) {
11760 const gchar *qmark;
11762 GtkTextIter start_iter;
11765 len = strlen(text);
11767 new_text = g_strndup(text, len);
11769 qmark = compose_quote_char_from_context(compose);
11771 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11772 gtk_text_buffer_place_cursor(buffer, iter);
11774 pos = gtk_text_iter_get_offset(iter);
11776 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11777 _("Quote format error at line %d."));
11778 quote_fmt_reset_vartable();
11780 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11781 GINT_TO_POINTER(paste_as_quotation - 1));
11783 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11784 gtk_text_buffer_place_cursor(buffer, iter);
11785 gtk_text_buffer_delete_mark(buffer, mark);
11787 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11788 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11789 compose_beautify_paragraph(compose, &start_iter, FALSE);
11790 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11791 gtk_text_buffer_delete_mark(buffer, mark);
11793 if (strcmp(text, "\n") || compose->automatic_break
11794 || gtk_text_iter_starts_line(iter)) {
11795 GtkTextIter before_ins;
11796 gtk_text_buffer_insert(buffer, iter, text, len);
11797 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11798 before_ins = *iter;
11799 gtk_text_iter_backward_chars(&before_ins, len);
11800 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11803 /* check if the preceding is just whitespace or quote */
11804 GtkTextIter start_line;
11805 gchar *tmp = NULL, *quote = NULL;
11806 gint quote_len = 0, is_normal = 0;
11807 start_line = *iter;
11808 gtk_text_iter_set_line_offset(&start_line, 0);
11809 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11812 if (*tmp == '\0') {
11815 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11823 gtk_text_buffer_insert(buffer, iter, text, len);
11825 gtk_text_buffer_insert_with_tags_by_name(buffer,
11826 iter, text, len, "no_join", NULL);
11831 if (!paste_as_quotation) {
11832 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11833 compose_beautify_paragraph(compose, iter, FALSE);
11834 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11835 gtk_text_buffer_delete_mark(buffer, mark);
11838 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11839 G_CALLBACK(text_inserted),
11841 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11843 if (compose_can_autosave(compose) &&
11844 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11845 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11846 compose->draft_timeout_tag = g_timeout_add
11847 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11851 static void compose_check_all(GtkAction *action, gpointer data)
11853 Compose *compose = (Compose *)data;
11854 if (!compose->gtkaspell)
11857 if (gtk_widget_has_focus(compose->subject_entry))
11858 claws_spell_entry_check_all(
11859 CLAWS_SPELL_ENTRY(compose->subject_entry));
11861 gtkaspell_check_all(compose->gtkaspell);
11864 static void compose_highlight_all(GtkAction *action, gpointer data)
11866 Compose *compose = (Compose *)data;
11867 if (compose->gtkaspell) {
11868 claws_spell_entry_recheck_all(
11869 CLAWS_SPELL_ENTRY(compose->subject_entry));
11870 gtkaspell_highlight_all(compose->gtkaspell);
11874 static void compose_check_backwards(GtkAction *action, gpointer data)
11876 Compose *compose = (Compose *)data;
11877 if (!compose->gtkaspell) {
11878 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11882 if (gtk_widget_has_focus(compose->subject_entry))
11883 claws_spell_entry_check_backwards(
11884 CLAWS_SPELL_ENTRY(compose->subject_entry));
11886 gtkaspell_check_backwards(compose->gtkaspell);
11889 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11891 Compose *compose = (Compose *)data;
11892 if (!compose->gtkaspell) {
11893 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11897 if (gtk_widget_has_focus(compose->subject_entry))
11898 claws_spell_entry_check_forwards_go(
11899 CLAWS_SPELL_ENTRY(compose->subject_entry));
11901 gtkaspell_check_forwards_go(compose->gtkaspell);
11906 *\brief Guess originating forward account from MsgInfo and several
11907 * "common preference" settings. Return NULL if no guess.
11909 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11911 PrefsAccount *account = NULL;
11913 cm_return_val_if_fail(msginfo, NULL);
11914 cm_return_val_if_fail(msginfo->folder, NULL);
11915 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11917 if (msginfo->folder->prefs->enable_default_account)
11918 account = account_find_from_id(msginfo->folder->prefs->default_account);
11920 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11922 Xstrdup_a(to, msginfo->to, return NULL);
11923 extract_address(to);
11924 account = account_find_from_address(to, FALSE);
11927 if (!account && prefs_common.forward_account_autosel) {
11929 if (!procheader_get_header_from_msginfo
11930 (msginfo, &cc, "Cc:")) {
11931 gchar *buf = cc + strlen("Cc:");
11932 extract_address(buf);
11933 account = account_find_from_address(buf, FALSE);
11938 if (!account && prefs_common.forward_account_autosel) {
11939 gchar *deliveredto = NULL;
11940 if (!procheader_get_header_from_msginfo
11941 (msginfo, &deliveredto, "Delivered-To:")) {
11942 gchar *buf = deliveredto + strlen("Delivered-To:");
11943 extract_address(buf);
11944 account = account_find_from_address(buf, FALSE);
11945 g_free(deliveredto);
11950 account = msginfo->folder->folder->account;
11955 gboolean compose_close(Compose *compose)
11959 cm_return_val_if_fail(compose, FALSE);
11961 if (!g_mutex_trylock(compose->mutex)) {
11962 /* we have to wait for the (possibly deferred by auto-save)
11963 * drafting to be done, before destroying the compose under
11965 debug_print("waiting for drafting to finish...\n");
11966 compose_allow_user_actions(compose, FALSE);
11967 if (compose->close_timeout_tag == 0) {
11968 compose->close_timeout_tag =
11969 g_timeout_add (500, (GSourceFunc) compose_close,
11975 if (compose->draft_timeout_tag >= 0) {
11976 g_source_remove(compose->draft_timeout_tag);
11977 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11980 gtkut_widget_get_uposition(compose->window, &x, &y);
11981 if (!compose->batch) {
11982 prefs_common.compose_x = x;
11983 prefs_common.compose_y = y;
11985 g_mutex_unlock(compose->mutex);
11986 compose_destroy(compose);
11991 * Add entry field for each address in list.
11992 * \param compose E-Mail composition object.
11993 * \param listAddress List of (formatted) E-Mail addresses.
11995 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11998 node = listAddress;
12000 addr = ( gchar * ) node->data;
12001 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12002 node = g_list_next( node );
12006 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12007 guint action, gboolean opening_multiple)
12009 gchar *body = NULL;
12010 GSList *new_msglist = NULL;
12011 MsgInfo *tmp_msginfo = NULL;
12012 gboolean originally_enc = FALSE;
12013 gboolean originally_sig = FALSE;
12014 Compose *compose = NULL;
12015 gchar *s_system = NULL;
12017 cm_return_if_fail(msgview != NULL);
12019 cm_return_if_fail(msginfo_list != NULL);
12021 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12022 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12023 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12025 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12026 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12027 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12028 orig_msginfo, mimeinfo);
12029 if (tmp_msginfo != NULL) {
12030 new_msglist = g_slist_append(NULL, tmp_msginfo);
12032 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12033 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12034 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12036 tmp_msginfo->folder = orig_msginfo->folder;
12037 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12038 if (orig_msginfo->tags) {
12039 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12040 tmp_msginfo->folder->tags_dirty = TRUE;
12046 if (!opening_multiple)
12047 body = messageview_get_selection(msgview);
12050 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12051 procmsg_msginfo_free(&tmp_msginfo);
12052 g_slist_free(new_msglist);
12054 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12056 if (compose && originally_enc) {
12057 compose_force_encryption(compose, compose->account, FALSE, s_system);
12060 if (compose && originally_sig && compose->account->default_sign_reply) {
12061 compose_force_signing(compose, compose->account, s_system);
12065 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12068 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12071 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12072 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12073 GSList *cur = msginfo_list;
12074 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12075 "messages. Opening the windows "
12076 "could take some time. Do you "
12077 "want to continue?"),
12078 g_slist_length(msginfo_list));
12079 if (g_slist_length(msginfo_list) > 9
12080 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12081 != G_ALERTALTERNATE) {
12086 /* We'll open multiple compose windows */
12087 /* let the WM place the next windows */
12088 compose_force_window_origin = FALSE;
12089 for (; cur; cur = cur->next) {
12091 tmplist.data = cur->data;
12092 tmplist.next = NULL;
12093 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12095 compose_force_window_origin = TRUE;
12097 /* forwarding multiple mails as attachments is done via a
12098 * single compose window */
12099 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12103 void compose_check_for_email_account(Compose *compose)
12105 PrefsAccount *ac = NULL, *curr = NULL;
12111 if (compose->account && compose->account->protocol == A_NNTP) {
12112 ac = account_get_cur_account();
12113 if (ac->protocol == A_NNTP) {
12114 list = account_get_list();
12116 for( ; list != NULL ; list = g_list_next(list)) {
12117 curr = (PrefsAccount *) list->data;
12118 if (curr->protocol != A_NNTP) {
12124 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12129 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12130 const gchar *address)
12132 GSList *msginfo_list = NULL;
12133 gchar *body = messageview_get_selection(msgview);
12136 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12138 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12139 compose_check_for_email_account(compose);
12140 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12141 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12142 compose_reply_set_subject(compose, msginfo);
12145 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12148 void compose_set_position(Compose *compose, gint pos)
12150 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12152 gtkut_text_view_set_position(text, pos);
12155 gboolean compose_search_string(Compose *compose,
12156 const gchar *str, gboolean case_sens)
12158 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12160 return gtkut_text_view_search_string(text, str, case_sens);
12163 gboolean compose_search_string_backward(Compose *compose,
12164 const gchar *str, gboolean case_sens)
12166 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12168 return gtkut_text_view_search_string_backward(text, str, case_sens);
12171 /* allocate a msginfo structure and populate its data from a compose data structure */
12172 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12174 MsgInfo *newmsginfo;
12176 gchar date[RFC822_DATE_BUFFSIZE];
12178 cm_return_val_if_fail( compose != NULL, NULL );
12180 newmsginfo = procmsg_msginfo_new();
12183 get_rfc822_date(date, sizeof(date));
12184 newmsginfo->date = g_strdup(date);
12187 if (compose->from_name) {
12188 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12189 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12193 if (compose->subject_entry)
12194 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12196 /* to, cc, reply-to, newsgroups */
12197 for (list = compose->header_list; list; list = list->next) {
12198 gchar *header = gtk_editable_get_chars(
12200 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12201 gchar *entry = gtk_editable_get_chars(
12202 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12204 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12205 if ( newmsginfo->to == NULL ) {
12206 newmsginfo->to = g_strdup(entry);
12207 } else if (entry && *entry) {
12208 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12209 g_free(newmsginfo->to);
12210 newmsginfo->to = tmp;
12213 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12214 if ( newmsginfo->cc == NULL ) {
12215 newmsginfo->cc = g_strdup(entry);
12216 } else if (entry && *entry) {
12217 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12218 g_free(newmsginfo->cc);
12219 newmsginfo->cc = tmp;
12222 if ( strcasecmp(header,
12223 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12224 if ( newmsginfo->newsgroups == NULL ) {
12225 newmsginfo->newsgroups = g_strdup(entry);
12226 } else if (entry && *entry) {
12227 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12228 g_free(newmsginfo->newsgroups);
12229 newmsginfo->newsgroups = tmp;
12237 /* other data is unset */
12243 /* update compose's dictionaries from folder dict settings */
12244 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12245 FolderItem *folder_item)
12247 cm_return_if_fail(compose != NULL);
12249 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12250 FolderItemPrefs *prefs = folder_item->prefs;
12252 if (prefs->enable_default_dictionary)
12253 gtkaspell_change_dict(compose->gtkaspell,
12254 prefs->default_dictionary, FALSE);
12255 if (folder_item->prefs->enable_default_alt_dictionary)
12256 gtkaspell_change_alt_dict(compose->gtkaspell,
12257 prefs->default_alt_dictionary);
12258 if (prefs->enable_default_dictionary
12259 || prefs->enable_default_alt_dictionary)
12260 compose_spell_menu_changed(compose);
12265 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12267 Compose *compose = (Compose *)data;
12269 cm_return_if_fail(compose != NULL);
12271 gtk_widget_grab_focus(compose->text);
12274 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12276 gtk_combo_box_popup(GTK_COMBO_BOX(data));