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);
8535 if (compose->toolbar->privacy_sign_btn != NULL) {
8536 gtk_widget_set_sensitive(
8537 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8540 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8541 gtk_widget_set_sensitive(
8542 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8547 static void compose_set_out_encoding(Compose *compose)
8549 CharSet out_encoding;
8550 const gchar *branch = NULL;
8551 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8553 switch(out_encoding) {
8554 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8555 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8556 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8557 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8558 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8559 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8560 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8561 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8562 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8563 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8564 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8565 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8566 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8567 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8568 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8569 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8570 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8571 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8572 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8573 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8574 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8575 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8576 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8577 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8578 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8579 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8580 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8581 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8582 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8583 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8584 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8585 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8586 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8587 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8589 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8592 static void compose_set_template_menu(Compose *compose)
8594 GSList *tmpl_list, *cur;
8598 tmpl_list = template_get_config();
8600 menu = gtk_menu_new();
8602 gtk_menu_set_accel_group (GTK_MENU (menu),
8603 gtk_ui_manager_get_accel_group(compose->ui_manager));
8604 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8605 Template *tmpl = (Template *)cur->data;
8606 gchar *accel_path = NULL;
8607 item = gtk_menu_item_new_with_label(tmpl->name);
8608 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8609 g_signal_connect(G_OBJECT(item), "activate",
8610 G_CALLBACK(compose_template_activate_cb),
8612 g_object_set_data(G_OBJECT(item), "template", tmpl);
8613 gtk_widget_show(item);
8614 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8615 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8619 gtk_widget_show(menu);
8620 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8623 void compose_update_actions_menu(Compose *compose)
8625 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8628 static void compose_update_privacy_systems_menu(Compose *compose)
8630 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8631 GSList *systems, *cur;
8633 GtkWidget *system_none;
8635 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8636 GtkWidget *privacy_menu = gtk_menu_new();
8638 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8639 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8641 g_signal_connect(G_OBJECT(system_none), "activate",
8642 G_CALLBACK(compose_set_privacy_system_cb), compose);
8644 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8645 gtk_widget_show(system_none);
8647 systems = privacy_get_system_ids();
8648 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8649 gchar *systemid = cur->data;
8651 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8652 widget = gtk_radio_menu_item_new_with_label(group,
8653 privacy_system_get_name(systemid));
8654 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8655 g_strdup(systemid), g_free);
8656 g_signal_connect(G_OBJECT(widget), "activate",
8657 G_CALLBACK(compose_set_privacy_system_cb), compose);
8659 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8660 gtk_widget_show(widget);
8663 g_slist_free(systems);
8664 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8665 gtk_widget_show_all(privacy_menu);
8666 gtk_widget_show_all(privacy_menuitem);
8669 void compose_reflect_prefs_all(void)
8674 for (cur = compose_list; cur != NULL; cur = cur->next) {
8675 compose = (Compose *)cur->data;
8676 compose_set_template_menu(compose);
8680 void compose_reflect_prefs_pixmap_theme(void)
8685 for (cur = compose_list; cur != NULL; cur = cur->next) {
8686 compose = (Compose *)cur->data;
8687 toolbar_update(TOOLBAR_COMPOSE, compose);
8691 static const gchar *compose_quote_char_from_context(Compose *compose)
8693 const gchar *qmark = NULL;
8695 cm_return_val_if_fail(compose != NULL, NULL);
8697 switch (compose->mode) {
8698 /* use forward-specific quote char */
8699 case COMPOSE_FORWARD:
8700 case COMPOSE_FORWARD_AS_ATTACH:
8701 case COMPOSE_FORWARD_INLINE:
8702 if (compose->folder && compose->folder->prefs &&
8703 compose->folder->prefs->forward_with_format)
8704 qmark = compose->folder->prefs->forward_quotemark;
8705 else if (compose->account->forward_with_format)
8706 qmark = compose->account->forward_quotemark;
8708 qmark = prefs_common.fw_quotemark;
8711 /* use reply-specific quote char in all other modes */
8713 if (compose->folder && compose->folder->prefs &&
8714 compose->folder->prefs->reply_with_format)
8715 qmark = compose->folder->prefs->reply_quotemark;
8716 else if (compose->account->reply_with_format)
8717 qmark = compose->account->reply_quotemark;
8719 qmark = prefs_common.quotemark;
8723 if (qmark == NULL || *qmark == '\0')
8729 static void compose_template_apply(Compose *compose, Template *tmpl,
8733 GtkTextBuffer *buffer;
8737 gchar *parsed_str = NULL;
8738 gint cursor_pos = 0;
8739 const gchar *err_msg = _("The body of the template has an error at line %d.");
8742 /* process the body */
8744 text = GTK_TEXT_VIEW(compose->text);
8745 buffer = gtk_text_view_get_buffer(text);
8748 qmark = compose_quote_char_from_context(compose);
8750 if (compose->replyinfo != 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->replyinfo,
8758 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8760 } else if (compose->fwdinfo != NULL) {
8763 gtk_text_buffer_set_text(buffer, "", -1);
8764 mark = gtk_text_buffer_get_insert(buffer);
8765 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8767 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8768 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8771 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8773 GtkTextIter start, end;
8776 gtk_text_buffer_get_start_iter(buffer, &start);
8777 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8778 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8780 /* clear the buffer now */
8782 gtk_text_buffer_set_text(buffer, "", -1);
8784 parsed_str = compose_quote_fmt(compose, dummyinfo,
8785 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8786 procmsg_msginfo_free( &dummyinfo );
8792 gtk_text_buffer_set_text(buffer, "", -1);
8793 mark = gtk_text_buffer_get_insert(buffer);
8794 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8797 if (replace && parsed_str && compose->account->auto_sig)
8798 compose_insert_sig(compose, FALSE);
8800 if (replace && parsed_str) {
8801 gtk_text_buffer_get_start_iter(buffer, &iter);
8802 gtk_text_buffer_place_cursor(buffer, &iter);
8806 cursor_pos = quote_fmt_get_cursor_pos();
8807 compose->set_cursor_pos = cursor_pos;
8808 if (cursor_pos == -1)
8810 gtk_text_buffer_get_start_iter(buffer, &iter);
8811 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8812 gtk_text_buffer_place_cursor(buffer, &iter);
8815 /* process the other fields */
8817 compose_template_apply_fields(compose, tmpl);
8818 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8819 quote_fmt_reset_vartable();
8820 compose_changed_cb(NULL, compose);
8823 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8824 gtkaspell_highlight_all(compose->gtkaspell);
8828 static void compose_template_apply_fields_error(const gchar *header)
8833 tr = g_strdup(C_("'%s' stands for a header name",
8834 "Template '%s' format error."));
8835 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8836 alertpanel_error("%s", text);
8842 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8844 MsgInfo* dummyinfo = NULL;
8845 MsgInfo *msginfo = NULL;
8848 if (compose->replyinfo != NULL)
8849 msginfo = compose->replyinfo;
8850 else if (compose->fwdinfo != NULL)
8851 msginfo = compose->fwdinfo;
8853 dummyinfo = compose_msginfo_new_from_compose(compose);
8854 msginfo = dummyinfo;
8857 if (tmpl->from && *tmpl->from != '\0') {
8859 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8860 compose->gtkaspell);
8862 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8864 quote_fmt_scan_string(tmpl->from);
8867 buf = quote_fmt_get_buffer();
8869 compose_template_apply_fields_error("From");
8871 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8875 if (tmpl->to && *tmpl->to != '\0') {
8877 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8878 compose->gtkaspell);
8880 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8882 quote_fmt_scan_string(tmpl->to);
8885 buf = quote_fmt_get_buffer();
8887 compose_template_apply_fields_error("To");
8889 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8893 if (tmpl->cc && *tmpl->cc != '\0') {
8895 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8896 compose->gtkaspell);
8898 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8900 quote_fmt_scan_string(tmpl->cc);
8903 buf = quote_fmt_get_buffer();
8905 compose_template_apply_fields_error("Cc");
8907 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8911 if (tmpl->bcc && *tmpl->bcc != '\0') {
8913 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8914 compose->gtkaspell);
8916 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8918 quote_fmt_scan_string(tmpl->bcc);
8921 buf = quote_fmt_get_buffer();
8923 compose_template_apply_fields_error("Bcc");
8925 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8929 if (tmpl->replyto && *tmpl->replyto != '\0') {
8931 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8932 compose->gtkaspell);
8934 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8936 quote_fmt_scan_string(tmpl->replyto);
8939 buf = quote_fmt_get_buffer();
8941 compose_template_apply_fields_error("Reply-To");
8943 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8947 /* process the subject */
8948 if (tmpl->subject && *tmpl->subject != '\0') {
8950 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8951 compose->gtkaspell);
8953 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8955 quote_fmt_scan_string(tmpl->subject);
8958 buf = quote_fmt_get_buffer();
8960 compose_template_apply_fields_error("Subject");
8962 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8966 procmsg_msginfo_free( &dummyinfo );
8969 static void compose_destroy(Compose *compose)
8971 GtkAllocation allocation;
8972 GtkTextBuffer *buffer;
8973 GtkClipboard *clipboard;
8975 compose_list = g_list_remove(compose_list, compose);
8977 if (compose->updating) {
8978 debug_print("danger, not destroying anything now\n");
8979 compose->deferred_destroy = TRUE;
8983 /* NOTE: address_completion_end() does nothing with the window
8984 * however this may change. */
8985 address_completion_end(compose->window);
8987 slist_free_strings_full(compose->to_list);
8988 slist_free_strings_full(compose->newsgroup_list);
8989 slist_free_strings_full(compose->header_list);
8991 slist_free_strings_full(extra_headers);
8992 extra_headers = NULL;
8994 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8996 g_hash_table_destroy(compose->email_hashtable);
8998 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8999 compose->folder_update_callback_id);
9001 procmsg_msginfo_free(&(compose->targetinfo));
9002 procmsg_msginfo_free(&(compose->replyinfo));
9003 procmsg_msginfo_free(&(compose->fwdinfo));
9005 g_free(compose->replyto);
9006 g_free(compose->cc);
9007 g_free(compose->bcc);
9008 g_free(compose->newsgroups);
9009 g_free(compose->followup_to);
9011 g_free(compose->ml_post);
9013 g_free(compose->inreplyto);
9014 g_free(compose->references);
9015 g_free(compose->msgid);
9016 g_free(compose->boundary);
9018 g_free(compose->redirect_filename);
9019 if (compose->undostruct)
9020 undo_destroy(compose->undostruct);
9022 g_free(compose->sig_str);
9024 g_free(compose->exteditor_file);
9026 g_free(compose->orig_charset);
9028 g_free(compose->privacy_system);
9029 g_free(compose->encdata);
9031 #ifndef USE_ALT_ADDRBOOK
9032 if (addressbook_get_target_compose() == compose)
9033 addressbook_set_target_compose(NULL);
9036 if (compose->gtkaspell) {
9037 gtkaspell_delete(compose->gtkaspell);
9038 compose->gtkaspell = NULL;
9042 if (!compose->batch) {
9043 gtk_widget_get_allocation(compose->window, &allocation);
9044 prefs_common.compose_width = allocation.width;
9045 prefs_common.compose_height = allocation.height;
9048 if (!gtk_widget_get_parent(compose->paned))
9049 gtk_widget_destroy(compose->paned);
9050 gtk_widget_destroy(compose->popupmenu);
9052 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9053 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9054 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9056 gtk_widget_destroy(compose->window);
9057 toolbar_destroy(compose->toolbar);
9058 g_free(compose->toolbar);
9059 cm_mutex_free(compose->mutex);
9063 static void compose_attach_info_free(AttachInfo *ainfo)
9065 g_free(ainfo->file);
9066 g_free(ainfo->content_type);
9067 g_free(ainfo->name);
9068 g_free(ainfo->charset);
9072 static void compose_attach_update_label(Compose *compose)
9077 GtkTreeModel *model;
9081 if (compose == NULL)
9084 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9085 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9086 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9090 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9091 total_size = ainfo->size;
9092 while(gtk_tree_model_iter_next(model, &iter)) {
9093 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9094 total_size += ainfo->size;
9097 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9098 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9102 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9104 Compose *compose = (Compose *)data;
9105 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9106 GtkTreeSelection *selection;
9108 GtkTreeModel *model;
9110 selection = gtk_tree_view_get_selection(tree_view);
9111 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9116 for (cur = sel; cur != NULL; cur = cur->next) {
9117 GtkTreePath *path = cur->data;
9118 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9121 gtk_tree_path_free(path);
9124 for (cur = sel; cur != NULL; cur = cur->next) {
9125 GtkTreeRowReference *ref = cur->data;
9126 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9129 if (gtk_tree_model_get_iter(model, &iter, path))
9130 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9132 gtk_tree_path_free(path);
9133 gtk_tree_row_reference_free(ref);
9137 compose_attach_update_label(compose);
9140 static struct _AttachProperty
9143 GtkWidget *mimetype_entry;
9144 GtkWidget *encoding_optmenu;
9145 GtkWidget *path_entry;
9146 GtkWidget *filename_entry;
9148 GtkWidget *cancel_btn;
9151 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9153 gtk_tree_path_free((GtkTreePath *)ptr);
9156 static void compose_attach_property(GtkAction *action, gpointer data)
9158 Compose *compose = (Compose *)data;
9159 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9161 GtkComboBox *optmenu;
9162 GtkTreeSelection *selection;
9164 GtkTreeModel *model;
9167 static gboolean cancelled;
9169 /* only if one selected */
9170 selection = gtk_tree_view_get_selection(tree_view);
9171 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9174 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9178 path = (GtkTreePath *) sel->data;
9179 gtk_tree_model_get_iter(model, &iter, path);
9180 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9183 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9189 if (!attach_prop.window)
9190 compose_attach_property_create(&cancelled);
9191 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9192 gtk_widget_grab_focus(attach_prop.ok_btn);
9193 gtk_widget_show(attach_prop.window);
9194 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9195 GTK_WINDOW(compose->window));
9197 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9198 if (ainfo->encoding == ENC_UNKNOWN)
9199 combobox_select_by_data(optmenu, ENC_BASE64);
9201 combobox_select_by_data(optmenu, ainfo->encoding);
9203 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9204 ainfo->content_type ? ainfo->content_type : "");
9205 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9206 ainfo->file ? ainfo->file : "");
9207 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9208 ainfo->name ? ainfo->name : "");
9211 const gchar *entry_text;
9213 gchar *cnttype = NULL;
9220 gtk_widget_hide(attach_prop.window);
9221 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9226 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9227 if (*entry_text != '\0') {
9230 text = g_strstrip(g_strdup(entry_text));
9231 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9232 cnttype = g_strdup(text);
9235 alertpanel_error(_("Invalid MIME type."));
9241 ainfo->encoding = combobox_get_active_data(optmenu);
9243 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9244 if (*entry_text != '\0') {
9245 if (is_file_exist(entry_text) &&
9246 (size = get_file_size(entry_text)) > 0)
9247 file = g_strdup(entry_text);
9250 (_("File doesn't exist or is empty."));
9256 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9257 if (*entry_text != '\0') {
9258 g_free(ainfo->name);
9259 ainfo->name = g_strdup(entry_text);
9263 g_free(ainfo->content_type);
9264 ainfo->content_type = cnttype;
9267 g_free(ainfo->file);
9271 ainfo->size = (goffset)size;
9273 /* update tree store */
9274 text = to_human_readable(ainfo->size);
9275 gtk_tree_model_get_iter(model, &iter, path);
9276 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9277 COL_MIMETYPE, ainfo->content_type,
9279 COL_NAME, ainfo->name,
9280 COL_CHARSET, ainfo->charset,
9286 gtk_tree_path_free(path);
9289 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9291 label = gtk_label_new(str); \
9292 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9293 GTK_FILL, 0, 0, 0); \
9294 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9296 entry = gtk_entry_new(); \
9297 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9298 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9301 static void compose_attach_property_create(gboolean *cancelled)
9307 GtkWidget *mimetype_entry;
9310 GtkListStore *optmenu_menu;
9311 GtkWidget *path_entry;
9312 GtkWidget *filename_entry;
9315 GtkWidget *cancel_btn;
9316 GList *mime_type_list, *strlist;
9319 debug_print("Creating attach_property window...\n");
9321 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9322 gtk_widget_set_size_request(window, 480, -1);
9323 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9324 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9325 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9326 g_signal_connect(G_OBJECT(window), "delete_event",
9327 G_CALLBACK(attach_property_delete_event),
9329 g_signal_connect(G_OBJECT(window), "key_press_event",
9330 G_CALLBACK(attach_property_key_pressed),
9333 vbox = gtk_vbox_new(FALSE, 8);
9334 gtk_container_add(GTK_CONTAINER(window), vbox);
9336 table = gtk_table_new(4, 2, FALSE);
9337 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9338 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9339 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9341 label = gtk_label_new(_("MIME type"));
9342 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9344 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9345 #if !GTK_CHECK_VERSION(2, 24, 0)
9346 mimetype_entry = gtk_combo_box_entry_new_text();
9348 mimetype_entry = gtk_combo_box_text_new_with_entry();
9350 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9351 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9353 /* stuff with list */
9354 mime_type_list = procmime_get_mime_type_list();
9356 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9357 MimeType *type = (MimeType *) mime_type_list->data;
9360 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9362 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9365 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9366 (GCompareFunc)strcmp2);
9369 for (mime_type_list = strlist; mime_type_list != NULL;
9370 mime_type_list = mime_type_list->next) {
9371 #if !GTK_CHECK_VERSION(2, 24, 0)
9372 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9374 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9376 g_free(mime_type_list->data);
9378 g_list_free(strlist);
9379 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9380 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9382 label = gtk_label_new(_("Encoding"));
9383 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9385 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9387 hbox = gtk_hbox_new(FALSE, 0);
9388 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9389 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9391 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9392 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9394 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9395 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9396 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9397 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9398 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9400 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9402 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9403 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9405 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9406 &ok_btn, GTK_STOCK_OK,
9408 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9409 gtk_widget_grab_default(ok_btn);
9411 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9412 G_CALLBACK(attach_property_ok),
9414 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9415 G_CALLBACK(attach_property_cancel),
9418 gtk_widget_show_all(vbox);
9420 attach_prop.window = window;
9421 attach_prop.mimetype_entry = mimetype_entry;
9422 attach_prop.encoding_optmenu = optmenu;
9423 attach_prop.path_entry = path_entry;
9424 attach_prop.filename_entry = filename_entry;
9425 attach_prop.ok_btn = ok_btn;
9426 attach_prop.cancel_btn = cancel_btn;
9429 #undef SET_LABEL_AND_ENTRY
9431 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9437 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9443 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9444 gboolean *cancelled)
9452 static gboolean attach_property_key_pressed(GtkWidget *widget,
9454 gboolean *cancelled)
9456 if (event && event->keyval == GDK_KEY_Escape) {
9460 if (event && event->keyval == GDK_KEY_Return) {
9468 static void compose_exec_ext_editor(Compose *compose)
9473 GdkNativeWindow socket_wid = 0;
9477 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9478 G_DIR_SEPARATOR, compose);
9480 if (compose_get_ext_editor_uses_socket()) {
9481 /* Only allow one socket */
9482 if (compose->exteditor_socket != NULL) {
9483 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9484 /* Move the focus off of the socket */
9485 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9490 /* Create the receiving GtkSocket */
9491 socket = gtk_socket_new ();
9492 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9493 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9495 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9496 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9497 /* Realize the socket so that we can use its ID */
9498 gtk_widget_realize(socket);
9499 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9500 compose->exteditor_socket = socket;
9503 if (pipe(pipe_fds) < 0) {
9509 if ((pid = fork()) < 0) {
9516 /* close the write side of the pipe */
9519 compose->exteditor_file = g_strdup(tmp);
9520 compose->exteditor_pid = pid;
9522 compose_set_ext_editor_sensitive(compose, FALSE);
9525 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9527 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9529 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9533 } else { /* process-monitoring process */
9539 /* close the read side of the pipe */
9542 if (compose_write_body_to_file(compose, tmp) < 0) {
9543 fd_write_all(pipe_fds[1], "2\n", 2);
9547 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9549 fd_write_all(pipe_fds[1], "1\n", 2);
9553 /* wait until editor is terminated */
9554 waitpid(pid_ed, NULL, 0);
9556 fd_write_all(pipe_fds[1], "0\n", 2);
9563 #endif /* G_OS_UNIX */
9566 static gboolean compose_can_autosave(Compose *compose)
9568 if (compose->privacy_system && compose->use_encryption)
9569 return prefs_common.autosave && prefs_common.autosave_encrypted;
9571 return prefs_common.autosave;
9575 static gboolean compose_get_ext_editor_cmd_valid()
9577 gboolean has_s = FALSE;
9578 gboolean has_w = FALSE;
9579 const gchar *p = prefs_common_get_ext_editor_cmd();
9582 while ((p = strchr(p, '%'))) {
9588 } else if (*p == 'w') {
9599 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9606 cm_return_val_if_fail(file != NULL, -1);
9608 if ((pid = fork()) < 0) {
9613 if (pid != 0) return pid;
9615 /* grandchild process */
9617 if (setpgid(0, getppid()))
9620 if (compose_get_ext_editor_cmd_valid()) {
9621 if (compose_get_ext_editor_uses_socket()) {
9622 p = g_strdup(prefs_common_get_ext_editor_cmd());
9623 s = strstr(p, "%w");
9625 if (strstr(p, "%s") < s)
9626 buf = g_strdup_printf(p, file, socket_wid);
9628 buf = g_strdup_printf(p, socket_wid, file);
9631 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9634 if (prefs_common_get_ext_editor_cmd())
9635 g_warning("External editor command-line is invalid: '%s'",
9636 prefs_common_get_ext_editor_cmd());
9637 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9640 cmdline = strsplit_with_quote(buf, " ", 0);
9642 execvp(cmdline[0], cmdline);
9645 g_strfreev(cmdline);
9650 static gboolean compose_ext_editor_kill(Compose *compose)
9652 pid_t pgid = compose->exteditor_pid * -1;
9655 ret = kill(pgid, 0);
9657 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9661 msg = g_strdup_printf
9662 (_("The external editor is still working.\n"
9663 "Force terminating the process?\n"
9664 "process group id: %d"), -pgid);
9665 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9666 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9670 if (val == G_ALERTALTERNATE) {
9671 g_source_remove(compose->exteditor_tag);
9672 g_io_channel_shutdown(compose->exteditor_ch,
9674 g_io_channel_unref(compose->exteditor_ch);
9676 if (kill(pgid, SIGTERM) < 0) perror("kill");
9677 waitpid(compose->exteditor_pid, NULL, 0);
9679 g_warning("Terminated process group id: %d. "
9680 "Temporary file: %s", -pgid, compose->exteditor_file);
9682 compose_set_ext_editor_sensitive(compose, TRUE);
9684 g_free(compose->exteditor_file);
9685 compose->exteditor_file = NULL;
9686 compose->exteditor_pid = -1;
9687 compose->exteditor_ch = NULL;
9688 compose->exteditor_tag = -1;
9696 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9700 Compose *compose = (Compose *)data;
9703 debug_print("Compose: input from monitoring process\n");
9705 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9710 g_io_channel_shutdown(source, FALSE, NULL);
9711 g_io_channel_unref(source);
9713 waitpid(compose->exteditor_pid, NULL, 0);
9715 if (buf[0] == '0') { /* success */
9716 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9717 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9718 GtkTextIter start, end;
9721 gtk_text_buffer_set_text(buffer, "", -1);
9722 compose_insert_file(compose, compose->exteditor_file);
9723 compose_changed_cb(NULL, compose);
9725 /* Check if we should save the draft or not */
9726 if (compose_can_autosave(compose))
9727 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9729 if (claws_unlink(compose->exteditor_file) < 0)
9730 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9732 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9733 gtk_text_buffer_get_start_iter(buffer, &start);
9734 gtk_text_buffer_get_end_iter(buffer, &end);
9735 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9736 if (chars && strlen(chars) > 0)
9737 compose->modified = TRUE;
9739 } else if (buf[0] == '1') { /* failed */
9740 g_warning("Couldn't exec external editor");
9741 if (claws_unlink(compose->exteditor_file) < 0)
9742 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9743 } else if (buf[0] == '2') {
9744 g_warning("Couldn't write to file");
9745 } else if (buf[0] == '3') {
9746 g_warning("Pipe read failed");
9749 compose_set_ext_editor_sensitive(compose, TRUE);
9751 g_free(compose->exteditor_file);
9752 compose->exteditor_file = NULL;
9753 compose->exteditor_pid = -1;
9754 compose->exteditor_ch = NULL;
9755 compose->exteditor_tag = -1;
9756 if (compose->exteditor_socket) {
9757 gtk_widget_destroy(compose->exteditor_socket);
9758 compose->exteditor_socket = NULL;
9765 static char *ext_editor_menu_entries[] = {
9766 "Menu/Message/Send",
9767 "Menu/Message/SendLater",
9768 "Menu/Message/InsertFile",
9769 "Menu/Message/InsertSig",
9770 "Menu/Message/ReplaceSig",
9771 "Menu/Message/Save",
9772 "Menu/Message/Print",
9777 "Menu/Tools/ShowRuler",
9778 "Menu/Tools/Actions",
9783 static void compose_set_ext_editor_sensitive(Compose *compose,
9788 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9789 cm_menu_set_sensitive_full(compose->ui_manager,
9790 ext_editor_menu_entries[i], sensitive);
9793 if (compose_get_ext_editor_uses_socket()) {
9795 if (compose->exteditor_socket)
9796 gtk_widget_hide(compose->exteditor_socket);
9797 gtk_widget_show(compose->scrolledwin);
9798 if (prefs_common.show_ruler)
9799 gtk_widget_show(compose->ruler_hbox);
9800 /* Fix the focus, as it doesn't go anywhere when the
9801 * socket is hidden or destroyed */
9802 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9804 g_assert (compose->exteditor_socket != NULL);
9805 /* Fix the focus, as it doesn't go anywhere when the
9806 * edit box is hidden */
9807 if (gtk_widget_is_focus(compose->text))
9808 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9809 gtk_widget_hide(compose->scrolledwin);
9810 gtk_widget_hide(compose->ruler_hbox);
9811 gtk_widget_show(compose->exteditor_socket);
9814 gtk_widget_set_sensitive(compose->text, sensitive);
9816 if (compose->toolbar->send_btn)
9817 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9818 if (compose->toolbar->sendl_btn)
9819 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9820 if (compose->toolbar->draft_btn)
9821 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9822 if (compose->toolbar->insert_btn)
9823 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9824 if (compose->toolbar->sig_btn)
9825 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9826 if (compose->toolbar->exteditor_btn)
9827 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9828 if (compose->toolbar->linewrap_current_btn)
9829 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9830 if (compose->toolbar->linewrap_all_btn)
9831 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9834 static gboolean compose_get_ext_editor_uses_socket()
9836 return (prefs_common_get_ext_editor_cmd() &&
9837 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9840 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9842 compose->exteditor_socket = NULL;
9843 /* returning FALSE allows destruction of the socket */
9846 #endif /* G_OS_UNIX */
9849 * compose_undo_state_changed:
9851 * Change the sensivity of the menuentries undo and redo
9853 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9854 gint redo_state, gpointer data)
9856 Compose *compose = (Compose *)data;
9858 switch (undo_state) {
9859 case UNDO_STATE_TRUE:
9860 if (!undostruct->undo_state) {
9861 undostruct->undo_state = TRUE;
9862 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9865 case UNDO_STATE_FALSE:
9866 if (undostruct->undo_state) {
9867 undostruct->undo_state = FALSE;
9868 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9871 case UNDO_STATE_UNCHANGED:
9873 case UNDO_STATE_REFRESH:
9874 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9877 g_warning("Undo state not recognized");
9881 switch (redo_state) {
9882 case UNDO_STATE_TRUE:
9883 if (!undostruct->redo_state) {
9884 undostruct->redo_state = TRUE;
9885 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9888 case UNDO_STATE_FALSE:
9889 if (undostruct->redo_state) {
9890 undostruct->redo_state = FALSE;
9891 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9894 case UNDO_STATE_UNCHANGED:
9896 case UNDO_STATE_REFRESH:
9897 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9900 g_warning("Redo state not recognized");
9905 /* callback functions */
9907 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9908 GtkAllocation *allocation,
9911 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9914 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9915 * includes "non-client" (windows-izm) in calculation, so this calculation
9916 * may not be accurate.
9918 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9919 GtkAllocation *allocation,
9920 GtkSHRuler *shruler)
9922 if (prefs_common.show_ruler) {
9923 gint char_width = 0, char_height = 0;
9924 gint line_width_in_chars;
9926 gtkut_get_font_size(GTK_WIDGET(widget),
9927 &char_width, &char_height);
9928 line_width_in_chars =
9929 (allocation->width - allocation->x) / char_width;
9931 /* got the maximum */
9932 gtk_shruler_set_range(GTK_SHRULER(shruler),
9933 0.0, line_width_in_chars, 0);
9942 ComposePrefType type;
9943 gboolean entry_marked;
9946 static void account_activated(GtkComboBox *optmenu, gpointer data)
9948 Compose *compose = (Compose *)data;
9951 gchar *folderidentifier;
9952 gint account_id = 0;
9955 GSList *list, *saved_list = NULL;
9956 HeaderEntryState *state;
9958 /* Get ID of active account in the combo box */
9959 menu = gtk_combo_box_get_model(optmenu);
9960 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9961 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9963 ac = account_find_from_id(account_id);
9964 cm_return_if_fail(ac != NULL);
9966 if (ac != compose->account) {
9967 compose_select_account(compose, ac, FALSE);
9969 for (list = compose->header_list; list; list = list->next) {
9970 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9972 if (hentry->type == PREF_ACCOUNT || !list->next) {
9973 compose_destroy_headerentry(compose, hentry);
9976 state = g_malloc0(sizeof(HeaderEntryState));
9977 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9978 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9979 state->entry = gtk_editable_get_chars(
9980 GTK_EDITABLE(hentry->entry), 0, -1);
9981 state->type = hentry->type;
9983 saved_list = g_slist_append(saved_list, state);
9984 compose_destroy_headerentry(compose, hentry);
9987 compose->header_last = NULL;
9988 g_slist_free(compose->header_list);
9989 compose->header_list = NULL;
9990 compose->header_nextrow = 1;
9991 compose_create_header_entry(compose);
9993 if (ac->set_autocc && ac->auto_cc)
9994 compose_entry_append(compose, ac->auto_cc,
9995 COMPOSE_CC, PREF_ACCOUNT);
9996 if (ac->set_autobcc && ac->auto_bcc)
9997 compose_entry_append(compose, ac->auto_bcc,
9998 COMPOSE_BCC, PREF_ACCOUNT);
9999 if (ac->set_autoreplyto && ac->auto_replyto)
10000 compose_entry_append(compose, ac->auto_replyto,
10001 COMPOSE_REPLYTO, PREF_ACCOUNT);
10003 for (list = saved_list; list; list = list->next) {
10004 state = (HeaderEntryState *) list->data;
10006 compose_add_header_entry(compose, state->header,
10007 state->entry, state->type);
10009 g_free(state->header);
10010 g_free(state->entry);
10013 g_slist_free(saved_list);
10015 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10016 (ac->protocol == A_NNTP) ?
10017 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10020 /* Set message save folder */
10021 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10022 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10024 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10025 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10027 compose_set_save_to(compose, NULL);
10028 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10029 folderidentifier = folder_item_get_identifier(account_get_special_folder
10030 (compose->account, F_OUTBOX));
10031 compose_set_save_to(compose, folderidentifier);
10032 g_free(folderidentifier);
10036 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10037 GtkTreeViewColumn *column, Compose *compose)
10039 compose_attach_property(NULL, compose);
10042 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10045 Compose *compose = (Compose *)data;
10046 GtkTreeSelection *attach_selection;
10047 gint attach_nr_selected;
10050 if (!event) return FALSE;
10052 if (event->button == 3) {
10053 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10054 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10056 /* If no rows, or just one row is selected, right-click should
10057 * open menu relevant to the row being right-clicked on. We
10058 * achieve that by selecting the clicked row first. If more
10059 * than one row is selected, we shouldn't modify the selection,
10060 * as user may want to remove selected rows (attachments). */
10061 if (attach_nr_selected < 2) {
10062 gtk_tree_selection_unselect_all(attach_selection);
10063 attach_nr_selected = 0;
10064 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10065 event->x, event->y, &path, NULL, NULL, NULL);
10066 if (path != NULL) {
10067 gtk_tree_selection_select_path(attach_selection, path);
10068 gtk_tree_path_free(path);
10069 attach_nr_selected++;
10073 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10074 /* Properties menu item makes no sense with more than one row
10075 * selected, the properties dialog can only edit one attachment. */
10076 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10078 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10079 NULL, NULL, event->button, event->time);
10086 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10089 Compose *compose = (Compose *)data;
10091 if (!event) return FALSE;
10093 switch (event->keyval) {
10094 case GDK_KEY_Delete:
10095 compose_attach_remove_selected(NULL, compose);
10101 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10103 toolbar_comp_set_sensitive(compose, allow);
10104 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10105 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10107 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10109 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10110 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10111 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10113 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10117 static void compose_send_cb(GtkAction *action, gpointer data)
10119 Compose *compose = (Compose *)data;
10122 if (compose->exteditor_tag != -1) {
10123 debug_print("ignoring send: external editor still open\n");
10127 if (prefs_common.work_offline &&
10128 !inc_offline_should_override(TRUE,
10129 _("Claws Mail needs network access in order "
10130 "to send this email.")))
10133 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10134 g_source_remove(compose->draft_timeout_tag);
10135 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10138 compose_send(compose);
10141 static void compose_send_later_cb(GtkAction *action, gpointer data)
10143 Compose *compose = (Compose *)data;
10147 compose_allow_user_actions(compose, FALSE);
10148 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10149 compose_allow_user_actions(compose, TRUE);
10153 compose_close(compose);
10154 } else if (val == -1) {
10155 alertpanel_error(_("Could not queue message."));
10156 } else if (val == -2) {
10157 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10158 } else if (val == -3) {
10159 if (privacy_peek_error())
10160 alertpanel_error(_("Could not queue message for sending:\n\n"
10161 "Signature failed: %s"), privacy_get_error());
10162 } else if (val == -4) {
10163 alertpanel_error(_("Could not queue message for sending:\n\n"
10164 "Charset conversion failed."));
10165 } else if (val == -5) {
10166 alertpanel_error(_("Could not queue message for sending:\n\n"
10167 "Couldn't get recipient encryption key."));
10168 } else if (val == -6) {
10171 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10174 #define DRAFTED_AT_EXIT "drafted_at_exit"
10175 static void compose_register_draft(MsgInfo *info)
10177 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10178 DRAFTED_AT_EXIT, NULL);
10179 FILE *fp = g_fopen(filepath, "ab");
10182 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10190 gboolean compose_draft (gpointer data, guint action)
10192 Compose *compose = (Compose *)data;
10197 MsgFlags flag = {0, 0};
10198 static gboolean lock = FALSE;
10199 MsgInfo *newmsginfo;
10201 gboolean target_locked = FALSE;
10202 gboolean err = FALSE;
10204 if (lock) return FALSE;
10206 if (compose->sending)
10209 draft = account_get_special_folder(compose->account, F_DRAFT);
10210 cm_return_val_if_fail(draft != NULL, FALSE);
10212 if (!g_mutex_trylock(compose->mutex)) {
10213 /* we don't want to lock the mutex once it's available,
10214 * because as the only other part of compose.c locking
10215 * it is compose_close - which means once unlocked,
10216 * the compose struct will be freed */
10217 debug_print("couldn't lock mutex, probably sending\n");
10223 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10224 G_DIR_SEPARATOR, compose);
10225 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10226 FILE_OP_ERROR(tmp, "fopen");
10230 /* chmod for security */
10231 if (change_file_mode_rw(fp, tmp) < 0) {
10232 FILE_OP_ERROR(tmp, "chmod");
10233 g_warning("can't change file mode");
10236 /* Save draft infos */
10237 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10238 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10240 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10241 gchar *savefolderid;
10243 savefolderid = compose_get_save_to(compose);
10244 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10245 g_free(savefolderid);
10247 if (compose->return_receipt) {
10248 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10250 if (compose->privacy_system) {
10251 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10252 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10253 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10256 /* Message-ID of message replying to */
10257 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10258 gchar *folderid = NULL;
10260 if (compose->replyinfo->folder)
10261 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10262 if (folderid == NULL)
10263 folderid = g_strdup("NULL");
10265 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10268 /* Message-ID of message forwarding to */
10269 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10270 gchar *folderid = NULL;
10272 if (compose->fwdinfo->folder)
10273 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10274 if (folderid == NULL)
10275 folderid = g_strdup("NULL");
10277 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10281 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10282 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10284 sheaders = compose_get_manual_headers_info(compose);
10285 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10288 /* end of headers */
10289 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10296 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10300 if (fclose(fp) == EOF) {
10304 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10305 if (compose->targetinfo) {
10306 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10308 flag.perm_flags |= MSG_LOCKED;
10310 flag.tmp_flags = MSG_DRAFT;
10312 folder_item_scan(draft);
10313 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10314 MsgInfo *tmpinfo = NULL;
10315 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10316 if (compose->msgid) {
10317 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10320 msgnum = tmpinfo->msgnum;
10321 procmsg_msginfo_free(&tmpinfo);
10322 debug_print("got draft msgnum %d from scanning\n", msgnum);
10324 debug_print("didn't get draft msgnum after scanning\n");
10327 debug_print("got draft msgnum %d from adding\n", msgnum);
10333 if (action != COMPOSE_AUTO_SAVE) {
10334 if (action != COMPOSE_DRAFT_FOR_EXIT)
10335 alertpanel_error(_("Could not save draft."));
10338 gtkut_window_popup(compose->window);
10339 val = alertpanel_full(_("Could not save draft"),
10340 _("Could not save draft.\n"
10341 "Do you want to cancel exit or discard this email?"),
10342 _("_Cancel exit"), _("_Discard email"), NULL,
10343 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10344 if (val == G_ALERTALTERNATE) {
10346 g_mutex_unlock(compose->mutex); /* must be done before closing */
10347 compose_close(compose);
10351 g_mutex_unlock(compose->mutex); /* must be done before closing */
10360 if (compose->mode == COMPOSE_REEDIT) {
10361 compose_remove_reedit_target(compose, TRUE);
10364 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10367 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10369 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10371 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10372 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10373 procmsg_msginfo_set_flags(newmsginfo, 0,
10374 MSG_HAS_ATTACHMENT);
10376 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10377 compose_register_draft(newmsginfo);
10379 procmsg_msginfo_free(&newmsginfo);
10382 folder_item_scan(draft);
10384 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10386 g_mutex_unlock(compose->mutex); /* must be done before closing */
10387 compose_close(compose);
10393 path = folder_item_fetch_msg(draft, msgnum);
10394 if (path == NULL) {
10395 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10398 if (g_stat(path, &s) < 0) {
10399 FILE_OP_ERROR(path, "stat");
10405 procmsg_msginfo_free(&(compose->targetinfo));
10406 compose->targetinfo = procmsg_msginfo_new();
10407 compose->targetinfo->msgnum = msgnum;
10408 compose->targetinfo->size = (goffset)s.st_size;
10409 compose->targetinfo->mtime = s.st_mtime;
10410 compose->targetinfo->folder = draft;
10412 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10413 compose->mode = COMPOSE_REEDIT;
10415 if (action == COMPOSE_AUTO_SAVE) {
10416 compose->autosaved_draft = compose->targetinfo;
10418 compose->modified = FALSE;
10419 compose_set_title(compose);
10423 g_mutex_unlock(compose->mutex);
10427 void compose_clear_exit_drafts(void)
10429 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10430 DRAFTED_AT_EXIT, NULL);
10431 if (is_file_exist(filepath))
10432 claws_unlink(filepath);
10437 void compose_reopen_exit_drafts(void)
10439 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10440 DRAFTED_AT_EXIT, NULL);
10441 FILE *fp = g_fopen(filepath, "rb");
10445 while (fgets(buf, sizeof(buf), fp)) {
10446 gchar **parts = g_strsplit(buf, "\t", 2);
10447 const gchar *folder = parts[0];
10448 int msgnum = parts[1] ? atoi(parts[1]):-1;
10450 if (folder && *folder && msgnum > -1) {
10451 FolderItem *item = folder_find_item_from_identifier(folder);
10452 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10454 compose_reedit(info, FALSE);
10461 compose_clear_exit_drafts();
10464 static void compose_save_cb(GtkAction *action, gpointer data)
10466 Compose *compose = (Compose *)data;
10467 compose_draft(compose, COMPOSE_KEEP_EDITING);
10468 compose->rmode = COMPOSE_REEDIT;
10471 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10473 if (compose && file_list) {
10476 for ( tmp = file_list; tmp; tmp = tmp->next) {
10477 gchar *file = (gchar *) tmp->data;
10478 gchar *utf8_filename = conv_filename_to_utf8(file);
10479 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10480 compose_changed_cb(NULL, compose);
10485 g_free(utf8_filename);
10490 static void compose_attach_cb(GtkAction *action, gpointer data)
10492 Compose *compose = (Compose *)data;
10495 if (compose->redirect_filename != NULL)
10498 /* Set focus_window properly, in case we were called via popup menu,
10499 * which unsets it (via focus_out_event callback on compose window). */
10500 manage_window_focus_in(compose->window, NULL, NULL);
10502 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10505 compose_attach_from_list(compose, file_list, TRUE);
10506 g_list_free(file_list);
10510 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10512 Compose *compose = (Compose *)data;
10514 gint files_inserted = 0;
10516 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10521 for ( tmp = file_list; tmp; tmp = tmp->next) {
10522 gchar *file = (gchar *) tmp->data;
10523 gchar *filedup = g_strdup(file);
10524 gchar *shortfile = g_path_get_basename(filedup);
10525 ComposeInsertResult res;
10526 /* insert the file if the file is short or if the user confirmed that
10527 he/she wants to insert the large file */
10528 res = compose_insert_file(compose, file);
10529 if (res == COMPOSE_INSERT_READ_ERROR) {
10530 alertpanel_error(_("File '%s' could not be read."), shortfile);
10531 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10532 alertpanel_error(_("File '%s' contained invalid characters\n"
10533 "for the current encoding, insertion may be incorrect."),
10535 } else if (res == COMPOSE_INSERT_SUCCESS)
10542 g_list_free(file_list);
10546 if (files_inserted > 0 && compose->gtkaspell &&
10547 compose->gtkaspell->check_while_typing)
10548 gtkaspell_highlight_all(compose->gtkaspell);
10552 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10554 Compose *compose = (Compose *)data;
10556 compose_insert_sig(compose, FALSE);
10559 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10561 Compose *compose = (Compose *)data;
10563 compose_insert_sig(compose, TRUE);
10566 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10570 Compose *compose = (Compose *)data;
10572 gtkut_widget_get_uposition(widget, &x, &y);
10573 if (!compose->batch) {
10574 prefs_common.compose_x = x;
10575 prefs_common.compose_y = y;
10577 if (compose->sending || compose->updating)
10579 compose_close_cb(NULL, compose);
10583 void compose_close_toolbar(Compose *compose)
10585 compose_close_cb(NULL, compose);
10588 static void compose_close_cb(GtkAction *action, gpointer data)
10590 Compose *compose = (Compose *)data;
10594 if (compose->exteditor_tag != -1) {
10595 if (!compose_ext_editor_kill(compose))
10600 if (compose->modified) {
10601 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10602 if (!g_mutex_trylock(compose->mutex)) {
10603 /* we don't want to lock the mutex once it's available,
10604 * because as the only other part of compose.c locking
10605 * it is compose_close - which means once unlocked,
10606 * the compose struct will be freed */
10607 debug_print("couldn't lock mutex, probably sending\n");
10611 val = alertpanel(_("Discard message"),
10612 _("This message has been modified. Discard it?"),
10613 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10615 val = alertpanel(_("Save changes"),
10616 _("This message has been modified. Save the latest changes?"),
10617 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10620 g_mutex_unlock(compose->mutex);
10622 case G_ALERTDEFAULT:
10623 if (compose_can_autosave(compose) && !reedit)
10624 compose_remove_draft(compose);
10626 case G_ALERTALTERNATE:
10627 compose_draft(data, COMPOSE_QUIT_EDITING);
10634 compose_close(compose);
10637 static void compose_print_cb(GtkAction *action, gpointer data)
10639 Compose *compose = (Compose *) data;
10641 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10642 if (compose->targetinfo)
10643 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10646 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10648 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10649 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10650 Compose *compose = (Compose *) data;
10653 compose->out_encoding = (CharSet)value;
10656 static void compose_address_cb(GtkAction *action, gpointer data)
10658 Compose *compose = (Compose *)data;
10660 #ifndef USE_ALT_ADDRBOOK
10661 addressbook_open(compose);
10663 GError* error = NULL;
10664 addressbook_connect_signals(compose);
10665 addressbook_dbus_open(TRUE, &error);
10667 g_warning("%s", error->message);
10668 g_error_free(error);
10673 static void about_show_cb(GtkAction *action, gpointer data)
10678 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10680 Compose *compose = (Compose *)data;
10685 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10686 cm_return_if_fail(tmpl != NULL);
10688 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10690 val = alertpanel(_("Apply template"), msg,
10691 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10694 if (val == G_ALERTDEFAULT)
10695 compose_template_apply(compose, tmpl, TRUE);
10696 else if (val == G_ALERTALTERNATE)
10697 compose_template_apply(compose, tmpl, FALSE);
10700 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10702 Compose *compose = (Compose *)data;
10705 if (compose->exteditor_tag != -1) {
10706 debug_print("ignoring open external editor: external editor still open\n");
10710 compose_exec_ext_editor(compose);
10713 static void compose_undo_cb(GtkAction *action, gpointer data)
10715 Compose *compose = (Compose *)data;
10716 gboolean prev_autowrap = compose->autowrap;
10718 compose->autowrap = FALSE;
10719 undo_undo(compose->undostruct);
10720 compose->autowrap = prev_autowrap;
10723 static void compose_redo_cb(GtkAction *action, gpointer data)
10725 Compose *compose = (Compose *)data;
10726 gboolean prev_autowrap = compose->autowrap;
10728 compose->autowrap = FALSE;
10729 undo_redo(compose->undostruct);
10730 compose->autowrap = prev_autowrap;
10733 static void entry_cut_clipboard(GtkWidget *entry)
10735 if (GTK_IS_EDITABLE(entry))
10736 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10737 else if (GTK_IS_TEXT_VIEW(entry))
10738 gtk_text_buffer_cut_clipboard(
10739 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10740 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10744 static void entry_copy_clipboard(GtkWidget *entry)
10746 if (GTK_IS_EDITABLE(entry))
10747 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10748 else if (GTK_IS_TEXT_VIEW(entry))
10749 gtk_text_buffer_copy_clipboard(
10750 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10751 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10754 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10755 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10757 if (GTK_IS_TEXT_VIEW(entry)) {
10758 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10759 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10760 GtkTextIter start_iter, end_iter;
10762 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10764 if (contents == NULL)
10767 /* we shouldn't delete the selection when middle-click-pasting, or we
10768 * can't mid-click-paste our own selection */
10769 if (clip != GDK_SELECTION_PRIMARY) {
10770 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10771 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10774 if (insert_place == NULL) {
10775 /* if insert_place isn't specified, insert at the cursor.
10776 * used for Ctrl-V pasting */
10777 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10778 start = gtk_text_iter_get_offset(&start_iter);
10779 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10781 /* if insert_place is specified, paste here.
10782 * used for mid-click-pasting */
10783 start = gtk_text_iter_get_offset(insert_place);
10784 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10785 if (prefs_common.primary_paste_unselects)
10786 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10790 /* paste unwrapped: mark the paste so it's not wrapped later */
10791 end = start + strlen(contents);
10792 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10793 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10794 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10795 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10796 /* rewrap paragraph now (after a mid-click-paste) */
10797 mark_start = gtk_text_buffer_get_insert(buffer);
10798 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10799 gtk_text_iter_backward_char(&start_iter);
10800 compose_beautify_paragraph(compose, &start_iter, TRUE);
10802 } else if (GTK_IS_EDITABLE(entry))
10803 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10805 compose->modified = TRUE;
10808 static void entry_allsel(GtkWidget *entry)
10810 if (GTK_IS_EDITABLE(entry))
10811 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10812 else if (GTK_IS_TEXT_VIEW(entry)) {
10813 GtkTextIter startiter, enditer;
10814 GtkTextBuffer *textbuf;
10816 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10817 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10818 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10820 gtk_text_buffer_move_mark_by_name(textbuf,
10821 "selection_bound", &startiter);
10822 gtk_text_buffer_move_mark_by_name(textbuf,
10823 "insert", &enditer);
10827 static void compose_cut_cb(GtkAction *action, gpointer data)
10829 Compose *compose = (Compose *)data;
10830 if (compose->focused_editable
10831 #ifndef GENERIC_UMPC
10832 && gtk_widget_has_focus(compose->focused_editable)
10835 entry_cut_clipboard(compose->focused_editable);
10838 static void compose_copy_cb(GtkAction *action, gpointer data)
10840 Compose *compose = (Compose *)data;
10841 if (compose->focused_editable
10842 #ifndef GENERIC_UMPC
10843 && gtk_widget_has_focus(compose->focused_editable)
10846 entry_copy_clipboard(compose->focused_editable);
10849 static void compose_paste_cb(GtkAction *action, gpointer data)
10851 Compose *compose = (Compose *)data;
10852 gint prev_autowrap;
10853 GtkTextBuffer *buffer;
10855 if (compose->focused_editable &&
10856 #ifndef GENERIC_UMPC
10857 gtk_widget_has_focus(compose->focused_editable)
10860 entry_paste_clipboard(compose, compose->focused_editable,
10861 prefs_common.linewrap_pastes,
10862 GDK_SELECTION_CLIPBOARD, NULL);
10867 #ifndef GENERIC_UMPC
10868 gtk_widget_has_focus(compose->text) &&
10870 compose->gtkaspell &&
10871 compose->gtkaspell->check_while_typing)
10872 gtkaspell_highlight_all(compose->gtkaspell);
10876 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10878 Compose *compose = (Compose *)data;
10879 gint wrap_quote = prefs_common.linewrap_quote;
10880 if (compose->focused_editable
10881 #ifndef GENERIC_UMPC
10882 && gtk_widget_has_focus(compose->focused_editable)
10885 /* let text_insert() (called directly or at a later time
10886 * after the gtk_editable_paste_clipboard) know that
10887 * text is to be inserted as a quotation. implemented
10888 * by using a simple refcount... */
10889 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10890 G_OBJECT(compose->focused_editable),
10891 "paste_as_quotation"));
10892 g_object_set_data(G_OBJECT(compose->focused_editable),
10893 "paste_as_quotation",
10894 GINT_TO_POINTER(paste_as_quotation + 1));
10895 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10896 entry_paste_clipboard(compose, compose->focused_editable,
10897 prefs_common.linewrap_pastes,
10898 GDK_SELECTION_CLIPBOARD, NULL);
10899 prefs_common.linewrap_quote = wrap_quote;
10903 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10905 Compose *compose = (Compose *)data;
10906 gint prev_autowrap;
10907 GtkTextBuffer *buffer;
10909 if (compose->focused_editable
10910 #ifndef GENERIC_UMPC
10911 && gtk_widget_has_focus(compose->focused_editable)
10914 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10915 GDK_SELECTION_CLIPBOARD, NULL);
10920 #ifndef GENERIC_UMPC
10921 gtk_widget_has_focus(compose->text) &&
10923 compose->gtkaspell &&
10924 compose->gtkaspell->check_while_typing)
10925 gtkaspell_highlight_all(compose->gtkaspell);
10929 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10931 Compose *compose = (Compose *)data;
10932 gint prev_autowrap;
10933 GtkTextBuffer *buffer;
10935 if (compose->focused_editable
10936 #ifndef GENERIC_UMPC
10937 && gtk_widget_has_focus(compose->focused_editable)
10940 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10941 GDK_SELECTION_CLIPBOARD, NULL);
10946 #ifndef GENERIC_UMPC
10947 gtk_widget_has_focus(compose->text) &&
10949 compose->gtkaspell &&
10950 compose->gtkaspell->check_while_typing)
10951 gtkaspell_highlight_all(compose->gtkaspell);
10955 static void compose_allsel_cb(GtkAction *action, gpointer data)
10957 Compose *compose = (Compose *)data;
10958 if (compose->focused_editable
10959 #ifndef GENERIC_UMPC
10960 && gtk_widget_has_focus(compose->focused_editable)
10963 entry_allsel(compose->focused_editable);
10966 static void textview_move_beginning_of_line (GtkTextView *text)
10968 GtkTextBuffer *buffer;
10972 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10974 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10975 mark = gtk_text_buffer_get_insert(buffer);
10976 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10977 gtk_text_iter_set_line_offset(&ins, 0);
10978 gtk_text_buffer_place_cursor(buffer, &ins);
10981 static void textview_move_forward_character (GtkTextView *text)
10983 GtkTextBuffer *buffer;
10987 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10989 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10990 mark = gtk_text_buffer_get_insert(buffer);
10991 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10992 if (gtk_text_iter_forward_cursor_position(&ins))
10993 gtk_text_buffer_place_cursor(buffer, &ins);
10996 static void textview_move_backward_character (GtkTextView *text)
10998 GtkTextBuffer *buffer;
11002 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11004 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11005 mark = gtk_text_buffer_get_insert(buffer);
11006 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11007 if (gtk_text_iter_backward_cursor_position(&ins))
11008 gtk_text_buffer_place_cursor(buffer, &ins);
11011 static void textview_move_forward_word (GtkTextView *text)
11013 GtkTextBuffer *buffer;
11018 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11020 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11021 mark = gtk_text_buffer_get_insert(buffer);
11022 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11023 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11024 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11025 gtk_text_iter_backward_word_start(&ins);
11026 gtk_text_buffer_place_cursor(buffer, &ins);
11030 static void textview_move_backward_word (GtkTextView *text)
11032 GtkTextBuffer *buffer;
11036 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11038 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11039 mark = gtk_text_buffer_get_insert(buffer);
11040 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11041 if (gtk_text_iter_backward_word_starts(&ins, 1))
11042 gtk_text_buffer_place_cursor(buffer, &ins);
11045 static void textview_move_end_of_line (GtkTextView *text)
11047 GtkTextBuffer *buffer;
11051 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11053 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11054 mark = gtk_text_buffer_get_insert(buffer);
11055 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11056 if (gtk_text_iter_forward_to_line_end(&ins))
11057 gtk_text_buffer_place_cursor(buffer, &ins);
11060 static void textview_move_next_line (GtkTextView *text)
11062 GtkTextBuffer *buffer;
11067 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11069 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11070 mark = gtk_text_buffer_get_insert(buffer);
11071 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11072 offset = gtk_text_iter_get_line_offset(&ins);
11073 if (gtk_text_iter_forward_line(&ins)) {
11074 gtk_text_iter_set_line_offset(&ins, offset);
11075 gtk_text_buffer_place_cursor(buffer, &ins);
11079 static void textview_move_previous_line (GtkTextView *text)
11081 GtkTextBuffer *buffer;
11086 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11088 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11089 mark = gtk_text_buffer_get_insert(buffer);
11090 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11091 offset = gtk_text_iter_get_line_offset(&ins);
11092 if (gtk_text_iter_backward_line(&ins)) {
11093 gtk_text_iter_set_line_offset(&ins, offset);
11094 gtk_text_buffer_place_cursor(buffer, &ins);
11098 static void textview_delete_forward_character (GtkTextView *text)
11100 GtkTextBuffer *buffer;
11102 GtkTextIter ins, end_iter;
11104 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11106 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11107 mark = gtk_text_buffer_get_insert(buffer);
11108 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11110 if (gtk_text_iter_forward_char(&end_iter)) {
11111 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11115 static void textview_delete_backward_character (GtkTextView *text)
11117 GtkTextBuffer *buffer;
11119 GtkTextIter ins, end_iter;
11121 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11123 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11124 mark = gtk_text_buffer_get_insert(buffer);
11125 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11127 if (gtk_text_iter_backward_char(&end_iter)) {
11128 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11132 static void textview_delete_forward_word (GtkTextView *text)
11134 GtkTextBuffer *buffer;
11136 GtkTextIter ins, end_iter;
11138 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11140 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11141 mark = gtk_text_buffer_get_insert(buffer);
11142 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11144 if (gtk_text_iter_forward_word_end(&end_iter)) {
11145 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11149 static void textview_delete_backward_word (GtkTextView *text)
11151 GtkTextBuffer *buffer;
11153 GtkTextIter ins, end_iter;
11155 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11157 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11158 mark = gtk_text_buffer_get_insert(buffer);
11159 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11161 if (gtk_text_iter_backward_word_start(&end_iter)) {
11162 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11166 static void textview_delete_line (GtkTextView *text)
11168 GtkTextBuffer *buffer;
11170 GtkTextIter ins, start_iter, end_iter;
11172 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11174 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11175 mark = gtk_text_buffer_get_insert(buffer);
11176 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11179 gtk_text_iter_set_line_offset(&start_iter, 0);
11182 if (gtk_text_iter_ends_line(&end_iter)){
11183 if (!gtk_text_iter_forward_char(&end_iter))
11184 gtk_text_iter_backward_char(&start_iter);
11187 gtk_text_iter_forward_to_line_end(&end_iter);
11188 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11191 static void textview_delete_to_line_end (GtkTextView *text)
11193 GtkTextBuffer *buffer;
11195 GtkTextIter ins, end_iter;
11197 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11199 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11200 mark = gtk_text_buffer_get_insert(buffer);
11201 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11203 if (gtk_text_iter_ends_line(&end_iter))
11204 gtk_text_iter_forward_char(&end_iter);
11206 gtk_text_iter_forward_to_line_end(&end_iter);
11207 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11210 #define DO_ACTION(name, act) { \
11211 if(!strcmp(name, a_name)) { \
11215 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11217 const gchar *a_name = gtk_action_get_name(action);
11218 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11219 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11220 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11221 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11222 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11223 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11224 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11225 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11226 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11227 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11228 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11229 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11230 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11231 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11232 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11235 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11237 Compose *compose = (Compose *)data;
11238 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11239 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11241 action = compose_call_advanced_action_from_path(gaction);
11244 void (*do_action) (GtkTextView *text);
11245 } action_table[] = {
11246 {textview_move_beginning_of_line},
11247 {textview_move_forward_character},
11248 {textview_move_backward_character},
11249 {textview_move_forward_word},
11250 {textview_move_backward_word},
11251 {textview_move_end_of_line},
11252 {textview_move_next_line},
11253 {textview_move_previous_line},
11254 {textview_delete_forward_character},
11255 {textview_delete_backward_character},
11256 {textview_delete_forward_word},
11257 {textview_delete_backward_word},
11258 {textview_delete_line},
11259 {textview_delete_to_line_end}
11262 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11264 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11265 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11266 if (action_table[action].do_action)
11267 action_table[action].do_action(text);
11269 g_warning("Not implemented yet.");
11273 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11275 GtkAllocation allocation;
11279 if (GTK_IS_EDITABLE(widget)) {
11280 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11281 gtk_editable_set_position(GTK_EDITABLE(widget),
11284 if ((parent = gtk_widget_get_parent(widget))
11285 && (parent = gtk_widget_get_parent(parent))
11286 && (parent = gtk_widget_get_parent(parent))) {
11287 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11288 gtk_widget_get_allocation(widget, &allocation);
11289 gint y = allocation.y;
11290 gint height = allocation.height;
11291 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11292 (GTK_SCROLLED_WINDOW(parent));
11294 gfloat value = gtk_adjustment_get_value(shown);
11295 gfloat upper = gtk_adjustment_get_upper(shown);
11296 gfloat page_size = gtk_adjustment_get_page_size(shown);
11297 if (y < (int)value) {
11298 gtk_adjustment_set_value(shown, y - 1);
11300 if ((y + height) > ((int)value + (int)page_size)) {
11301 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11302 gtk_adjustment_set_value(shown,
11303 y + height - (int)page_size - 1);
11305 gtk_adjustment_set_value(shown,
11306 (int)upper - (int)page_size - 1);
11313 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11314 compose->focused_editable = widget;
11316 #ifdef GENERIC_UMPC
11317 if (GTK_IS_TEXT_VIEW(widget)
11318 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11319 g_object_ref(compose->notebook);
11320 g_object_ref(compose->edit_vbox);
11321 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11322 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11323 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11324 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11325 g_object_unref(compose->notebook);
11326 g_object_unref(compose->edit_vbox);
11327 g_signal_handlers_block_by_func(G_OBJECT(widget),
11328 G_CALLBACK(compose_grab_focus_cb),
11330 gtk_widget_grab_focus(widget);
11331 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11332 G_CALLBACK(compose_grab_focus_cb),
11334 } else if (!GTK_IS_TEXT_VIEW(widget)
11335 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11336 g_object_ref(compose->notebook);
11337 g_object_ref(compose->edit_vbox);
11338 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11339 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11340 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11341 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11342 g_object_unref(compose->notebook);
11343 g_object_unref(compose->edit_vbox);
11344 g_signal_handlers_block_by_func(G_OBJECT(widget),
11345 G_CALLBACK(compose_grab_focus_cb),
11347 gtk_widget_grab_focus(widget);
11348 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11349 G_CALLBACK(compose_grab_focus_cb),
11355 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11357 compose->modified = TRUE;
11358 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11359 #ifndef GENERIC_UMPC
11360 compose_set_title(compose);
11364 static void compose_wrap_cb(GtkAction *action, gpointer data)
11366 Compose *compose = (Compose *)data;
11367 compose_beautify_paragraph(compose, NULL, TRUE);
11370 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11372 Compose *compose = (Compose *)data;
11373 compose_wrap_all_full(compose, TRUE);
11376 static void compose_find_cb(GtkAction *action, gpointer data)
11378 Compose *compose = (Compose *)data;
11380 message_search_compose(compose);
11383 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11386 Compose *compose = (Compose *)data;
11387 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11388 if (compose->autowrap)
11389 compose_wrap_all_full(compose, TRUE);
11390 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11393 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11396 Compose *compose = (Compose *)data;
11397 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11400 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11402 Compose *compose = (Compose *)data;
11404 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11405 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11408 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11410 Compose *compose = (Compose *)data;
11412 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11413 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11416 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11418 g_free(compose->privacy_system);
11419 g_free(compose->encdata);
11421 compose->privacy_system = g_strdup(account->default_privacy_system);
11422 compose_update_privacy_system_menu_item(compose, warn);
11425 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11427 Compose *compose = (Compose *)data;
11429 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11430 gtk_widget_show(compose->ruler_hbox);
11431 prefs_common.show_ruler = TRUE;
11433 gtk_widget_hide(compose->ruler_hbox);
11434 gtk_widget_queue_resize(compose->edit_vbox);
11435 prefs_common.show_ruler = FALSE;
11439 static void compose_attach_drag_received_cb (GtkWidget *widget,
11440 GdkDragContext *context,
11443 GtkSelectionData *data,
11446 gpointer user_data)
11448 Compose *compose = (Compose *)user_data;
11452 type = gtk_selection_data_get_data_type(data);
11453 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11454 && gtk_drag_get_source_widget(context) !=
11455 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11456 list = uri_list_extract_filenames(
11457 (const gchar *)gtk_selection_data_get_data(data));
11458 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11459 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11460 compose_attach_append
11461 (compose, (const gchar *)tmp->data,
11462 utf8_filename, NULL, NULL);
11463 g_free(utf8_filename);
11465 if (list) compose_changed_cb(NULL, compose);
11466 list_free_strings(list);
11468 } else if (gtk_drag_get_source_widget(context)
11469 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11470 /* comes from our summaryview */
11471 SummaryView * summaryview = NULL;
11472 GSList * list = NULL, *cur = NULL;
11474 if (mainwindow_get_mainwindow())
11475 summaryview = mainwindow_get_mainwindow()->summaryview;
11478 list = summary_get_selected_msg_list(summaryview);
11480 for (cur = list; cur; cur = cur->next) {
11481 MsgInfo *msginfo = (MsgInfo *)cur->data;
11482 gchar *file = NULL;
11484 file = procmsg_get_message_file_full(msginfo,
11487 compose_attach_append(compose, (const gchar *)file,
11488 (const gchar *)file, "message/rfc822", NULL);
11492 g_slist_free(list);
11496 static gboolean compose_drag_drop(GtkWidget *widget,
11497 GdkDragContext *drag_context,
11499 guint time, gpointer user_data)
11501 /* not handling this signal makes compose_insert_drag_received_cb
11506 static gboolean completion_set_focus_to_subject
11507 (GtkWidget *widget,
11508 GdkEventKey *event,
11511 cm_return_val_if_fail(compose != NULL, FALSE);
11513 /* make backtab move to subject field */
11514 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11515 gtk_widget_grab_focus(compose->subject_entry);
11521 static void compose_insert_drag_received_cb (GtkWidget *widget,
11522 GdkDragContext *drag_context,
11525 GtkSelectionData *data,
11528 gpointer user_data)
11530 Compose *compose = (Compose *)user_data;
11536 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11538 type = gtk_selection_data_get_data_type(data);
11539 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11540 AlertValue val = G_ALERTDEFAULT;
11541 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11543 list = uri_list_extract_filenames(ddata);
11544 num_files = g_list_length(list);
11545 if (list == NULL && strstr(ddata, "://")) {
11546 /* Assume a list of no files, and data has ://, is a remote link */
11547 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11548 gchar *tmpfile = get_tmp_file();
11549 str_write_to_file(tmpdata, tmpfile);
11551 compose_insert_file(compose, tmpfile);
11552 claws_unlink(tmpfile);
11554 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11555 compose_beautify_paragraph(compose, NULL, TRUE);
11558 switch (prefs_common.compose_dnd_mode) {
11559 case COMPOSE_DND_ASK:
11560 msg = g_strdup_printf(
11562 "Do you want to insert the contents of the file "
11563 "into the message body, or attach it to the email?",
11564 "Do you want to insert the contents of the %d files "
11565 "into the message body, or attach them to the email?",
11568 val = alertpanel_full(_("Insert or attach?"), msg,
11569 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11570 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11573 case COMPOSE_DND_INSERT:
11574 val = G_ALERTALTERNATE;
11576 case COMPOSE_DND_ATTACH:
11577 val = G_ALERTOTHER;
11580 /* unexpected case */
11581 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11584 if (val & G_ALERTDISABLE) {
11585 val &= ~G_ALERTDISABLE;
11586 /* remember what action to perform by default, only if we don't click Cancel */
11587 if (val == G_ALERTALTERNATE)
11588 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11589 else if (val == G_ALERTOTHER)
11590 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11593 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11594 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11595 list_free_strings(list);
11598 } else if (val == G_ALERTOTHER) {
11599 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11600 list_free_strings(list);
11605 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11606 compose_insert_file(compose, (const gchar *)tmp->data);
11608 list_free_strings(list);
11610 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11615 static void compose_header_drag_received_cb (GtkWidget *widget,
11616 GdkDragContext *drag_context,
11619 GtkSelectionData *data,
11622 gpointer user_data)
11624 GtkEditable *entry = (GtkEditable *)user_data;
11625 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11627 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11630 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11631 gchar *decoded=g_new(gchar, strlen(email));
11634 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11635 gtk_editable_delete_text(entry, 0, -1);
11636 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11637 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11641 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11644 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11646 Compose *compose = (Compose *)data;
11648 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11649 compose->return_receipt = TRUE;
11651 compose->return_receipt = FALSE;
11654 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11656 Compose *compose = (Compose *)data;
11658 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11659 compose->remove_references = TRUE;
11661 compose->remove_references = FALSE;
11664 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11665 ComposeHeaderEntry *headerentry)
11667 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11671 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11672 GdkEventKey *event,
11673 ComposeHeaderEntry *headerentry)
11675 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11676 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11677 !(event->state & GDK_MODIFIER_MASK) &&
11678 (event->keyval == GDK_KEY_BackSpace) &&
11679 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11680 gtk_container_remove
11681 (GTK_CONTAINER(headerentry->compose->header_table),
11682 headerentry->combo);
11683 gtk_container_remove
11684 (GTK_CONTAINER(headerentry->compose->header_table),
11685 headerentry->entry);
11686 headerentry->compose->header_list =
11687 g_slist_remove(headerentry->compose->header_list,
11689 g_free(headerentry);
11690 } else if (event->keyval == GDK_KEY_Tab) {
11691 if (headerentry->compose->header_last == headerentry) {
11692 /* Override default next focus, and give it to subject_entry
11693 * instead of notebook tabs
11695 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11696 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11703 static gboolean scroll_postpone(gpointer data)
11705 Compose *compose = (Compose *)data;
11707 if (compose->batch)
11710 GTK_EVENTS_FLUSH();
11711 compose_show_first_last_header(compose, FALSE);
11715 static void compose_headerentry_changed_cb(GtkWidget *entry,
11716 ComposeHeaderEntry *headerentry)
11718 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11719 compose_create_header_entry(headerentry->compose);
11720 g_signal_handlers_disconnect_matched
11721 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11722 0, 0, NULL, NULL, headerentry);
11724 if (!headerentry->compose->batch)
11725 g_timeout_add(0, scroll_postpone, headerentry->compose);
11729 static gboolean compose_defer_auto_save_draft(Compose *compose)
11731 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11732 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11736 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11738 GtkAdjustment *vadj;
11740 cm_return_if_fail(compose);
11745 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11746 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11747 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11748 gtk_widget_get_parent(compose->header_table)));
11749 gtk_adjustment_set_value(vadj, (show_first ?
11750 gtk_adjustment_get_lower(vadj) :
11751 (gtk_adjustment_get_upper(vadj) -
11752 gtk_adjustment_get_page_size(vadj))));
11753 gtk_adjustment_changed(vadj);
11756 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11757 const gchar *text, gint len, Compose *compose)
11759 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11760 (G_OBJECT(compose->text), "paste_as_quotation"));
11763 cm_return_if_fail(text != NULL);
11765 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11766 G_CALLBACK(text_inserted),
11768 if (paste_as_quotation) {
11770 const gchar *qmark;
11772 GtkTextIter start_iter;
11775 len = strlen(text);
11777 new_text = g_strndup(text, len);
11779 qmark = compose_quote_char_from_context(compose);
11781 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11782 gtk_text_buffer_place_cursor(buffer, iter);
11784 pos = gtk_text_iter_get_offset(iter);
11786 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11787 _("Quote format error at line %d."));
11788 quote_fmt_reset_vartable();
11790 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11791 GINT_TO_POINTER(paste_as_quotation - 1));
11793 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11794 gtk_text_buffer_place_cursor(buffer, iter);
11795 gtk_text_buffer_delete_mark(buffer, mark);
11797 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11798 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11799 compose_beautify_paragraph(compose, &start_iter, FALSE);
11800 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11801 gtk_text_buffer_delete_mark(buffer, mark);
11803 if (strcmp(text, "\n") || compose->automatic_break
11804 || gtk_text_iter_starts_line(iter)) {
11805 GtkTextIter before_ins;
11806 gtk_text_buffer_insert(buffer, iter, text, len);
11807 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11808 before_ins = *iter;
11809 gtk_text_iter_backward_chars(&before_ins, len);
11810 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11813 /* check if the preceding is just whitespace or quote */
11814 GtkTextIter start_line;
11815 gchar *tmp = NULL, *quote = NULL;
11816 gint quote_len = 0, is_normal = 0;
11817 start_line = *iter;
11818 gtk_text_iter_set_line_offset(&start_line, 0);
11819 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11822 if (*tmp == '\0') {
11825 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11833 gtk_text_buffer_insert(buffer, iter, text, len);
11835 gtk_text_buffer_insert_with_tags_by_name(buffer,
11836 iter, text, len, "no_join", NULL);
11841 if (!paste_as_quotation) {
11842 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11843 compose_beautify_paragraph(compose, iter, FALSE);
11844 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11845 gtk_text_buffer_delete_mark(buffer, mark);
11848 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11849 G_CALLBACK(text_inserted),
11851 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11853 if (compose_can_autosave(compose) &&
11854 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11855 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11856 compose->draft_timeout_tag = g_timeout_add
11857 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11861 static void compose_check_all(GtkAction *action, gpointer data)
11863 Compose *compose = (Compose *)data;
11864 if (!compose->gtkaspell)
11867 if (gtk_widget_has_focus(compose->subject_entry))
11868 claws_spell_entry_check_all(
11869 CLAWS_SPELL_ENTRY(compose->subject_entry));
11871 gtkaspell_check_all(compose->gtkaspell);
11874 static void compose_highlight_all(GtkAction *action, gpointer data)
11876 Compose *compose = (Compose *)data;
11877 if (compose->gtkaspell) {
11878 claws_spell_entry_recheck_all(
11879 CLAWS_SPELL_ENTRY(compose->subject_entry));
11880 gtkaspell_highlight_all(compose->gtkaspell);
11884 static void compose_check_backwards(GtkAction *action, gpointer data)
11886 Compose *compose = (Compose *)data;
11887 if (!compose->gtkaspell) {
11888 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11892 if (gtk_widget_has_focus(compose->subject_entry))
11893 claws_spell_entry_check_backwards(
11894 CLAWS_SPELL_ENTRY(compose->subject_entry));
11896 gtkaspell_check_backwards(compose->gtkaspell);
11899 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11901 Compose *compose = (Compose *)data;
11902 if (!compose->gtkaspell) {
11903 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11907 if (gtk_widget_has_focus(compose->subject_entry))
11908 claws_spell_entry_check_forwards_go(
11909 CLAWS_SPELL_ENTRY(compose->subject_entry));
11911 gtkaspell_check_forwards_go(compose->gtkaspell);
11916 *\brief Guess originating forward account from MsgInfo and several
11917 * "common preference" settings. Return NULL if no guess.
11919 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11921 PrefsAccount *account = NULL;
11923 cm_return_val_if_fail(msginfo, NULL);
11924 cm_return_val_if_fail(msginfo->folder, NULL);
11925 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11927 if (msginfo->folder->prefs->enable_default_account)
11928 account = account_find_from_id(msginfo->folder->prefs->default_account);
11930 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11932 Xstrdup_a(to, msginfo->to, return NULL);
11933 extract_address(to);
11934 account = account_find_from_address(to, FALSE);
11937 if (!account && prefs_common.forward_account_autosel) {
11939 if (!procheader_get_header_from_msginfo
11940 (msginfo, &cc, "Cc:")) {
11941 gchar *buf = cc + strlen("Cc:");
11942 extract_address(buf);
11943 account = account_find_from_address(buf, FALSE);
11948 if (!account && prefs_common.forward_account_autosel) {
11949 gchar *deliveredto = NULL;
11950 if (!procheader_get_header_from_msginfo
11951 (msginfo, &deliveredto, "Delivered-To:")) {
11952 gchar *buf = deliveredto + strlen("Delivered-To:");
11953 extract_address(buf);
11954 account = account_find_from_address(buf, FALSE);
11955 g_free(deliveredto);
11960 account = msginfo->folder->folder->account;
11965 gboolean compose_close(Compose *compose)
11969 cm_return_val_if_fail(compose, FALSE);
11971 if (!g_mutex_trylock(compose->mutex)) {
11972 /* we have to wait for the (possibly deferred by auto-save)
11973 * drafting to be done, before destroying the compose under
11975 debug_print("waiting for drafting to finish...\n");
11976 compose_allow_user_actions(compose, FALSE);
11977 if (compose->close_timeout_tag == 0) {
11978 compose->close_timeout_tag =
11979 g_timeout_add (500, (GSourceFunc) compose_close,
11985 if (compose->draft_timeout_tag >= 0) {
11986 g_source_remove(compose->draft_timeout_tag);
11987 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11990 gtkut_widget_get_uposition(compose->window, &x, &y);
11991 if (!compose->batch) {
11992 prefs_common.compose_x = x;
11993 prefs_common.compose_y = y;
11995 g_mutex_unlock(compose->mutex);
11996 compose_destroy(compose);
12001 * Add entry field for each address in list.
12002 * \param compose E-Mail composition object.
12003 * \param listAddress List of (formatted) E-Mail addresses.
12005 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12008 node = listAddress;
12010 addr = ( gchar * ) node->data;
12011 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12012 node = g_list_next( node );
12016 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12017 guint action, gboolean opening_multiple)
12019 gchar *body = NULL;
12020 GSList *new_msglist = NULL;
12021 MsgInfo *tmp_msginfo = NULL;
12022 gboolean originally_enc = FALSE;
12023 gboolean originally_sig = FALSE;
12024 Compose *compose = NULL;
12025 gchar *s_system = NULL;
12027 cm_return_if_fail(msgview != NULL);
12029 cm_return_if_fail(msginfo_list != NULL);
12031 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12032 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12033 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12035 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12036 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12037 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12038 orig_msginfo, mimeinfo);
12039 if (tmp_msginfo != NULL) {
12040 new_msglist = g_slist_append(NULL, tmp_msginfo);
12042 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12043 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12044 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12046 tmp_msginfo->folder = orig_msginfo->folder;
12047 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12048 if (orig_msginfo->tags) {
12049 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12050 tmp_msginfo->folder->tags_dirty = TRUE;
12056 if (!opening_multiple)
12057 body = messageview_get_selection(msgview);
12060 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12061 procmsg_msginfo_free(&tmp_msginfo);
12062 g_slist_free(new_msglist);
12064 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12066 if (compose && originally_enc) {
12067 compose_force_encryption(compose, compose->account, FALSE, s_system);
12070 if (compose && originally_sig && compose->account->default_sign_reply) {
12071 compose_force_signing(compose, compose->account, s_system);
12075 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12078 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12081 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12082 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12083 GSList *cur = msginfo_list;
12084 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12085 "messages. Opening the windows "
12086 "could take some time. Do you "
12087 "want to continue?"),
12088 g_slist_length(msginfo_list));
12089 if (g_slist_length(msginfo_list) > 9
12090 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12091 != G_ALERTALTERNATE) {
12096 /* We'll open multiple compose windows */
12097 /* let the WM place the next windows */
12098 compose_force_window_origin = FALSE;
12099 for (; cur; cur = cur->next) {
12101 tmplist.data = cur->data;
12102 tmplist.next = NULL;
12103 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12105 compose_force_window_origin = TRUE;
12107 /* forwarding multiple mails as attachments is done via a
12108 * single compose window */
12109 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12113 void compose_check_for_email_account(Compose *compose)
12115 PrefsAccount *ac = NULL, *curr = NULL;
12121 if (compose->account && compose->account->protocol == A_NNTP) {
12122 ac = account_get_cur_account();
12123 if (ac->protocol == A_NNTP) {
12124 list = account_get_list();
12126 for( ; list != NULL ; list = g_list_next(list)) {
12127 curr = (PrefsAccount *) list->data;
12128 if (curr->protocol != A_NNTP) {
12134 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12139 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12140 const gchar *address)
12142 GSList *msginfo_list = NULL;
12143 gchar *body = messageview_get_selection(msgview);
12146 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12148 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12149 compose_check_for_email_account(compose);
12150 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12151 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12152 compose_reply_set_subject(compose, msginfo);
12155 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12158 void compose_set_position(Compose *compose, gint pos)
12160 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12162 gtkut_text_view_set_position(text, pos);
12165 gboolean compose_search_string(Compose *compose,
12166 const gchar *str, gboolean case_sens)
12168 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12170 return gtkut_text_view_search_string(text, str, case_sens);
12173 gboolean compose_search_string_backward(Compose *compose,
12174 const gchar *str, gboolean case_sens)
12176 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12178 return gtkut_text_view_search_string_backward(text, str, case_sens);
12181 /* allocate a msginfo structure and populate its data from a compose data structure */
12182 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12184 MsgInfo *newmsginfo;
12186 gchar date[RFC822_DATE_BUFFSIZE];
12188 cm_return_val_if_fail( compose != NULL, NULL );
12190 newmsginfo = procmsg_msginfo_new();
12193 get_rfc822_date(date, sizeof(date));
12194 newmsginfo->date = g_strdup(date);
12197 if (compose->from_name) {
12198 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12199 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12203 if (compose->subject_entry)
12204 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12206 /* to, cc, reply-to, newsgroups */
12207 for (list = compose->header_list; list; list = list->next) {
12208 gchar *header = gtk_editable_get_chars(
12210 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12211 gchar *entry = gtk_editable_get_chars(
12212 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12214 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12215 if ( newmsginfo->to == NULL ) {
12216 newmsginfo->to = g_strdup(entry);
12217 } else if (entry && *entry) {
12218 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12219 g_free(newmsginfo->to);
12220 newmsginfo->to = tmp;
12223 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12224 if ( newmsginfo->cc == NULL ) {
12225 newmsginfo->cc = g_strdup(entry);
12226 } else if (entry && *entry) {
12227 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12228 g_free(newmsginfo->cc);
12229 newmsginfo->cc = tmp;
12232 if ( strcasecmp(header,
12233 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12234 if ( newmsginfo->newsgroups == NULL ) {
12235 newmsginfo->newsgroups = g_strdup(entry);
12236 } else if (entry && *entry) {
12237 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12238 g_free(newmsginfo->newsgroups);
12239 newmsginfo->newsgroups = tmp;
12247 /* other data is unset */
12253 /* update compose's dictionaries from folder dict settings */
12254 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12255 FolderItem *folder_item)
12257 cm_return_if_fail(compose != NULL);
12259 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12260 FolderItemPrefs *prefs = folder_item->prefs;
12262 if (prefs->enable_default_dictionary)
12263 gtkaspell_change_dict(compose->gtkaspell,
12264 prefs->default_dictionary, FALSE);
12265 if (folder_item->prefs->enable_default_alt_dictionary)
12266 gtkaspell_change_alt_dict(compose->gtkaspell,
12267 prefs->default_alt_dictionary);
12268 if (prefs->enable_default_dictionary
12269 || prefs->enable_default_alt_dictionary)
12270 compose_spell_menu_changed(compose);
12275 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12277 Compose *compose = (Compose *)data;
12279 cm_return_if_fail(compose != NULL);
12281 gtk_widget_grab_focus(compose->text);
12284 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12286 gtk_combo_box_popup(GTK_COMBO_BOX(data));