2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
126 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
186 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
188 static GdkColor default_header_bgcolor = {
195 static GdkColor default_header_color = {
202 static GList *compose_list = NULL;
203 static GSList *extra_headers = NULL;
205 static Compose *compose_generic_new (PrefsAccount *account,
209 GList *listAddress );
211 static Compose *compose_create (PrefsAccount *account,
216 static void compose_entry_indicate (Compose *compose,
217 const gchar *address);
218 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
219 ComposeQuoteMode quote_mode,
223 static Compose *compose_forward_multiple (PrefsAccount *account,
224 GSList *msginfo_list);
225 static Compose *compose_reply (MsgInfo *msginfo,
226 ComposeQuoteMode quote_mode,
231 static Compose *compose_reply_mode (ComposeMode mode,
232 GSList *msginfo_list,
234 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
235 static void compose_update_privacy_systems_menu(Compose *compose);
237 static GtkWidget *compose_account_option_menu_create
239 static void compose_set_out_encoding (Compose *compose);
240 static void compose_set_template_menu (Compose *compose);
241 static void compose_destroy (Compose *compose);
243 static MailField compose_entries_set (Compose *compose,
245 ComposeEntryType to_type);
246 static gint compose_parse_header (Compose *compose,
248 static gint compose_parse_manual_headers (Compose *compose,
250 HeaderEntry *entries);
251 static gchar *compose_parse_references (const gchar *ref,
254 static gchar *compose_quote_fmt (Compose *compose,
260 gboolean need_unescape,
261 const gchar *err_msg);
263 static void compose_reply_set_entry (Compose *compose,
269 followup_and_reply_to);
270 static void compose_reedit_set_entry (Compose *compose,
273 static void compose_insert_sig (Compose *compose,
275 static ComposeInsertResult compose_insert_file (Compose *compose,
278 static gboolean compose_attach_append (Compose *compose,
281 const gchar *content_type,
282 const gchar *charset);
283 static void compose_attach_parts (Compose *compose,
286 static gboolean compose_beautify_paragraph (Compose *compose,
287 GtkTextIter *par_iter,
289 static void compose_wrap_all (Compose *compose);
290 static void compose_wrap_all_full (Compose *compose,
293 static void compose_set_title (Compose *compose);
294 static void compose_select_account (Compose *compose,
295 PrefsAccount *account,
298 static PrefsAccount *compose_current_mail_account(void);
299 /* static gint compose_send (Compose *compose); */
300 static gboolean compose_check_for_valid_recipient
302 static gboolean compose_check_entries (Compose *compose,
303 gboolean check_everything);
304 static gint compose_write_to_file (Compose *compose,
307 gboolean attach_parts);
308 static gint compose_write_body_to_file (Compose *compose,
310 static gint compose_remove_reedit_target (Compose *compose,
312 static void compose_remove_draft (Compose *compose);
313 static gint compose_queue_sub (Compose *compose,
317 gboolean perform_checks,
318 gboolean remove_reedit_target);
319 static int compose_add_attachments (Compose *compose,
321 static gchar *compose_get_header (Compose *compose);
322 static gchar *compose_get_manual_headers_info (Compose *compose);
324 static void compose_convert_header (Compose *compose,
329 gboolean addr_field);
331 static void compose_attach_info_free (AttachInfo *ainfo);
332 static void compose_attach_remove_selected (GtkAction *action,
335 static void compose_template_apply (Compose *compose,
338 static void compose_attach_property (GtkAction *action,
340 static void compose_attach_property_create (gboolean *cancelled);
341 static void attach_property_ok (GtkWidget *widget,
342 gboolean *cancelled);
343 static void attach_property_cancel (GtkWidget *widget,
344 gboolean *cancelled);
345 static gint attach_property_delete_event (GtkWidget *widget,
347 gboolean *cancelled);
348 static gboolean attach_property_key_pressed (GtkWidget *widget,
350 gboolean *cancelled);
352 static void compose_exec_ext_editor (Compose *compose);
354 static gint compose_exec_ext_editor_real (const gchar *file,
355 GdkNativeWindow socket_wid);
356 static gboolean compose_ext_editor_kill (Compose *compose);
357 static gboolean compose_input_cb (GIOChannel *source,
358 GIOCondition condition,
360 static void compose_set_ext_editor_sensitive (Compose *compose,
362 static gboolean compose_get_ext_editor_cmd_valid();
363 static gboolean compose_get_ext_editor_uses_socket();
364 static gboolean compose_ext_editor_plug_removed_cb
367 #endif /* G_OS_UNIX */
369 static void compose_undo_state_changed (UndoMain *undostruct,
374 static void compose_create_header_entry (Compose *compose);
375 static void compose_add_header_entry (Compose *compose, const gchar *header,
376 gchar *text, ComposePrefType pref_type);
377 static void compose_remove_header_entries(Compose *compose);
379 static void compose_update_priority_menu_item(Compose * compose);
381 static void compose_spell_menu_changed (void *data);
382 static void compose_dict_changed (void *data);
384 static void compose_add_field_list ( Compose *compose,
385 GList *listAddress );
387 /* callback functions */
389 static void compose_notebook_size_alloc (GtkNotebook *notebook,
390 GtkAllocation *allocation,
392 static gboolean compose_edit_size_alloc (GtkEditable *widget,
393 GtkAllocation *allocation,
394 GtkSHRuler *shruler);
395 static void account_activated (GtkComboBox *optmenu,
397 static void attach_selected (GtkTreeView *tree_view,
398 GtkTreePath *tree_path,
399 GtkTreeViewColumn *column,
401 static gboolean attach_button_pressed (GtkWidget *widget,
402 GdkEventButton *event,
404 static gboolean attach_key_pressed (GtkWidget *widget,
407 static void compose_send_cb (GtkAction *action, gpointer data);
408 static void compose_send_later_cb (GtkAction *action, gpointer data);
410 static void compose_save_cb (GtkAction *action,
413 static void compose_attach_cb (GtkAction *action,
415 static void compose_insert_file_cb (GtkAction *action,
417 static void compose_insert_sig_cb (GtkAction *action,
419 static void compose_replace_sig_cb (GtkAction *action,
422 static void compose_close_cb (GtkAction *action,
424 static void compose_print_cb (GtkAction *action,
427 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
429 static void compose_address_cb (GtkAction *action,
431 static void about_show_cb (GtkAction *action,
433 static void compose_template_activate_cb(GtkWidget *widget,
436 static void compose_ext_editor_cb (GtkAction *action,
439 static gint compose_delete_cb (GtkWidget *widget,
443 static void compose_undo_cb (GtkAction *action,
445 static void compose_redo_cb (GtkAction *action,
447 static void compose_cut_cb (GtkAction *action,
449 static void compose_copy_cb (GtkAction *action,
451 static void compose_paste_cb (GtkAction *action,
453 static void compose_paste_as_quote_cb (GtkAction *action,
455 static void compose_paste_no_wrap_cb (GtkAction *action,
457 static void compose_paste_wrap_cb (GtkAction *action,
459 static void compose_allsel_cb (GtkAction *action,
462 static void compose_advanced_action_cb (GtkAction *action,
465 static void compose_grab_focus_cb (GtkWidget *widget,
468 static void compose_changed_cb (GtkTextBuffer *textbuf,
471 static void compose_wrap_cb (GtkAction *action,
473 static void compose_wrap_all_cb (GtkAction *action,
475 static void compose_find_cb (GtkAction *action,
477 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
479 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
482 static void compose_toggle_ruler_cb (GtkToggleAction *action,
484 static void compose_toggle_sign_cb (GtkToggleAction *action,
486 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
488 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
489 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
490 static void activate_privacy_system (Compose *compose,
491 PrefsAccount *account,
493 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
495 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
497 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
498 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
499 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
501 static void compose_attach_drag_received_cb (GtkWidget *widget,
502 GdkDragContext *drag_context,
505 GtkSelectionData *data,
509 static void compose_insert_drag_received_cb (GtkWidget *widget,
510 GdkDragContext *drag_context,
513 GtkSelectionData *data,
517 static void compose_header_drag_received_cb (GtkWidget *widget,
518 GdkDragContext *drag_context,
521 GtkSelectionData *data,
526 static gboolean compose_drag_drop (GtkWidget *widget,
527 GdkDragContext *drag_context,
529 guint time, gpointer user_data);
530 static gboolean completion_set_focus_to_subject
535 static void text_inserted (GtkTextBuffer *buffer,
540 static Compose *compose_generic_reply(MsgInfo *msginfo,
541 ComposeQuoteMode quote_mode,
545 gboolean followup_and_reply_to,
548 static void compose_headerentry_changed_cb (GtkWidget *entry,
549 ComposeHeaderEntry *headerentry);
550 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
552 ComposeHeaderEntry *headerentry);
553 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
554 ComposeHeaderEntry *headerentry);
556 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
558 static void compose_allow_user_actions (Compose *compose, gboolean allow);
560 static void compose_nothing_cb (GtkAction *action, gpointer data)
566 static void compose_check_all (GtkAction *action, gpointer data);
567 static void compose_highlight_all (GtkAction *action, gpointer data);
568 static void compose_check_backwards (GtkAction *action, gpointer data);
569 static void compose_check_forwards_go (GtkAction *action, gpointer data);
572 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
574 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
577 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
578 FolderItem *folder_item);
580 static void compose_attach_update_label(Compose *compose);
581 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
582 gboolean respect_default_to);
583 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
584 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
586 static GtkActionEntry compose_popup_entries[] =
588 {"Compose", NULL, "Compose", NULL, NULL, NULL },
589 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
590 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
591 {"Compose/---", NULL, "---", NULL, NULL, NULL },
592 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
595 static GtkActionEntry compose_entries[] =
597 {"Menu", NULL, "Menu", NULL, NULL, NULL },
599 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
600 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
602 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
604 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
605 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
606 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
608 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
609 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
610 {"Message/---", NULL, "---", NULL, NULL, NULL },
612 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
613 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
614 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
615 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
616 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
617 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
618 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
619 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
620 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
621 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
624 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
625 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
626 {"Edit/---", NULL, "---", NULL, NULL, NULL },
628 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
629 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
630 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
632 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
633 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
634 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
635 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
637 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
639 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
640 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
641 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
642 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
643 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
644 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
645 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
646 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
647 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
648 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
649 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
650 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
651 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
652 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
653 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
655 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
656 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
658 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
659 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
660 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
661 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
662 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
665 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
666 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
667 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
668 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
670 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
671 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
675 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
676 {"Options/---", NULL, "---", NULL, NULL, NULL },
677 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
678 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
680 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
681 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
683 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
684 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
685 #define ENC_ACTION(cs_char,c_char,string) \
686 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
688 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
689 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
690 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
691 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
692 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
693 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
694 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
695 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
696 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
699 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
701 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
702 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
703 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
704 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
707 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
710 static GtkToggleActionEntry compose_toggle_entries[] =
712 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
713 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
714 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
715 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
716 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
717 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
718 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
721 static GtkRadioActionEntry compose_radio_rm_entries[] =
723 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
724 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
725 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
729 static GtkRadioActionEntry compose_radio_prio_entries[] =
731 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
732 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
733 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
738 static GtkRadioActionEntry compose_radio_enc_entries[] =
740 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
775 static GtkTargetEntry compose_mime_types[] =
777 {"text/uri-list", 0, 0},
778 {"UTF8_STRING", 0, 0},
782 static gboolean compose_put_existing_to_front(MsgInfo *info)
784 const GList *compose_list = compose_get_compose_list();
785 const GList *elem = NULL;
788 for (elem = compose_list; elem != NULL && elem->data != NULL;
790 Compose *c = (Compose*)elem->data;
792 if (!c->targetinfo || !c->targetinfo->msgid ||
796 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
797 gtkut_window_popup(c->window);
805 static GdkColor quote_color1 =
806 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
807 static GdkColor quote_color2 =
808 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
809 static GdkColor quote_color3 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
812 static GdkColor quote_bgcolor1 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_bgcolor2 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor3 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor signature_color = {
826 static GdkColor uri_color = {
833 static void compose_create_tags(GtkTextView *text, Compose *compose)
835 GtkTextBuffer *buffer;
836 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
837 #if !GTK_CHECK_VERSION(2, 24, 0)
844 buffer = gtk_text_view_get_buffer(text);
846 if (prefs_common.enable_color) {
847 /* grab the quote colors, converting from an int to a GdkColor */
848 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
850 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
852 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
854 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
856 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
858 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
860 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
862 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
865 signature_color = quote_color1 = quote_color2 = quote_color3 =
866 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
869 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
870 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
871 "foreground-gdk", "e_color1,
872 "paragraph-background-gdk", "e_bgcolor1,
874 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
875 "foreground-gdk", "e_color2,
876 "paragraph-background-gdk", "e_bgcolor2,
878 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
879 "foreground-gdk", "e_color3,
880 "paragraph-background-gdk", "e_bgcolor3,
883 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
884 "foreground-gdk", "e_color1,
886 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
887 "foreground-gdk", "e_color2,
889 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
890 "foreground-gdk", "e_color3,
894 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
895 "foreground-gdk", &signature_color,
898 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
899 "foreground-gdk", &uri_color,
901 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
902 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
904 #if !GTK_CHECK_VERSION(2, 24, 0)
905 color[0] = quote_color1;
906 color[1] = quote_color2;
907 color[2] = quote_color3;
908 color[3] = quote_bgcolor1;
909 color[4] = quote_bgcolor2;
910 color[5] = quote_bgcolor3;
911 color[6] = signature_color;
912 color[7] = uri_color;
914 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
915 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
917 for (i = 0; i < 8; i++) {
918 if (success[i] == FALSE) {
919 g_warning("Compose: color allocation failed.");
920 quote_color1 = quote_color2 = quote_color3 =
921 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
922 signature_color = uri_color = black;
928 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
931 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
934 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
936 return compose_generic_new(account, mailto, item, NULL, NULL);
939 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
941 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
944 #define SCROLL_TO_CURSOR(compose) { \
945 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
946 gtk_text_view_get_buffer( \
947 GTK_TEXT_VIEW(compose->text))); \
948 gtk_text_view_scroll_mark_onscreen( \
949 GTK_TEXT_VIEW(compose->text), \
953 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
956 if (folderidentifier) {
957 #if !GTK_CHECK_VERSION(2, 24, 0)
958 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
960 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
962 prefs_common.compose_save_to_history = add_history(
963 prefs_common.compose_save_to_history, folderidentifier);
964 #if !GTK_CHECK_VERSION(2, 24, 0)
965 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
966 prefs_common.compose_save_to_history);
968 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
969 prefs_common.compose_save_to_history);
973 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
974 if (folderidentifier)
975 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
977 gtk_entry_set_text(GTK_ENTRY(entry), "");
980 static gchar *compose_get_save_to(Compose *compose)
983 gchar *result = NULL;
984 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
985 result = gtk_editable_get_chars(entry, 0, -1);
988 #if !GTK_CHECK_VERSION(2, 24, 0)
989 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
991 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
993 prefs_common.compose_save_to_history = add_history(
994 prefs_common.compose_save_to_history, result);
995 #if !GTK_CHECK_VERSION(2, 24, 0)
996 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
997 prefs_common.compose_save_to_history);
999 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1000 prefs_common.compose_save_to_history);
1006 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1007 GList *attach_files, GList *listAddress )
1010 GtkTextView *textview;
1011 GtkTextBuffer *textbuf;
1013 const gchar *subject_format = NULL;
1014 const gchar *body_format = NULL;
1015 gchar *mailto_from = NULL;
1016 PrefsAccount *mailto_account = NULL;
1017 MsgInfo* dummyinfo = NULL;
1018 gint cursor_pos = -1;
1019 MailField mfield = NO_FIELD_PRESENT;
1023 /* check if mailto defines a from */
1024 if (mailto && *mailto != '\0') {
1025 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1026 /* mailto defines a from, check if we can get account prefs from it,
1027 if not, the account prefs will be guessed using other ways, but we'll keep
1030 mailto_account = account_find_from_address(mailto_from, TRUE);
1031 if (mailto_account == NULL) {
1033 Xstrdup_a(tmp_from, mailto_from, return NULL);
1034 extract_address(tmp_from);
1035 mailto_account = account_find_from_address(tmp_from, TRUE);
1039 account = mailto_account;
1042 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1043 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1044 account = account_find_from_id(item->prefs->default_account);
1046 /* if no account prefs set, fallback to the current one */
1047 if (!account) account = cur_account;
1048 cm_return_val_if_fail(account != NULL, NULL);
1050 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1052 /* override from name if mailto asked for it */
1054 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1055 g_free(mailto_from);
1057 /* override from name according to folder properties */
1058 if (item && item->prefs &&
1059 item->prefs->compose_with_format &&
1060 item->prefs->compose_override_from_format &&
1061 *item->prefs->compose_override_from_format != '\0') {
1066 dummyinfo = compose_msginfo_new_from_compose(compose);
1068 /* decode \-escape sequences in the internal representation of the quote format */
1069 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1070 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1073 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1074 compose->gtkaspell);
1076 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1078 quote_fmt_scan_string(tmp);
1081 buf = quote_fmt_get_buffer();
1083 alertpanel_error(_("New message From format error."));
1085 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1086 quote_fmt_reset_vartable();
1087 quote_fmtlex_destroy();
1092 compose->replyinfo = NULL;
1093 compose->fwdinfo = NULL;
1095 textview = GTK_TEXT_VIEW(compose->text);
1096 textbuf = gtk_text_view_get_buffer(textview);
1097 compose_create_tags(textview, compose);
1099 undo_block(compose->undostruct);
1101 compose_set_dictionaries_from_folder_prefs(compose, item);
1104 if (account->auto_sig)
1105 compose_insert_sig(compose, FALSE);
1106 gtk_text_buffer_get_start_iter(textbuf, &iter);
1107 gtk_text_buffer_place_cursor(textbuf, &iter);
1109 if (account->protocol != A_NNTP) {
1110 if (mailto && *mailto != '\0') {
1111 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1114 compose_set_folder_prefs(compose, item, TRUE);
1116 if (item && item->ret_rcpt) {
1117 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1120 if (mailto && *mailto != '\0') {
1121 if (!strchr(mailto, '@'))
1122 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1124 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1125 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1126 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1127 mfield = TO_FIELD_PRESENT;
1130 * CLAWS: just don't allow return receipt request, even if the user
1131 * may want to send an email. simple but foolproof.
1133 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1135 compose_add_field_list( compose, listAddress );
1137 if (item && item->prefs && item->prefs->compose_with_format) {
1138 subject_format = item->prefs->compose_subject_format;
1139 body_format = item->prefs->compose_body_format;
1140 } else if (account->compose_with_format) {
1141 subject_format = account->compose_subject_format;
1142 body_format = account->compose_body_format;
1143 } else if (prefs_common.compose_with_format) {
1144 subject_format = prefs_common.compose_subject_format;
1145 body_format = prefs_common.compose_body_format;
1148 if (subject_format || body_format) {
1151 && *subject_format != '\0' )
1153 gchar *subject = NULL;
1158 dummyinfo = compose_msginfo_new_from_compose(compose);
1160 /* decode \-escape sequences in the internal representation of the quote format */
1161 tmp = g_malloc(strlen(subject_format)+1);
1162 pref_get_unescaped_pref(tmp, subject_format);
1164 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1166 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1167 compose->gtkaspell);
1169 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1171 quote_fmt_scan_string(tmp);
1174 buf = quote_fmt_get_buffer();
1176 alertpanel_error(_("New message subject format error."));
1178 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1179 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1180 quote_fmt_reset_vartable();
1181 quote_fmtlex_destroy();
1185 mfield = SUBJECT_FIELD_PRESENT;
1189 && *body_format != '\0' )
1192 GtkTextBuffer *buffer;
1193 GtkTextIter start, end;
1197 dummyinfo = compose_msginfo_new_from_compose(compose);
1199 text = GTK_TEXT_VIEW(compose->text);
1200 buffer = gtk_text_view_get_buffer(text);
1201 gtk_text_buffer_get_start_iter(buffer, &start);
1202 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1203 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1205 compose_quote_fmt(compose, dummyinfo,
1207 NULL, tmp, FALSE, TRUE,
1208 _("The body of the \"New message\" template has an error at line %d."));
1209 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1210 quote_fmt_reset_vartable();
1214 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1215 gtkaspell_highlight_all(compose->gtkaspell);
1217 mfield = BODY_FIELD_PRESENT;
1221 procmsg_msginfo_free( &dummyinfo );
1227 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1228 ainfo = (AttachInfo *) curr->data;
1229 compose_attach_append(compose, ainfo->file, ainfo->file,
1230 ainfo->content_type, ainfo->charset);
1234 compose_show_first_last_header(compose, TRUE);
1236 /* Set save folder */
1237 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1238 gchar *folderidentifier;
1240 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1241 folderidentifier = folder_item_get_identifier(item);
1242 compose_set_save_to(compose, folderidentifier);
1243 g_free(folderidentifier);
1246 /* Place cursor according to provided input (mfield) */
1248 case NO_FIELD_PRESENT:
1249 if (compose->header_last)
1250 gtk_widget_grab_focus(compose->header_last->entry);
1252 case TO_FIELD_PRESENT:
1253 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1255 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1258 gtk_widget_grab_focus(compose->subject_entry);
1260 case SUBJECT_FIELD_PRESENT:
1261 textview = GTK_TEXT_VIEW(compose->text);
1264 textbuf = gtk_text_view_get_buffer(textview);
1267 mark = gtk_text_buffer_get_insert(textbuf);
1268 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1269 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1271 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1272 * only defers where it comes to the variable body
1273 * is not null. If no body is present compose->text
1274 * will be null in which case you cannot place the
1275 * cursor inside the component so. An empty component
1276 * is therefore created before placing the cursor
1278 case BODY_FIELD_PRESENT:
1279 cursor_pos = quote_fmt_get_cursor_pos();
1280 if (cursor_pos == -1)
1281 gtk_widget_grab_focus(compose->header_last->entry);
1283 gtk_widget_grab_focus(compose->text);
1287 undo_unblock(compose->undostruct);
1289 if (prefs_common.auto_exteditor)
1290 compose_exec_ext_editor(compose);
1292 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1294 SCROLL_TO_CURSOR(compose);
1296 compose->modified = FALSE;
1297 compose_set_title(compose);
1299 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1304 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1305 gboolean override_pref, const gchar *system)
1307 const gchar *privacy = NULL;
1309 cm_return_if_fail(compose != NULL);
1310 cm_return_if_fail(account != NULL);
1312 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1315 if (account->default_privacy_system && strlen(account->default_privacy_system))
1316 privacy = account->default_privacy_system;
1320 GSList *privacy_avail = privacy_get_system_ids();
1321 if (privacy_avail && g_slist_length(privacy_avail)) {
1322 privacy = (gchar *)(privacy_avail->data);
1324 g_slist_free_full(privacy_avail, g_free);
1326 if (privacy != NULL) {
1328 g_free(compose->privacy_system);
1329 compose->privacy_system = NULL;
1330 g_free(compose->encdata);
1331 compose->encdata = NULL;
1333 if (compose->privacy_system == NULL)
1334 compose->privacy_system = g_strdup(privacy);
1335 else if (*(compose->privacy_system) == '\0') {
1336 g_free(compose->privacy_system);
1337 g_free(compose->encdata);
1338 compose->encdata = NULL;
1339 compose->privacy_system = g_strdup(privacy);
1341 compose_update_privacy_system_menu_item(compose, FALSE);
1342 compose_use_encryption(compose, TRUE);
1346 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1348 const gchar *privacy = NULL;
1350 if (account->default_privacy_system && strlen(account->default_privacy_system))
1351 privacy = account->default_privacy_system;
1355 GSList *privacy_avail = privacy_get_system_ids();
1356 if (privacy_avail && g_slist_length(privacy_avail)) {
1357 privacy = (gchar *)(privacy_avail->data);
1361 if (privacy != NULL) {
1363 g_free(compose->privacy_system);
1364 compose->privacy_system = NULL;
1365 g_free(compose->encdata);
1366 compose->encdata = NULL;
1368 if (compose->privacy_system == NULL)
1369 compose->privacy_system = g_strdup(privacy);
1370 compose_update_privacy_system_menu_item(compose, FALSE);
1371 compose_use_signing(compose, TRUE);
1375 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1379 Compose *compose = NULL;
1381 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1383 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1384 cm_return_val_if_fail(msginfo != NULL, NULL);
1386 list_len = g_slist_length(msginfo_list);
1390 case COMPOSE_REPLY_TO_ADDRESS:
1391 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1392 FALSE, prefs_common.default_reply_list, FALSE, body);
1394 case COMPOSE_REPLY_WITH_QUOTE:
1395 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1396 FALSE, prefs_common.default_reply_list, FALSE, body);
1398 case COMPOSE_REPLY_WITHOUT_QUOTE:
1399 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1400 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1402 case COMPOSE_REPLY_TO_SENDER:
1403 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1404 FALSE, FALSE, TRUE, body);
1406 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1407 compose = compose_followup_and_reply_to(msginfo,
1408 COMPOSE_QUOTE_CHECK,
1409 FALSE, FALSE, body);
1411 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1412 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1413 FALSE, FALSE, TRUE, body);
1415 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1416 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1417 FALSE, FALSE, TRUE, NULL);
1419 case COMPOSE_REPLY_TO_ALL:
1420 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1421 TRUE, FALSE, FALSE, body);
1423 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1424 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1425 TRUE, FALSE, FALSE, body);
1427 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1428 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1429 TRUE, FALSE, FALSE, NULL);
1431 case COMPOSE_REPLY_TO_LIST:
1432 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1433 FALSE, TRUE, FALSE, body);
1435 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1436 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1437 FALSE, TRUE, FALSE, body);
1439 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1440 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1441 FALSE, TRUE, FALSE, NULL);
1443 case COMPOSE_FORWARD:
1444 if (prefs_common.forward_as_attachment) {
1445 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1448 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1452 case COMPOSE_FORWARD_INLINE:
1453 /* check if we reply to more than one Message */
1454 if (list_len == 1) {
1455 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1458 /* more messages FALL THROUGH */
1459 case COMPOSE_FORWARD_AS_ATTACH:
1460 compose = compose_forward_multiple(NULL, msginfo_list);
1462 case COMPOSE_REDIRECT:
1463 compose = compose_redirect(NULL, msginfo, FALSE);
1466 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1469 if (compose == NULL) {
1470 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1474 compose->rmode = mode;
1475 switch (compose->rmode) {
1477 case COMPOSE_REPLY_WITH_QUOTE:
1478 case COMPOSE_REPLY_WITHOUT_QUOTE:
1479 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1480 debug_print("reply mode Normal\n");
1481 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1482 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1484 case COMPOSE_REPLY_TO_SENDER:
1485 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1486 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1487 debug_print("reply mode Sender\n");
1488 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1490 case COMPOSE_REPLY_TO_ALL:
1491 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1492 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1493 debug_print("reply mode All\n");
1494 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1496 case COMPOSE_REPLY_TO_LIST:
1497 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1498 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1499 debug_print("reply mode List\n");
1500 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1502 case COMPOSE_REPLY_TO_ADDRESS:
1503 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1511 static Compose *compose_reply(MsgInfo *msginfo,
1512 ComposeQuoteMode quote_mode,
1518 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1519 to_sender, FALSE, body);
1522 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1523 ComposeQuoteMode quote_mode,
1528 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1529 to_sender, TRUE, body);
1532 static void compose_extract_original_charset(Compose *compose)
1534 MsgInfo *info = NULL;
1535 if (compose->replyinfo) {
1536 info = compose->replyinfo;
1537 } else if (compose->fwdinfo) {
1538 info = compose->fwdinfo;
1539 } else if (compose->targetinfo) {
1540 info = compose->targetinfo;
1543 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1544 MimeInfo *partinfo = mimeinfo;
1545 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1546 partinfo = procmime_mimeinfo_next(partinfo);
1548 compose->orig_charset =
1549 g_strdup(procmime_mimeinfo_get_parameter(
1550 partinfo, "charset"));
1552 procmime_mimeinfo_free_all(&mimeinfo);
1556 #define SIGNAL_BLOCK(buffer) { \
1557 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1558 G_CALLBACK(compose_changed_cb), \
1560 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1561 G_CALLBACK(text_inserted), \
1565 #define SIGNAL_UNBLOCK(buffer) { \
1566 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1567 G_CALLBACK(compose_changed_cb), \
1569 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1570 G_CALLBACK(text_inserted), \
1574 static Compose *compose_generic_reply(MsgInfo *msginfo,
1575 ComposeQuoteMode quote_mode,
1576 gboolean to_all, gboolean to_ml,
1578 gboolean followup_and_reply_to,
1582 PrefsAccount *account = NULL;
1583 GtkTextView *textview;
1584 GtkTextBuffer *textbuf;
1585 gboolean quote = FALSE;
1586 const gchar *qmark = NULL;
1587 const gchar *body_fmt = NULL;
1588 gchar *s_system = NULL;
1590 cm_return_val_if_fail(msginfo != NULL, NULL);
1591 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1593 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1595 cm_return_val_if_fail(account != NULL, NULL);
1597 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1599 compose->updating = TRUE;
1601 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1602 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1604 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1605 if (!compose->replyinfo)
1606 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1608 compose_extract_original_charset(compose);
1610 if (msginfo->folder && msginfo->folder->ret_rcpt)
1611 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1613 /* Set save folder */
1614 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1615 gchar *folderidentifier;
1617 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1618 folderidentifier = folder_item_get_identifier(msginfo->folder);
1619 compose_set_save_to(compose, folderidentifier);
1620 g_free(folderidentifier);
1623 if (compose_parse_header(compose, msginfo) < 0) {
1624 compose->updating = FALSE;
1625 compose_destroy(compose);
1629 /* override from name according to folder properties */
1630 if (msginfo->folder && msginfo->folder->prefs &&
1631 msginfo->folder->prefs->reply_with_format &&
1632 msginfo->folder->prefs->reply_override_from_format &&
1633 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1638 /* decode \-escape sequences in the internal representation of the quote format */
1639 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1640 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1643 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1644 compose->gtkaspell);
1646 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1648 quote_fmt_scan_string(tmp);
1651 buf = quote_fmt_get_buffer();
1653 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1655 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1656 quote_fmt_reset_vartable();
1657 quote_fmtlex_destroy();
1662 textview = (GTK_TEXT_VIEW(compose->text));
1663 textbuf = gtk_text_view_get_buffer(textview);
1664 compose_create_tags(textview, compose);
1666 undo_block(compose->undostruct);
1668 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1669 gtkaspell_block_check(compose->gtkaspell);
1672 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1673 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1674 /* use the reply format of folder (if enabled), or the account's one
1675 (if enabled) or fallback to the global reply format, which is always
1676 enabled (even if empty), and use the relevant quotemark */
1678 if (msginfo->folder && msginfo->folder->prefs &&
1679 msginfo->folder->prefs->reply_with_format) {
1680 qmark = msginfo->folder->prefs->reply_quotemark;
1681 body_fmt = msginfo->folder->prefs->reply_body_format;
1683 } else if (account->reply_with_format) {
1684 qmark = account->reply_quotemark;
1685 body_fmt = account->reply_body_format;
1688 qmark = prefs_common.quotemark;
1689 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1690 body_fmt = gettext(prefs_common.quotefmt);
1697 /* empty quotemark is not allowed */
1698 if (qmark == NULL || *qmark == '\0')
1700 compose_quote_fmt(compose, compose->replyinfo,
1701 body_fmt, qmark, body, FALSE, TRUE,
1702 _("The body of the \"Reply\" template has an error at line %d."));
1703 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1704 quote_fmt_reset_vartable();
1707 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1708 compose_force_encryption(compose, account, FALSE, s_system);
1711 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1712 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1713 compose_force_signing(compose, account, s_system);
1717 SIGNAL_BLOCK(textbuf);
1719 if (account->auto_sig)
1720 compose_insert_sig(compose, FALSE);
1722 compose_wrap_all(compose);
1725 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1726 gtkaspell_highlight_all(compose->gtkaspell);
1727 gtkaspell_unblock_check(compose->gtkaspell);
1729 SIGNAL_UNBLOCK(textbuf);
1731 gtk_widget_grab_focus(compose->text);
1733 undo_unblock(compose->undostruct);
1735 if (prefs_common.auto_exteditor)
1736 compose_exec_ext_editor(compose);
1738 compose->modified = FALSE;
1739 compose_set_title(compose);
1741 compose->updating = FALSE;
1742 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1743 SCROLL_TO_CURSOR(compose);
1745 if (compose->deferred_destroy) {
1746 compose_destroy(compose);
1754 #define INSERT_FW_HEADER(var, hdr) \
1755 if (msginfo->var && *msginfo->var) { \
1756 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1757 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1758 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1761 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1762 gboolean as_attach, const gchar *body,
1763 gboolean no_extedit,
1767 GtkTextView *textview;
1768 GtkTextBuffer *textbuf;
1769 gint cursor_pos = -1;
1772 cm_return_val_if_fail(msginfo != NULL, NULL);
1773 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1775 if (!account && !(account = compose_find_account(msginfo)))
1776 account = cur_account;
1778 if (!prefs_common.forward_as_attachment)
1779 mode = COMPOSE_FORWARD_INLINE;
1781 mode = COMPOSE_FORWARD;
1782 compose = compose_create(account, msginfo->folder, mode, batch);
1784 compose->updating = TRUE;
1785 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1786 if (!compose->fwdinfo)
1787 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1789 compose_extract_original_charset(compose);
1791 if (msginfo->subject && *msginfo->subject) {
1792 gchar *buf, *buf2, *p;
1794 buf = p = g_strdup(msginfo->subject);
1795 p += subject_get_prefix_length(p);
1796 memmove(buf, p, strlen(p) + 1);
1798 buf2 = g_strdup_printf("Fw: %s", buf);
1799 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1805 /* override from name according to folder properties */
1806 if (msginfo->folder && msginfo->folder->prefs &&
1807 msginfo->folder->prefs->forward_with_format &&
1808 msginfo->folder->prefs->forward_override_from_format &&
1809 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1813 MsgInfo *full_msginfo = NULL;
1816 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1818 full_msginfo = procmsg_msginfo_copy(msginfo);
1820 /* decode \-escape sequences in the internal representation of the quote format */
1821 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1822 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1825 gtkaspell_block_check(compose->gtkaspell);
1826 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1827 compose->gtkaspell);
1829 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1831 quote_fmt_scan_string(tmp);
1834 buf = quote_fmt_get_buffer();
1836 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1838 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1839 quote_fmt_reset_vartable();
1840 quote_fmtlex_destroy();
1843 procmsg_msginfo_free(&full_msginfo);
1846 textview = GTK_TEXT_VIEW(compose->text);
1847 textbuf = gtk_text_view_get_buffer(textview);
1848 compose_create_tags(textview, compose);
1850 undo_block(compose->undostruct);
1854 msgfile = procmsg_get_message_file(msginfo);
1855 if (!is_file_exist(msgfile))
1856 g_warning("%s: file does not exist", msgfile);
1858 compose_attach_append(compose, msgfile, msgfile,
1859 "message/rfc822", NULL);
1863 const gchar *qmark = NULL;
1864 const gchar *body_fmt = NULL;
1865 MsgInfo *full_msginfo;
1867 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1869 full_msginfo = procmsg_msginfo_copy(msginfo);
1871 /* use the forward format of folder (if enabled), or the account's one
1872 (if enabled) or fallback to the global forward format, which is always
1873 enabled (even if empty), and use the relevant quotemark */
1874 if (msginfo->folder && msginfo->folder->prefs &&
1875 msginfo->folder->prefs->forward_with_format) {
1876 qmark = msginfo->folder->prefs->forward_quotemark;
1877 body_fmt = msginfo->folder->prefs->forward_body_format;
1879 } else if (account->forward_with_format) {
1880 qmark = account->forward_quotemark;
1881 body_fmt = account->forward_body_format;
1884 qmark = prefs_common.fw_quotemark;
1885 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1886 body_fmt = gettext(prefs_common.fw_quotefmt);
1891 /* empty quotemark is not allowed */
1892 if (qmark == NULL || *qmark == '\0')
1895 compose_quote_fmt(compose, full_msginfo,
1896 body_fmt, qmark, body, FALSE, TRUE,
1897 _("The body of the \"Forward\" template has an error at line %d."));
1898 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1899 quote_fmt_reset_vartable();
1900 compose_attach_parts(compose, msginfo);
1902 procmsg_msginfo_free(&full_msginfo);
1905 SIGNAL_BLOCK(textbuf);
1907 if (account->auto_sig)
1908 compose_insert_sig(compose, FALSE);
1910 compose_wrap_all(compose);
1913 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1914 gtkaspell_highlight_all(compose->gtkaspell);
1915 gtkaspell_unblock_check(compose->gtkaspell);
1917 SIGNAL_UNBLOCK(textbuf);
1919 cursor_pos = quote_fmt_get_cursor_pos();
1920 if (cursor_pos == -1)
1921 gtk_widget_grab_focus(compose->header_last->entry);
1923 gtk_widget_grab_focus(compose->text);
1925 if (!no_extedit && prefs_common.auto_exteditor)
1926 compose_exec_ext_editor(compose);
1929 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1930 gchar *folderidentifier;
1932 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1933 folderidentifier = folder_item_get_identifier(msginfo->folder);
1934 compose_set_save_to(compose, folderidentifier);
1935 g_free(folderidentifier);
1938 undo_unblock(compose->undostruct);
1940 compose->modified = FALSE;
1941 compose_set_title(compose);
1943 compose->updating = FALSE;
1944 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1945 SCROLL_TO_CURSOR(compose);
1947 if (compose->deferred_destroy) {
1948 compose_destroy(compose);
1952 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1957 #undef INSERT_FW_HEADER
1959 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1962 GtkTextView *textview;
1963 GtkTextBuffer *textbuf;
1967 gboolean single_mail = TRUE;
1969 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1971 if (g_slist_length(msginfo_list) > 1)
1972 single_mail = FALSE;
1974 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1975 if (((MsgInfo *)msginfo->data)->folder == NULL)
1978 /* guess account from first selected message */
1980 !(account = compose_find_account(msginfo_list->data)))
1981 account = cur_account;
1983 cm_return_val_if_fail(account != NULL, NULL);
1985 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1986 if (msginfo->data) {
1987 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1988 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1992 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1993 g_warning("no msginfo_list");
1997 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1999 compose->updating = TRUE;
2001 /* override from name according to folder properties */
2002 if (msginfo_list->data) {
2003 MsgInfo *msginfo = msginfo_list->data;
2005 if (msginfo->folder && msginfo->folder->prefs &&
2006 msginfo->folder->prefs->forward_with_format &&
2007 msginfo->folder->prefs->forward_override_from_format &&
2008 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2013 /* decode \-escape sequences in the internal representation of the quote format */
2014 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2015 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2019 compose->gtkaspell);
2021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2023 quote_fmt_scan_string(tmp);
2026 buf = quote_fmt_get_buffer();
2028 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2031 quote_fmt_reset_vartable();
2032 quote_fmtlex_destroy();
2038 textview = GTK_TEXT_VIEW(compose->text);
2039 textbuf = gtk_text_view_get_buffer(textview);
2040 compose_create_tags(textview, compose);
2042 undo_block(compose->undostruct);
2043 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2044 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2046 if (!is_file_exist(msgfile))
2047 g_warning("%s: file does not exist", msgfile);
2049 compose_attach_append(compose, msgfile, msgfile,
2050 "message/rfc822", NULL);
2055 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2056 if (info->subject && *info->subject) {
2057 gchar *buf, *buf2, *p;
2059 buf = p = g_strdup(info->subject);
2060 p += subject_get_prefix_length(p);
2061 memmove(buf, p, strlen(p) + 1);
2063 buf2 = g_strdup_printf("Fw: %s", buf);
2064 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2070 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2071 _("Fw: multiple emails"));
2074 SIGNAL_BLOCK(textbuf);
2076 if (account->auto_sig)
2077 compose_insert_sig(compose, FALSE);
2079 compose_wrap_all(compose);
2081 SIGNAL_UNBLOCK(textbuf);
2083 gtk_text_buffer_get_start_iter(textbuf, &iter);
2084 gtk_text_buffer_place_cursor(textbuf, &iter);
2086 if (prefs_common.auto_exteditor)
2087 compose_exec_ext_editor(compose);
2089 gtk_widget_grab_focus(compose->header_last->entry);
2090 undo_unblock(compose->undostruct);
2091 compose->modified = FALSE;
2092 compose_set_title(compose);
2094 compose->updating = FALSE;
2095 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2096 SCROLL_TO_CURSOR(compose);
2098 if (compose->deferred_destroy) {
2099 compose_destroy(compose);
2103 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2108 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2110 GtkTextIter start = *iter;
2111 GtkTextIter end_iter;
2112 int start_pos = gtk_text_iter_get_offset(&start);
2114 if (!compose->account->sig_sep)
2117 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2118 start_pos+strlen(compose->account->sig_sep));
2120 /* check sig separator */
2121 str = gtk_text_iter_get_text(&start, &end_iter);
2122 if (!strcmp(str, compose->account->sig_sep)) {
2124 /* check end of line (\n) */
2125 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2126 start_pos+strlen(compose->account->sig_sep));
2127 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2128 start_pos+strlen(compose->account->sig_sep)+1);
2129 tmp = gtk_text_iter_get_text(&start, &end_iter);
2130 if (!strcmp(tmp,"\n")) {
2142 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2144 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2145 Compose *compose = (Compose *)data;
2146 FolderItem *old_item = NULL;
2147 FolderItem *new_item = NULL;
2148 gchar *old_id, *new_id;
2150 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2151 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2154 old_item = hookdata->item;
2155 new_item = hookdata->item2;
2157 old_id = folder_item_get_identifier(old_item);
2158 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2160 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2161 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2162 compose->targetinfo->folder = new_item;
2165 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2166 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2167 compose->replyinfo->folder = new_item;
2170 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2171 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2172 compose->fwdinfo->folder = new_item;
2180 static void compose_colorize_signature(Compose *compose)
2182 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2184 GtkTextIter end_iter;
2185 gtk_text_buffer_get_start_iter(buffer, &iter);
2186 while (gtk_text_iter_forward_line(&iter))
2187 if (compose_is_sig_separator(compose, buffer, &iter)) {
2188 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2189 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2193 #define BLOCK_WRAP() { \
2194 prev_autowrap = compose->autowrap; \
2195 buffer = gtk_text_view_get_buffer( \
2196 GTK_TEXT_VIEW(compose->text)); \
2197 compose->autowrap = FALSE; \
2199 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2200 G_CALLBACK(compose_changed_cb), \
2202 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2203 G_CALLBACK(text_inserted), \
2206 #define UNBLOCK_WRAP() { \
2207 compose->autowrap = prev_autowrap; \
2208 if (compose->autowrap) { \
2209 gint old = compose->draft_timeout_tag; \
2210 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2211 compose_wrap_all(compose); \
2212 compose->draft_timeout_tag = old; \
2215 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2216 G_CALLBACK(compose_changed_cb), \
2218 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2219 G_CALLBACK(text_inserted), \
2223 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2225 Compose *compose = NULL;
2226 PrefsAccount *account = NULL;
2227 GtkTextView *textview;
2228 GtkTextBuffer *textbuf;
2232 gboolean use_signing = FALSE;
2233 gboolean use_encryption = FALSE;
2234 gchar *privacy_system = NULL;
2235 int priority = PRIORITY_NORMAL;
2236 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2237 gboolean autowrap = prefs_common.autowrap;
2238 gboolean autoindent = prefs_common.auto_indent;
2239 HeaderEntry *manual_headers = NULL;
2241 cm_return_val_if_fail(msginfo != NULL, NULL);
2242 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2244 if (compose_put_existing_to_front(msginfo)) {
2248 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2249 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2250 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2251 gchar *queueheader_buf = NULL;
2254 /* Select Account from queue headers */
2255 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2256 "X-Claws-Account-Id:")) {
2257 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2258 account = account_find_from_id(id);
2259 g_free(queueheader_buf);
2261 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2262 "X-Sylpheed-Account-Id:")) {
2263 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2264 account = account_find_from_id(id);
2265 g_free(queueheader_buf);
2267 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2269 id = atoi(&queueheader_buf[strlen("NAID:")]);
2270 account = account_find_from_id(id);
2271 g_free(queueheader_buf);
2273 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2275 id = atoi(&queueheader_buf[strlen("MAID:")]);
2276 account = account_find_from_id(id);
2277 g_free(queueheader_buf);
2279 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2281 account = account_find_from_address(queueheader_buf, FALSE);
2282 g_free(queueheader_buf);
2284 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2286 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2287 use_signing = param;
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Sylpheed-Sign:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2293 use_signing = param;
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 "X-Claws-Encrypt:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2299 use_encryption = param;
2300 g_free(queueheader_buf);
2302 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 "X-Sylpheed-Encrypt:")) {
2304 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2305 use_encryption = param;
2306 g_free(queueheader_buf);
2308 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2309 "X-Claws-Auto-Wrapping:")) {
2310 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2312 g_free(queueheader_buf);
2314 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2315 "X-Claws-Auto-Indent:")) {
2316 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2318 g_free(queueheader_buf);
2320 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2321 "X-Claws-Privacy-System:")) {
2322 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2323 g_free(queueheader_buf);
2325 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2326 "X-Sylpheed-Privacy-System:")) {
2327 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2328 g_free(queueheader_buf);
2330 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2332 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2334 g_free(queueheader_buf);
2336 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2338 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2339 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2340 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2341 if (orig_item != NULL) {
2342 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2346 g_free(queueheader_buf);
2348 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2350 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2351 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2352 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2353 if (orig_item != NULL) {
2354 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2358 g_free(queueheader_buf);
2360 /* Get manual headers */
2361 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2362 "X-Claws-Manual-Headers:")) {
2363 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2364 if (listmh && *listmh != '\0') {
2365 debug_print("Got manual headers: %s\n", listmh);
2366 manual_headers = procheader_entries_from_str(listmh);
2369 g_free(queueheader_buf);
2372 account = msginfo->folder->folder->account;
2375 if (!account && prefs_common.reedit_account_autosel) {
2377 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2378 extract_address(from);
2379 account = account_find_from_address(from, FALSE);
2384 account = cur_account;
2386 cm_return_val_if_fail(account != NULL, NULL);
2388 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2390 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2391 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2392 compose->autowrap = autowrap;
2393 compose->replyinfo = replyinfo;
2394 compose->fwdinfo = fwdinfo;
2396 compose->updating = TRUE;
2397 compose->priority = priority;
2399 if (privacy_system != NULL) {
2400 compose->privacy_system = privacy_system;
2401 compose_use_signing(compose, use_signing);
2402 compose_use_encryption(compose, use_encryption);
2403 compose_update_privacy_system_menu_item(compose, FALSE);
2405 activate_privacy_system(compose, account, FALSE);
2408 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2410 compose_extract_original_charset(compose);
2412 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2413 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2414 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2415 gchar *queueheader_buf = NULL;
2417 /* Set message save folder */
2418 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2419 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2420 compose_set_save_to(compose, &queueheader_buf[4]);
2421 g_free(queueheader_buf);
2423 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2424 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2426 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2428 g_free(queueheader_buf);
2432 if (compose_parse_header(compose, msginfo) < 0) {
2433 compose->updating = FALSE;
2434 compose_destroy(compose);
2437 compose_reedit_set_entry(compose, msginfo);
2439 textview = GTK_TEXT_VIEW(compose->text);
2440 textbuf = gtk_text_view_get_buffer(textview);
2441 compose_create_tags(textview, compose);
2443 mark = gtk_text_buffer_get_insert(textbuf);
2444 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2446 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2447 G_CALLBACK(compose_changed_cb),
2450 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2451 fp = procmime_get_first_encrypted_text_content(msginfo);
2453 compose_force_encryption(compose, account, TRUE, NULL);
2456 fp = procmime_get_first_text_content(msginfo);
2459 g_warning("Can't get text part");
2463 gchar buf[BUFFSIZE];
2464 gboolean prev_autowrap;
2465 GtkTextBuffer *buffer;
2467 while (fgets(buf, sizeof(buf), fp) != NULL) {
2469 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2475 compose_attach_parts(compose, msginfo);
2477 compose_colorize_signature(compose);
2479 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2480 G_CALLBACK(compose_changed_cb),
2483 if (manual_headers != NULL) {
2484 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2485 procheader_entries_free(manual_headers);
2486 compose->updating = FALSE;
2487 compose_destroy(compose);
2490 procheader_entries_free(manual_headers);
2493 gtk_widget_grab_focus(compose->text);
2495 if (prefs_common.auto_exteditor) {
2496 compose_exec_ext_editor(compose);
2498 compose->modified = FALSE;
2499 compose_set_title(compose);
2501 compose->updating = FALSE;
2502 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2503 SCROLL_TO_CURSOR(compose);
2505 if (compose->deferred_destroy) {
2506 compose_destroy(compose);
2510 compose->sig_str = account_get_signature_str(compose->account);
2512 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2517 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2524 cm_return_val_if_fail(msginfo != NULL, NULL);
2527 account = account_get_reply_account(msginfo,
2528 prefs_common.reply_account_autosel);
2529 cm_return_val_if_fail(account != NULL, NULL);
2531 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2533 compose->updating = TRUE;
2535 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2536 compose->replyinfo = NULL;
2537 compose->fwdinfo = NULL;
2539 compose_show_first_last_header(compose, TRUE);
2541 gtk_widget_grab_focus(compose->header_last->entry);
2543 filename = procmsg_get_message_file(msginfo);
2545 if (filename == NULL) {
2546 compose->updating = FALSE;
2547 compose_destroy(compose);
2552 compose->redirect_filename = filename;
2554 /* Set save folder */
2555 item = msginfo->folder;
2556 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2557 gchar *folderidentifier;
2559 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2560 folderidentifier = folder_item_get_identifier(item);
2561 compose_set_save_to(compose, folderidentifier);
2562 g_free(folderidentifier);
2565 compose_attach_parts(compose, msginfo);
2567 if (msginfo->subject)
2568 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2570 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2572 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2573 _("The body of the \"Redirect\" template has an error at line %d."));
2574 quote_fmt_reset_vartable();
2575 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2577 compose_colorize_signature(compose);
2580 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2581 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2585 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2594 if (compose->toolbar->draft_btn)
2595 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2596 if (compose->toolbar->insert_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2598 if (compose->toolbar->attach_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2600 if (compose->toolbar->sig_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2602 if (compose->toolbar->exteditor_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2604 if (compose->toolbar->linewrap_current_btn)
2605 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2606 if (compose->toolbar->linewrap_all_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2609 compose->modified = FALSE;
2610 compose_set_title(compose);
2611 compose->updating = FALSE;
2612 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2613 SCROLL_TO_CURSOR(compose);
2615 if (compose->deferred_destroy) {
2616 compose_destroy(compose);
2620 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2625 const GList *compose_get_compose_list(void)
2627 return compose_list;
2630 void compose_entry_append(Compose *compose, const gchar *address,
2631 ComposeEntryType type, ComposePrefType pref_type)
2633 const gchar *header;
2635 gboolean in_quote = FALSE;
2636 if (!address || *address == '\0') return;
2643 header = N_("Bcc:");
2645 case COMPOSE_REPLYTO:
2646 header = N_("Reply-To:");
2648 case COMPOSE_NEWSGROUPS:
2649 header = N_("Newsgroups:");
2651 case COMPOSE_FOLLOWUPTO:
2652 header = N_( "Followup-To:");
2654 case COMPOSE_INREPLYTO:
2655 header = N_( "In-Reply-To:");
2662 header = prefs_common_translated_header_name(header);
2664 cur = begin = (gchar *)address;
2666 /* we separate the line by commas, but not if we're inside a quoted
2668 while (*cur != '\0') {
2670 in_quote = !in_quote;
2671 if (*cur == ',' && !in_quote) {
2672 gchar *tmp = g_strdup(begin);
2674 tmp[cur-begin]='\0';
2677 while (*tmp == ' ' || *tmp == '\t')
2679 compose_add_header_entry(compose, header, tmp, pref_type);
2680 compose_entry_indicate(compose, tmp);
2687 gchar *tmp = g_strdup(begin);
2689 tmp[cur-begin]='\0';
2690 while (*tmp == ' ' || *tmp == '\t')
2692 compose_add_header_entry(compose, header, tmp, pref_type);
2693 compose_entry_indicate(compose, tmp);
2698 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2703 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2704 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2705 if (gtk_entry_get_text(entry) &&
2706 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2707 gtk_widget_modify_base(
2708 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2709 GTK_STATE_NORMAL, &default_header_bgcolor);
2710 gtk_widget_modify_text(
2711 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2712 GTK_STATE_NORMAL, &default_header_color);
2717 void compose_toolbar_cb(gint action, gpointer data)
2719 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2720 Compose *compose = (Compose*)toolbar_item->parent;
2722 cm_return_if_fail(compose != NULL);
2726 compose_send_cb(NULL, compose);
2729 compose_send_later_cb(NULL, compose);
2732 compose_draft(compose, COMPOSE_QUIT_EDITING);
2735 compose_insert_file_cb(NULL, compose);
2738 compose_attach_cb(NULL, compose);
2741 compose_insert_sig(compose, FALSE);
2744 compose_insert_sig(compose, TRUE);
2747 compose_ext_editor_cb(NULL, compose);
2749 case A_LINEWRAP_CURRENT:
2750 compose_beautify_paragraph(compose, NULL, TRUE);
2752 case A_LINEWRAP_ALL:
2753 compose_wrap_all_full(compose, TRUE);
2756 compose_address_cb(NULL, compose);
2759 case A_CHECK_SPELLING:
2760 compose_check_all(NULL, compose);
2763 case A_PRIVACY_SIGN:
2765 case A_PRIVACY_ENCRYPT:
2772 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2777 gchar *subject = NULL;
2781 gchar **attach = NULL;
2782 gchar *inreplyto = NULL;
2783 MailField mfield = NO_FIELD_PRESENT;
2785 /* get mailto parts but skip from */
2786 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2789 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2790 mfield = TO_FIELD_PRESENT;
2793 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2795 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2797 if (!g_utf8_validate (subject, -1, NULL)) {
2798 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2799 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2802 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2804 mfield = SUBJECT_FIELD_PRESENT;
2807 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2808 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2811 gboolean prev_autowrap = compose->autowrap;
2813 compose->autowrap = FALSE;
2815 mark = gtk_text_buffer_get_insert(buffer);
2816 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2818 if (!g_utf8_validate (body, -1, NULL)) {
2819 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2820 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2823 gtk_text_buffer_insert(buffer, &iter, body, -1);
2825 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2827 compose->autowrap = prev_autowrap;
2828 if (compose->autowrap)
2829 compose_wrap_all(compose);
2830 mfield = BODY_FIELD_PRESENT;
2834 gint i = 0, att = 0;
2835 gchar *warn_files = NULL;
2836 while (attach[i] != NULL) {
2837 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2838 if (utf8_filename) {
2839 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2840 gchar *tmp = g_strdup_printf("%s%s\n",
2841 warn_files?warn_files:"",
2847 g_free(utf8_filename);
2849 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2854 alertpanel_notice(ngettext(
2855 "The following file has been attached: \n%s",
2856 "The following files have been attached: \n%s", att), warn_files);
2861 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2874 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2876 static HeaderEntry hentry[] = {
2877 {"Reply-To:", NULL, TRUE },
2878 {"Cc:", NULL, TRUE },
2879 {"References:", NULL, FALSE },
2880 {"Bcc:", NULL, TRUE },
2881 {"Newsgroups:", NULL, TRUE },
2882 {"Followup-To:", NULL, TRUE },
2883 {"List-Post:", NULL, FALSE },
2884 {"X-Priority:", NULL, FALSE },
2885 {NULL, NULL, FALSE }
2902 cm_return_val_if_fail(msginfo != NULL, -1);
2904 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2905 procheader_get_header_fields(fp, hentry);
2908 if (hentry[H_REPLY_TO].body != NULL) {
2909 if (hentry[H_REPLY_TO].body[0] != '\0') {
2911 conv_unmime_header(hentry[H_REPLY_TO].body,
2914 g_free(hentry[H_REPLY_TO].body);
2915 hentry[H_REPLY_TO].body = NULL;
2917 if (hentry[H_CC].body != NULL) {
2918 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2919 g_free(hentry[H_CC].body);
2920 hentry[H_CC].body = NULL;
2922 if (hentry[H_REFERENCES].body != NULL) {
2923 if (compose->mode == COMPOSE_REEDIT)
2924 compose->references = hentry[H_REFERENCES].body;
2926 compose->references = compose_parse_references
2927 (hentry[H_REFERENCES].body, msginfo->msgid);
2928 g_free(hentry[H_REFERENCES].body);
2930 hentry[H_REFERENCES].body = NULL;
2932 if (hentry[H_BCC].body != NULL) {
2933 if (compose->mode == COMPOSE_REEDIT)
2935 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2936 g_free(hentry[H_BCC].body);
2937 hentry[H_BCC].body = NULL;
2939 if (hentry[H_NEWSGROUPS].body != NULL) {
2940 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2941 hentry[H_NEWSGROUPS].body = NULL;
2943 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2944 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2945 compose->followup_to =
2946 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2949 g_free(hentry[H_FOLLOWUP_TO].body);
2950 hentry[H_FOLLOWUP_TO].body = NULL;
2952 if (hentry[H_LIST_POST].body != NULL) {
2953 gchar *to = NULL, *start = NULL;
2955 extract_address(hentry[H_LIST_POST].body);
2956 if (hentry[H_LIST_POST].body[0] != '\0') {
2957 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2959 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2960 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2963 g_free(compose->ml_post);
2964 compose->ml_post = to;
2967 g_free(hentry[H_LIST_POST].body);
2968 hentry[H_LIST_POST].body = NULL;
2971 /* CLAWS - X-Priority */
2972 if (compose->mode == COMPOSE_REEDIT)
2973 if (hentry[H_X_PRIORITY].body != NULL) {
2976 priority = atoi(hentry[H_X_PRIORITY].body);
2977 g_free(hentry[H_X_PRIORITY].body);
2979 hentry[H_X_PRIORITY].body = NULL;
2981 if (priority < PRIORITY_HIGHEST ||
2982 priority > PRIORITY_LOWEST)
2983 priority = PRIORITY_NORMAL;
2985 compose->priority = priority;
2988 if (compose->mode == COMPOSE_REEDIT) {
2989 if (msginfo->inreplyto && *msginfo->inreplyto)
2990 compose->inreplyto = g_strdup(msginfo->inreplyto);
2992 if (msginfo->msgid && *msginfo->msgid &&
2993 compose->folder != NULL &&
2994 compose->folder->stype == F_DRAFT)
2995 compose->msgid = g_strdup(msginfo->msgid);
2997 if (msginfo->msgid && *msginfo->msgid)
2998 compose->inreplyto = g_strdup(msginfo->msgid);
3000 if (!compose->references) {
3001 if (msginfo->msgid && *msginfo->msgid) {
3002 if (msginfo->inreplyto && *msginfo->inreplyto)
3003 compose->references =
3004 g_strdup_printf("<%s>\n\t<%s>",
3008 compose->references =
3009 g_strconcat("<", msginfo->msgid, ">",
3011 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3012 compose->references =
3013 g_strconcat("<", msginfo->inreplyto, ">",
3022 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3027 cm_return_val_if_fail(msginfo != NULL, -1);
3029 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3030 procheader_get_header_fields(fp, entries);
3034 while (he != NULL && he->name != NULL) {
3036 GtkListStore *model = NULL;
3038 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3039 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3040 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3041 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3042 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3049 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3051 GSList *ref_id_list, *cur;
3055 ref_id_list = references_list_append(NULL, ref);
3056 if (!ref_id_list) return NULL;
3057 if (msgid && *msgid)
3058 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3063 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3064 /* "<" + Message-ID + ">" + CR+LF+TAB */
3065 len += strlen((gchar *)cur->data) + 5;
3067 if (len > MAX_REFERENCES_LEN) {
3068 /* remove second message-ID */
3069 if (ref_id_list && ref_id_list->next &&
3070 ref_id_list->next->next) {
3071 g_free(ref_id_list->next->data);
3072 ref_id_list = g_slist_remove
3073 (ref_id_list, ref_id_list->next->data);
3075 slist_free_strings_full(ref_id_list);
3082 new_ref = g_string_new("");
3083 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3084 if (new_ref->len > 0)
3085 g_string_append(new_ref, "\n\t");
3086 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3089 slist_free_strings_full(ref_id_list);
3091 new_ref_str = new_ref->str;
3092 g_string_free(new_ref, FALSE);
3097 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3098 const gchar *fmt, const gchar *qmark,
3099 const gchar *body, gboolean rewrap,
3100 gboolean need_unescape,
3101 const gchar *err_msg)
3103 MsgInfo* dummyinfo = NULL;
3104 gchar *quote_str = NULL;
3106 gboolean prev_autowrap;
3107 const gchar *trimmed_body = body;
3108 gint cursor_pos = -1;
3109 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3110 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3115 SIGNAL_BLOCK(buffer);
3118 dummyinfo = compose_msginfo_new_from_compose(compose);
3119 msginfo = dummyinfo;
3122 if (qmark != NULL) {
3124 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3125 compose->gtkaspell);
3127 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3129 quote_fmt_scan_string(qmark);
3132 buf = quote_fmt_get_buffer();
3135 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3137 Xstrdup_a(quote_str, buf, goto error)
3140 if (fmt && *fmt != '\0') {
3143 while (*trimmed_body == '\n')
3147 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3148 compose->gtkaspell);
3150 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3152 if (need_unescape) {
3155 /* decode \-escape sequences in the internal representation of the quote format */
3156 tmp = g_malloc(strlen(fmt)+1);
3157 pref_get_unescaped_pref(tmp, fmt);
3158 quote_fmt_scan_string(tmp);
3162 quote_fmt_scan_string(fmt);
3166 buf = quote_fmt_get_buffer();
3169 gint line = quote_fmt_get_line();
3170 alertpanel_error(err_msg, line);
3178 prev_autowrap = compose->autowrap;
3179 compose->autowrap = FALSE;
3181 mark = gtk_text_buffer_get_insert(buffer);
3182 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3183 if (g_utf8_validate(buf, -1, NULL)) {
3184 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3186 gchar *tmpout = NULL;
3187 tmpout = conv_codeset_strdup
3188 (buf, conv_get_locale_charset_str_no_utf8(),
3190 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3192 tmpout = g_malloc(strlen(buf)*2+1);
3193 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3195 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3199 cursor_pos = quote_fmt_get_cursor_pos();
3200 if (cursor_pos == -1)
3201 cursor_pos = gtk_text_iter_get_offset(&iter);
3202 compose->set_cursor_pos = cursor_pos;
3204 gtk_text_buffer_get_start_iter(buffer, &iter);
3205 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3206 gtk_text_buffer_place_cursor(buffer, &iter);
3208 compose->autowrap = prev_autowrap;
3209 if (compose->autowrap && rewrap)
3210 compose_wrap_all(compose);
3217 SIGNAL_UNBLOCK(buffer);
3219 procmsg_msginfo_free( &dummyinfo );
3224 /* if ml_post is of type addr@host and from is of type
3225 * addr-anything@host, return TRUE
3227 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3229 gchar *left_ml = NULL;
3230 gchar *right_ml = NULL;
3231 gchar *left_from = NULL;
3232 gchar *right_from = NULL;
3233 gboolean result = FALSE;
3235 if (!ml_post || !from)
3238 left_ml = g_strdup(ml_post);
3239 if (strstr(left_ml, "@")) {
3240 right_ml = strstr(left_ml, "@")+1;
3241 *(strstr(left_ml, "@")) = '\0';
3244 left_from = g_strdup(from);
3245 if (strstr(left_from, "@")) {
3246 right_from = strstr(left_from, "@")+1;
3247 *(strstr(left_from, "@")) = '\0';
3250 if (right_ml && right_from
3251 && !strncmp(left_from, left_ml, strlen(left_ml))
3252 && !strcmp(right_from, right_ml)) {
3261 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3262 gboolean respect_default_to)
3266 if (!folder || !folder->prefs)
3269 if (respect_default_to && folder->prefs->enable_default_to) {
3270 compose_entry_append(compose, folder->prefs->default_to,
3271 COMPOSE_TO, PREF_FOLDER);
3272 compose_entry_indicate(compose, folder->prefs->default_to);
3274 if (folder->prefs->enable_default_cc) {
3275 compose_entry_append(compose, folder->prefs->default_cc,
3276 COMPOSE_CC, PREF_FOLDER);
3277 compose_entry_indicate(compose, folder->prefs->default_cc);
3279 if (folder->prefs->enable_default_bcc) {
3280 compose_entry_append(compose, folder->prefs->default_bcc,
3281 COMPOSE_BCC, PREF_FOLDER);
3282 compose_entry_indicate(compose, folder->prefs->default_bcc);
3284 if (folder->prefs->enable_default_replyto) {
3285 compose_entry_append(compose, folder->prefs->default_replyto,
3286 COMPOSE_REPLYTO, PREF_FOLDER);
3287 compose_entry_indicate(compose, folder->prefs->default_replyto);
3291 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3296 if (!compose || !msginfo)
3299 if (msginfo->subject && *msginfo->subject) {
3300 buf = p = g_strdup(msginfo->subject);
3301 p += subject_get_prefix_length(p);
3302 memmove(buf, p, strlen(p) + 1);
3304 buf2 = g_strdup_printf("Re: %s", buf);
3305 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3310 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3313 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3314 gboolean to_all, gboolean to_ml,
3316 gboolean followup_and_reply_to)
3318 GSList *cc_list = NULL;
3321 gchar *replyto = NULL;
3322 gchar *ac_email = NULL;
3324 gboolean reply_to_ml = FALSE;
3325 gboolean default_reply_to = FALSE;
3327 cm_return_if_fail(compose->account != NULL);
3328 cm_return_if_fail(msginfo != NULL);
3330 reply_to_ml = to_ml && compose->ml_post;
3332 default_reply_to = msginfo->folder &&
3333 msginfo->folder->prefs->enable_default_reply_to;
3335 if (compose->account->protocol != A_NNTP) {
3336 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3338 if (reply_to_ml && !default_reply_to) {
3340 gboolean is_subscr = is_subscription(compose->ml_post,
3343 /* normal answer to ml post with a reply-to */
3344 compose_entry_append(compose,
3346 COMPOSE_TO, PREF_ML);
3347 if (compose->replyto)
3348 compose_entry_append(compose,
3350 COMPOSE_CC, PREF_ML);
3352 /* answer to subscription confirmation */
3353 if (compose->replyto)
3354 compose_entry_append(compose,
3356 COMPOSE_TO, PREF_ML);
3357 else if (msginfo->from)
3358 compose_entry_append(compose,
3360 COMPOSE_TO, PREF_ML);
3363 else if (!(to_all || to_sender) && default_reply_to) {
3364 compose_entry_append(compose,
3365 msginfo->folder->prefs->default_reply_to,
3366 COMPOSE_TO, PREF_FOLDER);
3367 compose_entry_indicate(compose,
3368 msginfo->folder->prefs->default_reply_to);
3374 compose_entry_append(compose, msginfo->from,
3375 COMPOSE_TO, PREF_NONE);
3377 Xstrdup_a(tmp1, msginfo->from, return);
3378 extract_address(tmp1);
3379 compose_entry_append(compose,
3380 (!account_find_from_address(tmp1, FALSE))
3383 COMPOSE_TO, PREF_NONE);
3384 if (compose->replyto)
3385 compose_entry_append(compose,
3387 COMPOSE_CC, PREF_NONE);
3389 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3390 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3391 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3392 if (compose->replyto) {
3393 compose_entry_append(compose,
3395 COMPOSE_TO, PREF_NONE);
3397 compose_entry_append(compose,
3398 msginfo->from ? msginfo->from : "",
3399 COMPOSE_TO, PREF_NONE);
3402 /* replying to own mail, use original recp */
3403 compose_entry_append(compose,
3404 msginfo->to ? msginfo->to : "",
3405 COMPOSE_TO, PREF_NONE);
3406 compose_entry_append(compose,
3407 msginfo->cc ? msginfo->cc : "",
3408 COMPOSE_CC, PREF_NONE);
3413 if (to_sender || (compose->followup_to &&
3414 !strncmp(compose->followup_to, "poster", 6)))
3415 compose_entry_append
3417 (compose->replyto ? compose->replyto :
3418 msginfo->from ? msginfo->from : ""),
3419 COMPOSE_TO, PREF_NONE);
3421 else if (followup_and_reply_to || to_all) {
3422 compose_entry_append
3424 (compose->replyto ? compose->replyto :
3425 msginfo->from ? msginfo->from : ""),
3426 COMPOSE_TO, PREF_NONE);
3428 compose_entry_append
3430 compose->followup_to ? compose->followup_to :
3431 compose->newsgroups ? compose->newsgroups : "",
3432 COMPOSE_NEWSGROUPS, PREF_NONE);
3434 compose_entry_append
3436 msginfo->cc ? msginfo->cc : "",
3437 COMPOSE_CC, PREF_NONE);
3440 compose_entry_append
3442 compose->followup_to ? compose->followup_to :
3443 compose->newsgroups ? compose->newsgroups : "",
3444 COMPOSE_NEWSGROUPS, PREF_NONE);
3446 compose_reply_set_subject(compose, msginfo);
3448 if (to_ml && compose->ml_post) return;
3449 if (!to_all || compose->account->protocol == A_NNTP) return;
3451 if (compose->replyto) {
3452 Xstrdup_a(replyto, compose->replyto, return);
3453 extract_address(replyto);
3455 if (msginfo->from) {
3456 Xstrdup_a(from, msginfo->from, return);
3457 extract_address(from);
3460 if (replyto && from)
3461 cc_list = address_list_append_with_comments(cc_list, from);
3462 if (to_all && msginfo->folder &&
3463 msginfo->folder->prefs->enable_default_reply_to)
3464 cc_list = address_list_append_with_comments(cc_list,
3465 msginfo->folder->prefs->default_reply_to);
3466 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3467 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3469 ac_email = g_utf8_strdown(compose->account->address, -1);
3472 for (cur = cc_list; cur != NULL; cur = cur->next) {
3473 gchar *addr = g_utf8_strdown(cur->data, -1);
3474 extract_address(addr);
3476 if (strcmp(ac_email, addr))
3477 compose_entry_append(compose, (gchar *)cur->data,
3478 COMPOSE_CC, PREF_NONE);
3480 debug_print("Cc address same as compose account's, ignoring\n");
3485 slist_free_strings_full(cc_list);
3491 #define SET_ENTRY(entry, str) \
3494 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3497 #define SET_ADDRESS(type, str) \
3500 compose_entry_append(compose, str, type, PREF_NONE); \
3503 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3505 cm_return_if_fail(msginfo != NULL);
3507 SET_ENTRY(subject_entry, msginfo->subject);
3508 SET_ENTRY(from_name, msginfo->from);
3509 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3510 SET_ADDRESS(COMPOSE_CC, compose->cc);
3511 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3512 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3513 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3514 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3516 compose_update_priority_menu_item(compose);
3517 compose_update_privacy_system_menu_item(compose, FALSE);
3518 compose_show_first_last_header(compose, TRUE);
3524 static void compose_insert_sig(Compose *compose, gboolean replace)
3526 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3527 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3529 GtkTextIter iter, iter_end;
3530 gint cur_pos, ins_pos;
3531 gboolean prev_autowrap;
3532 gboolean found = FALSE;
3533 gboolean exists = FALSE;
3535 cm_return_if_fail(compose->account != NULL);
3539 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3540 G_CALLBACK(compose_changed_cb),
3543 mark = gtk_text_buffer_get_insert(buffer);
3544 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3545 cur_pos = gtk_text_iter_get_offset (&iter);
3548 gtk_text_buffer_get_end_iter(buffer, &iter);
3550 exists = (compose->sig_str != NULL);
3553 GtkTextIter first_iter, start_iter, end_iter;
3555 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3557 if (!exists || compose->sig_str[0] == '\0')
3560 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3561 compose->signature_tag);
3564 /* include previous \n\n */
3565 gtk_text_iter_backward_chars(&first_iter, 1);
3566 start_iter = first_iter;
3567 end_iter = first_iter;
3569 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3570 compose->signature_tag);
3571 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3572 compose->signature_tag);
3574 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3580 g_free(compose->sig_str);
3581 compose->sig_str = account_get_signature_str(compose->account);
3583 cur_pos = gtk_text_iter_get_offset(&iter);
3585 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3586 g_free(compose->sig_str);
3587 compose->sig_str = NULL;
3589 if (compose->sig_inserted == FALSE)
3590 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3591 compose->sig_inserted = TRUE;
3593 cur_pos = gtk_text_iter_get_offset(&iter);
3594 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3596 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3597 gtk_text_iter_forward_chars(&iter, 1);
3598 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3599 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3601 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3602 cur_pos = gtk_text_buffer_get_char_count (buffer);
3605 /* put the cursor where it should be
3606 * either where the quote_fmt says, either where it was */
3607 if (compose->set_cursor_pos < 0)
3608 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3610 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3611 compose->set_cursor_pos);
3613 compose->set_cursor_pos = -1;
3614 gtk_text_buffer_place_cursor(buffer, &iter);
3615 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3616 G_CALLBACK(compose_changed_cb),
3622 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3625 GtkTextBuffer *buffer;
3628 const gchar *cur_encoding;
3629 gchar buf[BUFFSIZE];
3632 gboolean prev_autowrap;
3635 GString *file_contents = NULL;
3636 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3638 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3640 /* get the size of the file we are about to insert */
3641 ret = g_stat(file, &file_stat);
3643 gchar *shortfile = g_path_get_basename(file);
3644 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3646 return COMPOSE_INSERT_NO_FILE;
3647 } else if (prefs_common.warn_large_insert == TRUE) {
3649 /* ask user for confirmation if the file is large */
3650 if (prefs_common.warn_large_insert_size < 0 ||
3651 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3655 msg = g_strdup_printf(_("You are about to insert a file of %s "
3656 "in the message body. Are you sure you want to do that?"),
3657 to_human_readable(file_stat.st_size));
3658 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3659 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3662 /* do we ask for confirmation next time? */
3663 if (aval & G_ALERTDISABLE) {
3664 /* no confirmation next time, disable feature in preferences */
3665 aval &= ~G_ALERTDISABLE;
3666 prefs_common.warn_large_insert = FALSE;
3669 /* abort file insertion if user canceled action */
3670 if (aval != G_ALERTALTERNATE) {
3671 return COMPOSE_INSERT_NO_FILE;
3677 if ((fp = g_fopen(file, "rb")) == NULL) {
3678 FILE_OP_ERROR(file, "fopen");
3679 return COMPOSE_INSERT_READ_ERROR;
3682 prev_autowrap = compose->autowrap;
3683 compose->autowrap = FALSE;
3685 text = GTK_TEXT_VIEW(compose->text);
3686 buffer = gtk_text_view_get_buffer(text);
3687 mark = gtk_text_buffer_get_insert(buffer);
3688 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3690 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3691 G_CALLBACK(text_inserted),
3694 cur_encoding = conv_get_locale_charset_str_no_utf8();
3696 file_contents = g_string_new("");
3697 while (fgets(buf, sizeof(buf), fp) != NULL) {
3700 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3701 str = g_strdup(buf);
3703 codeconv_set_strict(TRUE);
3704 str = conv_codeset_strdup
3705 (buf, cur_encoding, CS_INTERNAL);
3706 codeconv_set_strict(FALSE);
3709 result = COMPOSE_INSERT_INVALID_CHARACTER;
3715 /* strip <CR> if DOS/Windows file,
3716 replace <CR> with <LF> if Macintosh file. */
3719 if (len > 0 && str[len - 1] != '\n') {
3721 if (str[len] == '\r') str[len] = '\n';
3724 file_contents = g_string_append(file_contents, str);
3728 if (result == COMPOSE_INSERT_SUCCESS) {
3729 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3731 compose_changed_cb(NULL, compose);
3732 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3733 G_CALLBACK(text_inserted),
3735 compose->autowrap = prev_autowrap;
3736 if (compose->autowrap)
3737 compose_wrap_all(compose);
3740 g_string_free(file_contents, TRUE);
3746 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3747 const gchar *filename,
3748 const gchar *content_type,
3749 const gchar *charset)
3757 GtkListStore *store;
3759 gboolean has_binary = FALSE;
3761 if (!is_file_exist(file)) {
3762 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3763 gboolean result = FALSE;
3764 if (file_from_uri && is_file_exist(file_from_uri)) {
3765 result = compose_attach_append(
3766 compose, file_from_uri,
3767 filename, content_type,
3770 g_free(file_from_uri);
3773 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3776 if ((size = get_file_size(file)) < 0) {
3777 alertpanel_error("Can't get file size of %s\n", filename);
3781 /* In batch mode, we allow 0-length files to be attached no questions asked */
3782 if (size == 0 && !compose->batch) {
3783 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3784 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3785 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3786 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3789 if (aval != G_ALERTALTERNATE) {
3793 if ((fp = g_fopen(file, "rb")) == NULL) {
3794 alertpanel_error(_("Can't read %s."), filename);
3799 ainfo = g_new0(AttachInfo, 1);
3800 auto_ainfo = g_auto_pointer_new_with_free
3801 (ainfo, (GFreeFunc) compose_attach_info_free);
3802 ainfo->file = g_strdup(file);
3805 ainfo->content_type = g_strdup(content_type);
3806 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3808 MsgFlags flags = {0, 0};
3810 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3811 ainfo->encoding = ENC_7BIT;
3813 ainfo->encoding = ENC_8BIT;
3815 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3816 if (msginfo && msginfo->subject)
3817 name = g_strdup(msginfo->subject);
3819 name = g_path_get_basename(filename ? filename : file);
3821 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3823 procmsg_msginfo_free(&msginfo);
3825 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3826 ainfo->charset = g_strdup(charset);
3827 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3829 ainfo->encoding = ENC_BASE64;
3831 name = g_path_get_basename(filename ? filename : file);
3832 ainfo->name = g_strdup(name);
3836 ainfo->content_type = procmime_get_mime_type(file);
3837 if (!ainfo->content_type) {
3838 ainfo->content_type =
3839 g_strdup("application/octet-stream");
3840 ainfo->encoding = ENC_BASE64;
3841 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3843 procmime_get_encoding_for_text_file(file, &has_binary);
3845 ainfo->encoding = ENC_BASE64;
3846 name = g_path_get_basename(filename ? filename : file);
3847 ainfo->name = g_strdup(name);
3851 if (ainfo->name != NULL
3852 && !strcmp(ainfo->name, ".")) {
3853 g_free(ainfo->name);
3857 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3858 g_free(ainfo->content_type);
3859 ainfo->content_type = g_strdup("application/octet-stream");
3860 g_free(ainfo->charset);
3861 ainfo->charset = NULL;
3864 ainfo->size = (goffset)size;
3865 size_text = to_human_readable((goffset)size);
3867 store = GTK_LIST_STORE(gtk_tree_view_get_model
3868 (GTK_TREE_VIEW(compose->attach_clist)));
3870 gtk_list_store_append(store, &iter);
3871 gtk_list_store_set(store, &iter,
3872 COL_MIMETYPE, ainfo->content_type,
3873 COL_SIZE, size_text,
3874 COL_NAME, ainfo->name,
3875 COL_CHARSET, ainfo->charset,
3877 COL_AUTODATA, auto_ainfo,
3880 g_auto_pointer_free(auto_ainfo);
3881 compose_attach_update_label(compose);
3885 void compose_use_signing(Compose *compose, gboolean use_signing)
3887 compose->use_signing = use_signing;
3888 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3891 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3893 compose->use_encryption = use_encryption;
3894 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3897 #define NEXT_PART_NOT_CHILD(info) \
3899 node = info->node; \
3900 while (node->children) \
3901 node = g_node_last_child(node); \
3902 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3905 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3909 MimeInfo *firsttext = NULL;
3910 MimeInfo *encrypted = NULL;
3913 const gchar *partname = NULL;
3915 mimeinfo = procmime_scan_message(msginfo);
3916 if (!mimeinfo) return;
3918 if (mimeinfo->node->children == NULL) {
3919 procmime_mimeinfo_free_all(&mimeinfo);
3923 /* find first content part */
3924 child = (MimeInfo *) mimeinfo->node->children->data;
3925 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3926 child = (MimeInfo *)child->node->children->data;
3929 if (child->type == MIMETYPE_TEXT) {
3931 debug_print("First text part found\n");
3932 } else if (compose->mode == COMPOSE_REEDIT &&
3933 child->type == MIMETYPE_APPLICATION &&
3934 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3935 encrypted = (MimeInfo *)child->node->parent->data;
3938 child = (MimeInfo *) mimeinfo->node->children->data;
3939 while (child != NULL) {
3942 if (child == encrypted) {
3943 /* skip this part of tree */
3944 NEXT_PART_NOT_CHILD(child);
3948 if (child->type == MIMETYPE_MULTIPART) {
3949 /* get the actual content */
3950 child = procmime_mimeinfo_next(child);
3954 if (child == firsttext) {
3955 child = procmime_mimeinfo_next(child);
3959 outfile = procmime_get_tmp_file_name(child);
3960 if ((err = procmime_get_part(outfile, child)) < 0)
3961 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3963 gchar *content_type;
3965 content_type = procmime_get_content_type_str(child->type, child->subtype);
3967 /* if we meet a pgp signature, we don't attach it, but
3968 * we force signing. */
3969 if ((strcmp(content_type, "application/pgp-signature") &&
3970 strcmp(content_type, "application/pkcs7-signature") &&
3971 strcmp(content_type, "application/x-pkcs7-signature"))
3972 || compose->mode == COMPOSE_REDIRECT) {
3973 partname = procmime_mimeinfo_get_parameter(child, "filename");
3974 if (partname == NULL)
3975 partname = procmime_mimeinfo_get_parameter(child, "name");
3976 if (partname == NULL)
3978 compose_attach_append(compose, outfile,
3979 partname, content_type,
3980 procmime_mimeinfo_get_parameter(child, "charset"));
3982 compose_force_signing(compose, compose->account, NULL);
3984 g_free(content_type);
3987 NEXT_PART_NOT_CHILD(child);
3989 procmime_mimeinfo_free_all(&mimeinfo);
3992 #undef NEXT_PART_NOT_CHILD
3997 WAIT_FOR_INDENT_CHAR,
3998 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4001 /* return indent length, we allow:
4002 indent characters followed by indent characters or spaces/tabs,
4003 alphabets and numbers immediately followed by indent characters,
4004 and the repeating sequences of the above
4005 If quote ends with multiple spaces, only the first one is included. */
4006 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4007 const GtkTextIter *start, gint *len)
4009 GtkTextIter iter = *start;
4013 IndentState state = WAIT_FOR_INDENT_CHAR;
4016 gint alnum_count = 0;
4017 gint space_count = 0;
4020 if (prefs_common.quote_chars == NULL) {
4024 while (!gtk_text_iter_ends_line(&iter)) {
4025 wc = gtk_text_iter_get_char(&iter);
4026 if (g_unichar_iswide(wc))
4028 clen = g_unichar_to_utf8(wc, ch);
4032 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4033 is_space = g_unichar_isspace(wc);
4035 if (state == WAIT_FOR_INDENT_CHAR) {
4036 if (!is_indent && !g_unichar_isalnum(wc))
4039 quote_len += alnum_count + space_count + 1;
4040 alnum_count = space_count = 0;
4041 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4044 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4045 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4049 else if (is_indent) {
4050 quote_len += alnum_count + space_count + 1;
4051 alnum_count = space_count = 0;
4054 state = WAIT_FOR_INDENT_CHAR;
4058 gtk_text_iter_forward_char(&iter);
4061 if (quote_len > 0 && space_count > 0)
4067 if (quote_len > 0) {
4069 gtk_text_iter_forward_chars(&iter, quote_len);
4070 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4076 /* return >0 if the line is itemized */
4077 static int compose_itemized_length(GtkTextBuffer *buffer,
4078 const GtkTextIter *start)
4080 GtkTextIter iter = *start;
4085 if (gtk_text_iter_ends_line(&iter))
4090 wc = gtk_text_iter_get_char(&iter);
4091 if (!g_unichar_isspace(wc))
4093 gtk_text_iter_forward_char(&iter);
4094 if (gtk_text_iter_ends_line(&iter))
4098 clen = g_unichar_to_utf8(wc, ch);
4099 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4101 wc == 0x2022 || /* BULLET */
4102 wc == 0x2023 || /* TRIANGULAR BULLET */
4103 wc == 0x2043 || /* HYPHEN BULLET */
4104 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4105 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4106 wc == 0x2219 || /* BULLET OPERATOR */
4107 wc == 0x25d8 || /* INVERSE BULLET */
4108 wc == 0x25e6 || /* WHITE BULLET */
4109 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4110 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4111 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4112 wc == 0x29be || /* CIRCLED WHITE BULLET */
4113 wc == 0x29bf /* CIRCLED BULLET */
4117 gtk_text_iter_forward_char(&iter);
4118 if (gtk_text_iter_ends_line(&iter))
4120 wc = gtk_text_iter_get_char(&iter);
4121 if (g_unichar_isspace(wc)) {
4127 /* return the string at the start of the itemization */
4128 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4129 const GtkTextIter *start)
4131 GtkTextIter iter = *start;
4134 GString *item_chars = g_string_new("");
4137 if (gtk_text_iter_ends_line(&iter))
4142 wc = gtk_text_iter_get_char(&iter);
4143 if (!g_unichar_isspace(wc))
4145 gtk_text_iter_forward_char(&iter);
4146 if (gtk_text_iter_ends_line(&iter))
4148 g_string_append_unichar(item_chars, wc);
4151 str = item_chars->str;
4152 g_string_free(item_chars, FALSE);
4156 /* return the number of spaces at a line's start */
4157 static int compose_left_offset_length(GtkTextBuffer *buffer,
4158 const GtkTextIter *start)
4160 GtkTextIter iter = *start;
4163 if (gtk_text_iter_ends_line(&iter))
4167 wc = gtk_text_iter_get_char(&iter);
4168 if (!g_unichar_isspace(wc))
4171 gtk_text_iter_forward_char(&iter);
4172 if (gtk_text_iter_ends_line(&iter))
4176 gtk_text_iter_forward_char(&iter);
4177 if (gtk_text_iter_ends_line(&iter))
4182 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4183 const GtkTextIter *start,
4184 GtkTextIter *break_pos,
4188 GtkTextIter iter = *start, line_end = *start;
4189 PangoLogAttr *attrs;
4196 gboolean can_break = FALSE;
4197 gboolean do_break = FALSE;
4198 gboolean was_white = FALSE;
4199 gboolean prev_dont_break = FALSE;
4201 gtk_text_iter_forward_to_line_end(&line_end);
4202 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4203 len = g_utf8_strlen(str, -1);
4207 g_warning("compose_get_line_break_pos: len = 0!");
4211 /* g_print("breaking line: %d: %s (len = %d)\n",
4212 gtk_text_iter_get_line(&iter), str, len); */
4214 attrs = g_new(PangoLogAttr, len + 1);
4216 pango_default_break(str, -1, NULL, attrs, len + 1);
4220 /* skip quote and leading spaces */
4221 for (i = 0; *p != '\0' && i < len; i++) {
4224 wc = g_utf8_get_char(p);
4225 if (i >= quote_len && !g_unichar_isspace(wc))
4227 if (g_unichar_iswide(wc))
4229 else if (*p == '\t')
4233 p = g_utf8_next_char(p);
4236 for (; *p != '\0' && i < len; i++) {
4237 PangoLogAttr *attr = attrs + i;
4241 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4244 was_white = attr->is_white;
4246 /* don't wrap URI */
4247 if ((uri_len = get_uri_len(p)) > 0) {
4249 if (pos > 0 && col > max_col) {
4259 wc = g_utf8_get_char(p);
4260 if (g_unichar_iswide(wc)) {
4262 if (prev_dont_break && can_break && attr->is_line_break)
4264 } else if (*p == '\t')
4268 if (pos > 0 && col > max_col) {
4273 if (*p == '-' || *p == '/')
4274 prev_dont_break = TRUE;
4276 prev_dont_break = FALSE;
4278 p = g_utf8_next_char(p);
4282 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4287 *break_pos = *start;
4288 gtk_text_iter_set_line_offset(break_pos, pos);
4293 static gboolean compose_join_next_line(Compose *compose,
4294 GtkTextBuffer *buffer,
4296 const gchar *quote_str)
4298 GtkTextIter iter_ = *iter, cur, prev, next, end;
4299 PangoLogAttr attrs[3];
4301 gchar *next_quote_str;
4304 gboolean keep_cursor = FALSE;
4306 if (!gtk_text_iter_forward_line(&iter_) ||
4307 gtk_text_iter_ends_line(&iter_)) {
4310 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4312 if ((quote_str || next_quote_str) &&
4313 strcmp2(quote_str, next_quote_str) != 0) {
4314 g_free(next_quote_str);
4317 g_free(next_quote_str);
4320 if (quote_len > 0) {
4321 gtk_text_iter_forward_chars(&end, quote_len);
4322 if (gtk_text_iter_ends_line(&end)) {
4327 /* don't join itemized lines */
4328 if (compose_itemized_length(buffer, &end) > 0) {
4332 /* don't join signature separator */
4333 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4336 /* delete quote str */
4338 gtk_text_buffer_delete(buffer, &iter_, &end);
4340 /* don't join line breaks put by the user */
4342 gtk_text_iter_backward_char(&cur);
4343 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4344 gtk_text_iter_forward_char(&cur);
4348 gtk_text_iter_forward_char(&cur);
4349 /* delete linebreak and extra spaces */
4350 while (gtk_text_iter_backward_char(&cur)) {
4351 wc1 = gtk_text_iter_get_char(&cur);
4352 if (!g_unichar_isspace(wc1))
4357 while (!gtk_text_iter_ends_line(&cur)) {
4358 wc1 = gtk_text_iter_get_char(&cur);
4359 if (!g_unichar_isspace(wc1))
4361 gtk_text_iter_forward_char(&cur);
4364 if (!gtk_text_iter_equal(&prev, &next)) {
4367 mark = gtk_text_buffer_get_insert(buffer);
4368 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4369 if (gtk_text_iter_equal(&prev, &cur))
4371 gtk_text_buffer_delete(buffer, &prev, &next);
4375 /* insert space if required */
4376 gtk_text_iter_backward_char(&prev);
4377 wc1 = gtk_text_iter_get_char(&prev);
4378 wc2 = gtk_text_iter_get_char(&next);
4379 gtk_text_iter_forward_char(&next);
4380 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4381 pango_default_break(str, -1, NULL, attrs, 3);
4382 if (!attrs[1].is_line_break ||
4383 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4384 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4386 gtk_text_iter_backward_char(&iter_);
4387 gtk_text_buffer_place_cursor(buffer, &iter_);
4396 #define ADD_TXT_POS(bp_, ep_, pti_) \
4397 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4398 last = last->next; \
4399 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4400 last->next = NULL; \
4402 g_warning("alloc error scanning URIs"); \
4405 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4407 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4408 GtkTextBuffer *buffer;
4409 GtkTextIter iter, break_pos, end_of_line;
4410 gchar *quote_str = NULL;
4412 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4413 gboolean prev_autowrap = compose->autowrap;
4414 gint startq_offset = -1, noq_offset = -1;
4415 gint uri_start = -1, uri_stop = -1;
4416 gint nouri_start = -1, nouri_stop = -1;
4417 gint num_blocks = 0;
4418 gint quotelevel = -1;
4419 gboolean modified = force;
4420 gboolean removed = FALSE;
4421 gboolean modified_before_remove = FALSE;
4423 gboolean start = TRUE;
4424 gint itemized_len = 0, rem_item_len = 0;
4425 gchar *itemized_chars = NULL;
4426 gboolean item_continuation = FALSE;
4431 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4435 compose->autowrap = FALSE;
4437 buffer = gtk_text_view_get_buffer(text);
4438 undo_wrapping(compose->undostruct, TRUE);
4443 mark = gtk_text_buffer_get_insert(buffer);
4444 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4448 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4449 if (gtk_text_iter_ends_line(&iter)) {
4450 while (gtk_text_iter_ends_line(&iter) &&
4451 gtk_text_iter_forward_line(&iter))
4454 while (gtk_text_iter_backward_line(&iter)) {
4455 if (gtk_text_iter_ends_line(&iter)) {
4456 gtk_text_iter_forward_line(&iter);
4462 /* move to line start */
4463 gtk_text_iter_set_line_offset(&iter, 0);
4466 itemized_len = compose_itemized_length(buffer, &iter);
4468 if (!itemized_len) {
4469 itemized_len = compose_left_offset_length(buffer, &iter);
4470 item_continuation = TRUE;
4474 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4476 /* go until paragraph end (empty line) */
4477 while (start || !gtk_text_iter_ends_line(&iter)) {
4478 gchar *scanpos = NULL;
4479 /* parse table - in order of priority */
4481 const gchar *needle; /* token */
4483 /* token search function */
4484 gchar *(*search) (const gchar *haystack,
4485 const gchar *needle);
4486 /* part parsing function */
4487 gboolean (*parse) (const gchar *start,
4488 const gchar *scanpos,
4492 /* part to URI function */
4493 gchar *(*build_uri) (const gchar *bp,
4497 static struct table parser[] = {
4498 {"http://", strcasestr, get_uri_part, make_uri_string},
4499 {"https://", strcasestr, get_uri_part, make_uri_string},
4500 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4501 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4502 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4503 {"www.", strcasestr, get_uri_part, make_http_string},
4504 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4505 {"@", strcasestr, get_email_part, make_email_string}
4507 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4508 gint last_index = PARSE_ELEMS;
4510 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4514 if (!prev_autowrap && num_blocks == 0) {
4516 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4517 G_CALLBACK(text_inserted),
4520 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4523 uri_start = uri_stop = -1;
4525 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4528 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4529 if (startq_offset == -1)
4530 startq_offset = gtk_text_iter_get_offset(&iter);
4531 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4532 if (quotelevel > 2) {
4533 /* recycle colors */
4534 if (prefs_common.recycle_quote_colors)
4543 if (startq_offset == -1)
4544 noq_offset = gtk_text_iter_get_offset(&iter);
4548 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4551 if (gtk_text_iter_ends_line(&iter)) {
4553 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4554 prefs_common.linewrap_len,
4556 GtkTextIter prev, next, cur;
4557 if (prev_autowrap != FALSE || force) {
4558 compose->automatic_break = TRUE;
4560 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4561 compose->automatic_break = FALSE;
4562 if (itemized_len && compose->autoindent) {
4563 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4564 if (!item_continuation)
4565 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4567 } else if (quote_str && wrap_quote) {
4568 compose->automatic_break = TRUE;
4570 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4571 compose->automatic_break = FALSE;
4572 if (itemized_len && compose->autoindent) {
4573 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4574 if (!item_continuation)
4575 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4579 /* remove trailing spaces */
4581 rem_item_len = itemized_len;
4582 while (compose->autoindent && rem_item_len-- > 0)
4583 gtk_text_iter_backward_char(&cur);
4584 gtk_text_iter_backward_char(&cur);
4587 while (!gtk_text_iter_starts_line(&cur)) {
4590 gtk_text_iter_backward_char(&cur);
4591 wc = gtk_text_iter_get_char(&cur);
4592 if (!g_unichar_isspace(wc))
4596 if (!gtk_text_iter_equal(&prev, &next)) {
4597 gtk_text_buffer_delete(buffer, &prev, &next);
4599 gtk_text_iter_forward_char(&break_pos);
4603 gtk_text_buffer_insert(buffer, &break_pos,
4607 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4609 /* move iter to current line start */
4610 gtk_text_iter_set_line_offset(&iter, 0);
4617 /* move iter to next line start */
4623 if (!prev_autowrap && num_blocks > 0) {
4625 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4626 G_CALLBACK(text_inserted),
4630 while (!gtk_text_iter_ends_line(&end_of_line)) {
4631 gtk_text_iter_forward_char(&end_of_line);
4633 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4635 nouri_start = gtk_text_iter_get_offset(&iter);
4636 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4638 walk_pos = gtk_text_iter_get_offset(&iter);
4639 /* FIXME: this looks phony. scanning for anything in the parse table */
4640 for (n = 0; n < PARSE_ELEMS; n++) {
4643 tmp = parser[n].search(walk, parser[n].needle);
4645 if (scanpos == NULL || tmp < scanpos) {
4654 /* check if URI can be parsed */
4655 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4656 (const gchar **)&ep, FALSE)
4657 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4661 strlen(parser[last_index].needle);
4664 uri_start = walk_pos + (bp - o_walk);
4665 uri_stop = walk_pos + (ep - o_walk);
4669 gtk_text_iter_forward_line(&iter);
4672 if (startq_offset != -1) {
4673 GtkTextIter startquote, endquote;
4674 gtk_text_buffer_get_iter_at_offset(
4675 buffer, &startquote, startq_offset);
4678 switch (quotelevel) {
4680 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4681 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4682 gtk_text_buffer_apply_tag_by_name(
4683 buffer, "quote0", &startquote, &endquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote1", &startquote, &endquote);
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer, "quote2", &startquote, &endquote);
4692 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4693 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4694 gtk_text_buffer_apply_tag_by_name(
4695 buffer, "quote1", &startquote, &endquote);
4696 gtk_text_buffer_remove_tag_by_name(
4697 buffer, "quote0", &startquote, &endquote);
4698 gtk_text_buffer_remove_tag_by_name(
4699 buffer, "quote2", &startquote, &endquote);
4704 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4705 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4706 gtk_text_buffer_apply_tag_by_name(
4707 buffer, "quote2", &startquote, &endquote);
4708 gtk_text_buffer_remove_tag_by_name(
4709 buffer, "quote0", &startquote, &endquote);
4710 gtk_text_buffer_remove_tag_by_name(
4711 buffer, "quote1", &startquote, &endquote);
4717 } else if (noq_offset != -1) {
4718 GtkTextIter startnoquote, endnoquote;
4719 gtk_text_buffer_get_iter_at_offset(
4720 buffer, &startnoquote, noq_offset);
4723 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4724 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4725 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4726 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4727 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4728 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4729 gtk_text_buffer_remove_tag_by_name(
4730 buffer, "quote0", &startnoquote, &endnoquote);
4731 gtk_text_buffer_remove_tag_by_name(
4732 buffer, "quote1", &startnoquote, &endnoquote);
4733 gtk_text_buffer_remove_tag_by_name(
4734 buffer, "quote2", &startnoquote, &endnoquote);
4740 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4741 GtkTextIter nouri_start_iter, nouri_end_iter;
4742 gtk_text_buffer_get_iter_at_offset(
4743 buffer, &nouri_start_iter, nouri_start);
4744 gtk_text_buffer_get_iter_at_offset(
4745 buffer, &nouri_end_iter, nouri_stop);
4746 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4747 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4748 gtk_text_buffer_remove_tag_by_name(
4749 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4750 modified_before_remove = modified;
4755 if (uri_start >= 0 && uri_stop > 0) {
4756 GtkTextIter uri_start_iter, uri_end_iter, back;
4757 gtk_text_buffer_get_iter_at_offset(
4758 buffer, &uri_start_iter, uri_start);
4759 gtk_text_buffer_get_iter_at_offset(
4760 buffer, &uri_end_iter, uri_stop);
4761 back = uri_end_iter;
4762 gtk_text_iter_backward_char(&back);
4763 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4764 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4765 gtk_text_buffer_apply_tag_by_name(
4766 buffer, "link", &uri_start_iter, &uri_end_iter);
4768 if (removed && !modified_before_remove) {
4774 /* debug_print("not modified, out after %d lines\n", lines); */
4778 /* debug_print("modified, out after %d lines\n", lines); */
4780 g_free(itemized_chars);
4783 undo_wrapping(compose->undostruct, FALSE);
4784 compose->autowrap = prev_autowrap;
4789 void compose_action_cb(void *data)
4791 Compose *compose = (Compose *)data;
4792 compose_wrap_all(compose);
4795 static void compose_wrap_all(Compose *compose)
4797 compose_wrap_all_full(compose, FALSE);
4800 static void compose_wrap_all_full(Compose *compose, gboolean force)
4802 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4803 GtkTextBuffer *buffer;
4805 gboolean modified = TRUE;
4807 buffer = gtk_text_view_get_buffer(text);
4809 gtk_text_buffer_get_start_iter(buffer, &iter);
4811 undo_wrapping(compose->undostruct, TRUE);
4813 while (!gtk_text_iter_is_end(&iter) && modified)
4814 modified = compose_beautify_paragraph(compose, &iter, force);
4816 undo_wrapping(compose->undostruct, FALSE);
4820 static void compose_set_title(Compose *compose)
4826 edited = compose->modified ? _(" [Edited]") : "";
4828 subject = gtk_editable_get_chars(
4829 GTK_EDITABLE(compose->subject_entry), 0, -1);
4831 #ifndef GENERIC_UMPC
4832 if (subject && strlen(subject))
4833 str = g_strdup_printf(_("%s - Compose message%s"),
4836 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4838 str = g_strdup(_("Compose message"));
4841 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4847 * compose_current_mail_account:
4849 * Find a current mail account (the currently selected account, or the
4850 * default account, if a news account is currently selected). If a
4851 * mail account cannot be found, display an error message.
4853 * Return value: Mail account, or NULL if not found.
4855 static PrefsAccount *
4856 compose_current_mail_account(void)
4860 if (cur_account && cur_account->protocol != A_NNTP)
4863 ac = account_get_default();
4864 if (!ac || ac->protocol == A_NNTP) {
4865 alertpanel_error(_("Account for sending mail is not specified.\n"
4866 "Please select a mail account before sending."));
4873 #define QUOTE_IF_REQUIRED(out, str) \
4875 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4879 len = strlen(str) + 3; \
4880 if ((__tmp = alloca(len)) == NULL) { \
4881 g_warning("can't allocate memory"); \
4882 g_string_free(header, TRUE); \
4885 g_snprintf(__tmp, len, "\"%s\"", str); \
4890 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4891 g_warning("can't allocate memory"); \
4892 g_string_free(header, TRUE); \
4895 strcpy(__tmp, str); \
4901 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4903 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4907 len = strlen(str) + 3; \
4908 if ((__tmp = alloca(len)) == NULL) { \
4909 g_warning("can't allocate memory"); \
4912 g_snprintf(__tmp, len, "\"%s\"", str); \
4917 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4918 g_warning("can't allocate memory"); \
4921 strcpy(__tmp, str); \
4927 static void compose_select_account(Compose *compose, PrefsAccount *account,
4930 gchar *from = NULL, *header = NULL;
4931 ComposeHeaderEntry *header_entry;
4932 #if GTK_CHECK_VERSION(2, 24, 0)
4936 cm_return_if_fail(account != NULL);
4938 compose->account = account;
4939 if (account->name && *account->name) {
4941 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4942 qbuf = escape_internal_quotes(buf, '"');
4943 from = g_strdup_printf("%s <%s>",
4944 qbuf, account->address);
4947 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4949 from = g_strdup_printf("<%s>",
4951 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4956 compose_set_title(compose);
4958 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4959 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4961 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4962 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4963 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4965 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4967 activate_privacy_system(compose, account, FALSE);
4969 if (!init && compose->mode != COMPOSE_REDIRECT) {
4970 undo_block(compose->undostruct);
4971 compose_insert_sig(compose, TRUE);
4972 undo_unblock(compose->undostruct);
4975 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4976 #if !GTK_CHECK_VERSION(2, 24, 0)
4977 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4979 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4980 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4981 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4984 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4985 if (account->protocol == A_NNTP) {
4986 if (!strcmp(header, _("To:")))
4987 combobox_select_by_text(
4988 GTK_COMBO_BOX(header_entry->combo),
4991 if (!strcmp(header, _("Newsgroups:")))
4992 combobox_select_by_text(
4993 GTK_COMBO_BOX(header_entry->combo),
5001 /* use account's dict info if set */
5002 if (compose->gtkaspell) {
5003 if (account->enable_default_dictionary)
5004 gtkaspell_change_dict(compose->gtkaspell,
5005 account->default_dictionary, FALSE);
5006 if (account->enable_default_alt_dictionary)
5007 gtkaspell_change_alt_dict(compose->gtkaspell,
5008 account->default_alt_dictionary);
5009 if (account->enable_default_dictionary
5010 || account->enable_default_alt_dictionary)
5011 compose_spell_menu_changed(compose);
5016 gboolean compose_check_for_valid_recipient(Compose *compose) {
5017 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5018 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5019 gboolean recipient_found = FALSE;
5023 /* free to and newsgroup list */
5024 slist_free_strings_full(compose->to_list);
5025 compose->to_list = NULL;
5027 slist_free_strings_full(compose->newsgroup_list);
5028 compose->newsgroup_list = NULL;
5030 /* search header entries for to and newsgroup entries */
5031 for (list = compose->header_list; list; list = list->next) {
5034 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5035 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5038 if (entry[0] != '\0') {
5039 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5040 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5041 compose->to_list = address_list_append(compose->to_list, entry);
5042 recipient_found = TRUE;
5045 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5046 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5047 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5048 recipient_found = TRUE;
5055 return recipient_found;
5058 static gboolean compose_check_for_set_recipients(Compose *compose)
5060 if (compose->account->set_autocc && compose->account->auto_cc) {
5061 gboolean found_other = FALSE;
5063 /* search header entries for to and newsgroup entries */
5064 for (list = compose->header_list; list; list = list->next) {
5067 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5068 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5071 if (strcmp(entry, compose->account->auto_cc)
5072 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5083 if (compose->batch) {
5084 gtk_widget_show_all(compose->window);
5086 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5087 prefs_common_translated_header_name("Cc"));
5088 aval = alertpanel(_("Send"),
5090 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5092 if (aval != G_ALERTALTERNATE)
5096 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5097 gboolean found_other = FALSE;
5099 /* search header entries for to and newsgroup entries */
5100 for (list = compose->header_list; list; list = list->next) {
5103 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5104 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5107 if (strcmp(entry, compose->account->auto_bcc)
5108 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5120 if (compose->batch) {
5121 gtk_widget_show_all(compose->window);
5123 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5124 prefs_common_translated_header_name("Bcc"));
5125 aval = alertpanel(_("Send"),
5127 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5129 if (aval != G_ALERTALTERNATE)
5136 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5140 if (compose_check_for_valid_recipient(compose) == FALSE) {
5141 if (compose->batch) {
5142 gtk_widget_show_all(compose->window);
5144 alertpanel_error(_("Recipient is not specified."));
5148 if (compose_check_for_set_recipients(compose) == FALSE) {
5152 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5153 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5154 if (*str == '\0' && check_everything == TRUE &&
5155 compose->mode != COMPOSE_REDIRECT) {
5157 gchar *button_label;
5160 if (compose->sending)
5161 button_label = g_strconcat("+", _("_Send"), NULL);
5163 button_label = g_strconcat("+", _("_Queue"), NULL);
5164 message = g_strdup_printf(_("Subject is empty. %s"),
5165 compose->sending?_("Send it anyway?"):
5166 _("Queue it anyway?"));
5168 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5169 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5170 ALERT_QUESTION, G_ALERTDEFAULT);
5172 g_free(button_label);
5173 if (aval & G_ALERTDISABLE) {
5174 aval &= ~G_ALERTDISABLE;
5175 prefs_common.warn_empty_subj = FALSE;
5177 if (aval != G_ALERTALTERNATE)
5182 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5183 && check_everything == TRUE) {
5187 /* count To and Cc recipients */
5188 for (list = compose->header_list; list; list = list->next) {
5192 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5193 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5196 if ((entry[0] != '\0') &&
5197 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5198 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5204 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5206 gchar *button_label;
5209 if (compose->sending)
5210 button_label = g_strconcat("+", _("_Send"), NULL);
5212 button_label = g_strconcat("+", _("_Queue"), NULL);
5213 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5214 compose->sending?_("Send it anyway?"):
5215 _("Queue it anyway?"));
5217 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5218 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5219 ALERT_QUESTION, G_ALERTDEFAULT);
5221 if (aval & G_ALERTDISABLE) {
5222 aval &= ~G_ALERTDISABLE;
5223 prefs_common.warn_sending_many_recipients_num = 0;
5225 if (aval != G_ALERTALTERNATE)
5230 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5236 gint compose_send(Compose *compose)
5239 FolderItem *folder = NULL;
5241 gchar *msgpath = NULL;
5242 gboolean discard_window = FALSE;
5243 gchar *errstr = NULL;
5244 gchar *tmsgid = NULL;
5245 MainWindow *mainwin = mainwindow_get_mainwindow();
5246 gboolean queued_removed = FALSE;
5248 if (prefs_common.send_dialog_invisible
5249 || compose->batch == TRUE)
5250 discard_window = TRUE;
5252 compose_allow_user_actions (compose, FALSE);
5253 compose->sending = TRUE;
5255 if (compose_check_entries(compose, TRUE) == FALSE) {
5256 if (compose->batch) {
5257 gtk_widget_show_all(compose->window);
5263 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5266 if (compose->batch) {
5267 gtk_widget_show_all(compose->window);
5270 alertpanel_error(_("Could not queue message for sending:\n\n"
5271 "Charset conversion failed."));
5272 } else if (val == -5) {
5273 alertpanel_error(_("Could not queue message for sending:\n\n"
5274 "Couldn't get recipient encryption key."));
5275 } else if (val == -6) {
5277 } else if (val == -3) {
5278 if (privacy_peek_error())
5279 alertpanel_error(_("Could not queue message for sending:\n\n"
5280 "Signature failed: %s"), privacy_get_error());
5281 } else if (val == -2 && errno != 0) {
5282 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5284 alertpanel_error(_("Could not queue message for sending."));
5289 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5290 if (discard_window) {
5291 compose->sending = FALSE;
5292 compose_close(compose);
5293 /* No more compose access in the normal codepath
5294 * after this point! */
5299 alertpanel_error(_("The message was queued but could not be "
5300 "sent.\nUse \"Send queued messages\" from "
5301 "the main window to retry."));
5302 if (!discard_window) {
5309 if (msgpath == NULL) {
5310 msgpath = folder_item_fetch_msg(folder, msgnum);
5311 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5314 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5315 claws_unlink(msgpath);
5318 if (!discard_window) {
5320 if (!queued_removed)
5321 folder_item_remove_msg(folder, msgnum);
5322 folder_item_scan(folder);
5324 /* make sure we delete that */
5325 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5327 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5328 folder_item_remove_msg(folder, tmp->msgnum);
5329 procmsg_msginfo_free(&tmp);
5336 if (!queued_removed)
5337 folder_item_remove_msg(folder, msgnum);
5338 folder_item_scan(folder);
5340 /* make sure we delete that */
5341 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5343 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5344 folder_item_remove_msg(folder, tmp->msgnum);
5345 procmsg_msginfo_free(&tmp);
5348 if (!discard_window) {
5349 compose->sending = FALSE;
5350 compose_allow_user_actions (compose, TRUE);
5351 compose_close(compose);
5355 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5356 "the main window to retry."), errstr);
5359 alertpanel_error_log(_("The message was queued but could not be "
5360 "sent.\nUse \"Send queued messages\" from "
5361 "the main window to retry."));
5363 if (!discard_window) {
5372 toolbar_main_set_sensitive(mainwin);
5373 main_window_set_menu_sensitive(mainwin);
5379 compose_allow_user_actions (compose, TRUE);
5380 compose->sending = FALSE;
5381 compose->modified = TRUE;
5382 toolbar_main_set_sensitive(mainwin);
5383 main_window_set_menu_sensitive(mainwin);
5388 static gboolean compose_use_attach(Compose *compose)
5390 GtkTreeModel *model = gtk_tree_view_get_model
5391 (GTK_TREE_VIEW(compose->attach_clist));
5392 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5395 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5398 gchar buf[BUFFSIZE];
5400 gboolean first_to_address;
5401 gboolean first_cc_address;
5403 ComposeHeaderEntry *headerentry;
5404 const gchar *headerentryname;
5405 const gchar *cc_hdr;
5406 const gchar *to_hdr;
5407 gboolean err = FALSE;
5409 debug_print("Writing redirect header\n");
5411 cc_hdr = prefs_common_translated_header_name("Cc:");
5412 to_hdr = prefs_common_translated_header_name("To:");
5414 first_to_address = TRUE;
5415 for (list = compose->header_list; list; list = list->next) {
5416 headerentry = ((ComposeHeaderEntry *)list->data);
5417 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5419 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5420 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5421 Xstrdup_a(str, entstr, return -1);
5423 if (str[0] != '\0') {
5424 compose_convert_header
5425 (compose, buf, sizeof(buf), str,
5426 strlen("Resent-To") + 2, TRUE);
5428 if (first_to_address) {
5429 err |= (fprintf(fp, "Resent-To: ") < 0);
5430 first_to_address = FALSE;
5432 err |= (fprintf(fp, ",") < 0);
5434 err |= (fprintf(fp, "%s", buf) < 0);
5438 if (!first_to_address) {
5439 err |= (fprintf(fp, "\n") < 0);
5442 first_cc_address = TRUE;
5443 for (list = compose->header_list; list; list = list->next) {
5444 headerentry = ((ComposeHeaderEntry *)list->data);
5445 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5447 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5448 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5449 Xstrdup_a(str, strg, return -1);
5451 if (str[0] != '\0') {
5452 compose_convert_header
5453 (compose, buf, sizeof(buf), str,
5454 strlen("Resent-Cc") + 2, TRUE);
5456 if (first_cc_address) {
5457 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5458 first_cc_address = FALSE;
5460 err |= (fprintf(fp, ",") < 0);
5462 err |= (fprintf(fp, "%s", buf) < 0);
5466 if (!first_cc_address) {
5467 err |= (fprintf(fp, "\n") < 0);
5470 return (err ? -1:0);
5473 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5475 gchar date[RFC822_DATE_BUFFSIZE];
5476 gchar buf[BUFFSIZE];
5478 const gchar *entstr;
5479 /* struct utsname utsbuf; */
5480 gboolean err = FALSE;
5482 cm_return_val_if_fail(fp != NULL, -1);
5483 cm_return_val_if_fail(compose->account != NULL, -1);
5484 cm_return_val_if_fail(compose->account->address != NULL, -1);
5487 if (prefs_common.hide_timezone)
5488 get_rfc822_date_hide_tz(date, sizeof(date));
5490 get_rfc822_date(date, sizeof(date));
5491 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5494 if (compose->account->name && *compose->account->name) {
5495 compose_convert_header
5496 (compose, buf, sizeof(buf), compose->account->name,
5497 strlen("From: "), TRUE);
5498 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5499 buf, compose->account->address) < 0);
5501 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5504 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5505 if (*entstr != '\0') {
5506 Xstrdup_a(str, entstr, return -1);
5509 compose_convert_header(compose, buf, sizeof(buf), str,
5510 strlen("Subject: "), FALSE);
5511 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5515 /* Resent-Message-ID */
5516 if (compose->account->gen_msgid) {
5517 gchar *addr = prefs_account_generate_msgid(compose->account);
5518 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5520 g_free(compose->msgid);
5521 compose->msgid = addr;
5523 compose->msgid = NULL;
5526 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5529 /* separator between header and body */
5530 err |= (fputs("\n", fp) == EOF);
5532 return (err ? -1:0);
5535 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5540 gchar rewrite_buf[BUFFSIZE];
5542 gboolean skip = FALSE;
5543 gboolean err = FALSE;
5544 gchar *not_included[]={
5545 "Return-Path:", "Delivered-To:", "Received:",
5546 "Subject:", "X-UIDL:", "AF:",
5547 "NF:", "PS:", "SRH:",
5548 "SFN:", "DSR:", "MID:",
5549 "CFG:", "PT:", "S:",
5550 "RQ:", "SSV:", "NSV:",
5551 "SSH:", "R:", "MAID:",
5552 "NAID:", "RMID:", "FMID:",
5553 "SCF:", "RRCPT:", "NG:",
5554 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5555 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5556 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5557 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5558 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5563 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5564 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5568 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5570 for (i = 0; not_included[i] != NULL; i++) {
5571 if (g_ascii_strncasecmp(buf, not_included[i],
5572 strlen(not_included[i])) == 0) {
5582 if (fputs(buf, fdest) == -1) {
5588 if (!prefs_common.redirect_keep_from) {
5589 if (g_ascii_strncasecmp(buf, "From:",
5590 strlen("From:")) == 0) {
5591 err |= (fputs(" (by way of ", fdest) == EOF);
5592 if (compose->account->name
5593 && *compose->account->name) {
5594 gchar buffer[BUFFSIZE];
5596 compose_convert_header
5597 (compose, buffer, sizeof(buffer),
5598 compose->account->name,
5601 err |= (fprintf(fdest, "%s <%s>",
5603 compose->account->address) < 0);
5605 err |= (fprintf(fdest, "%s",
5606 compose->account->address) < 0);
5607 err |= (fputs(")", fdest) == EOF);
5613 if (fputs("\n", fdest) == -1)
5620 if (compose_redirect_write_headers(compose, fdest))
5623 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5624 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5638 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5640 GtkTextBuffer *buffer;
5641 GtkTextIter start, end, tmp;
5642 gchar *chars, *tmp_enc_file, *content;
5644 const gchar *out_codeset;
5645 EncodingType encoding = ENC_UNKNOWN;
5646 MimeInfo *mimemsg, *mimetext;
5648 const gchar *src_codeset = CS_INTERNAL;
5649 gchar *from_addr = NULL;
5650 gchar *from_name = NULL;
5653 if (action == COMPOSE_WRITE_FOR_SEND) {
5654 attach_parts = TRUE;
5656 /* We're sending the message, generate a Message-ID
5658 if (compose->msgid == NULL &&
5659 compose->account->gen_msgid) {
5660 compose->msgid = prefs_account_generate_msgid(compose->account);
5664 /* create message MimeInfo */
5665 mimemsg = procmime_mimeinfo_new();
5666 mimemsg->type = MIMETYPE_MESSAGE;
5667 mimemsg->subtype = g_strdup("rfc822");
5668 mimemsg->content = MIMECONTENT_MEM;
5669 mimemsg->tmp = TRUE; /* must free content later */
5670 mimemsg->data.mem = compose_get_header(compose);
5672 /* Create text part MimeInfo */
5673 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5674 gtk_text_buffer_get_end_iter(buffer, &end);
5677 /* We make sure that there is a newline at the end. */
5678 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5679 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5680 if (*chars != '\n') {
5681 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5686 /* get all composed text */
5687 gtk_text_buffer_get_start_iter(buffer, &start);
5688 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5690 out_codeset = conv_get_charset_str(compose->out_encoding);
5692 if (!out_codeset && is_ascii_str(chars)) {
5693 out_codeset = CS_US_ASCII;
5694 } else if (prefs_common.outgoing_fallback_to_ascii &&
5695 is_ascii_str(chars)) {
5696 out_codeset = CS_US_ASCII;
5697 encoding = ENC_7BIT;
5701 gchar *test_conv_global_out = NULL;
5702 gchar *test_conv_reply = NULL;
5704 /* automatic mode. be automatic. */
5705 codeconv_set_strict(TRUE);
5707 out_codeset = conv_get_outgoing_charset_str();
5709 debug_print("trying to convert to %s\n", out_codeset);
5710 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5713 if (!test_conv_global_out && compose->orig_charset
5714 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5715 out_codeset = compose->orig_charset;
5716 debug_print("failure; trying to convert to %s\n", out_codeset);
5717 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5720 if (!test_conv_global_out && !test_conv_reply) {
5722 out_codeset = CS_INTERNAL;
5723 debug_print("failure; finally using %s\n", out_codeset);
5725 g_free(test_conv_global_out);
5726 g_free(test_conv_reply);
5727 codeconv_set_strict(FALSE);
5730 if (encoding == ENC_UNKNOWN) {
5731 if (prefs_common.encoding_method == CTE_BASE64)
5732 encoding = ENC_BASE64;
5733 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5734 encoding = ENC_QUOTED_PRINTABLE;
5735 else if (prefs_common.encoding_method == CTE_8BIT)
5736 encoding = ENC_8BIT;
5738 encoding = procmime_get_encoding_for_charset(out_codeset);
5741 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5742 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5744 if (action == COMPOSE_WRITE_FOR_SEND) {
5745 codeconv_set_strict(TRUE);
5746 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5747 codeconv_set_strict(FALSE);
5752 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5753 "to the specified %s charset.\n"
5754 "Send it as %s?"), out_codeset, src_codeset);
5755 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5756 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5757 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5760 if (aval != G_ALERTALTERNATE) {
5765 out_codeset = src_codeset;
5771 out_codeset = src_codeset;
5776 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5777 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5778 strstr(buf, "\nFrom ") != NULL) {
5779 encoding = ENC_QUOTED_PRINTABLE;
5783 mimetext = procmime_mimeinfo_new();
5784 mimetext->content = MIMECONTENT_MEM;
5785 mimetext->tmp = TRUE; /* must free content later */
5786 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5787 * and free the data, which we need later. */
5788 mimetext->data.mem = g_strdup(buf);
5789 mimetext->type = MIMETYPE_TEXT;
5790 mimetext->subtype = g_strdup("plain");
5791 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5792 g_strdup(out_codeset));
5794 /* protect trailing spaces when signing message */
5795 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5796 privacy_system_can_sign(compose->privacy_system)) {
5797 encoding = ENC_QUOTED_PRINTABLE;
5801 debug_print("main text: %Id bytes encoded as %s in %d\n",
5803 debug_print("main text: %zd bytes encoded as %s in %d\n",
5805 strlen(buf), out_codeset, encoding);
5807 /* check for line length limit */
5808 if (action == COMPOSE_WRITE_FOR_SEND &&
5809 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5810 check_line_length(buf, 1000, &line) < 0) {
5813 msg = g_strdup_printf
5814 (_("Line %d exceeds the line length limit (998 bytes).\n"
5815 "The contents of the message might be broken on the way to the delivery.\n"
5817 "Send it anyway?"), line + 1);
5818 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5820 if (aval != G_ALERTALTERNATE) {
5826 if (encoding != ENC_UNKNOWN)
5827 procmime_encode_content(mimetext, encoding);
5829 /* append attachment parts */
5830 if (compose_use_attach(compose) && attach_parts) {
5831 MimeInfo *mimempart;
5832 gchar *boundary = NULL;
5833 mimempart = procmime_mimeinfo_new();
5834 mimempart->content = MIMECONTENT_EMPTY;
5835 mimempart->type = MIMETYPE_MULTIPART;
5836 mimempart->subtype = g_strdup("mixed");
5840 boundary = generate_mime_boundary(NULL);
5841 } while (strstr(buf, boundary) != NULL);
5843 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5846 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5848 g_node_append(mimempart->node, mimetext->node);
5849 g_node_append(mimemsg->node, mimempart->node);
5851 if (compose_add_attachments(compose, mimempart) < 0)
5854 g_node_append(mimemsg->node, mimetext->node);
5858 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5859 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5860 /* extract name and address */
5861 if (strstr(spec, " <") && strstr(spec, ">")) {
5862 from_addr = g_strdup(strrchr(spec, '<')+1);
5863 *(strrchr(from_addr, '>')) = '\0';
5864 from_name = g_strdup(spec);
5865 *(strrchr(from_name, '<')) = '\0';
5872 /* sign message if sending */
5873 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5874 privacy_system_can_sign(compose->privacy_system))
5875 if (!privacy_sign(compose->privacy_system, mimemsg,
5876 compose->account, from_addr)) {
5884 if (compose->use_encryption) {
5885 if (compose->encdata != NULL &&
5886 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5888 /* First, write an unencrypted copy and save it to outbox, if
5889 * user wants that. */
5890 if (compose->account->save_encrypted_as_clear_text) {
5891 debug_print("saving sent message unencrypted...\n");
5892 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5896 /* fp now points to a file with headers written,
5897 * let's make a copy. */
5899 content = file_read_stream_to_str(fp);
5901 str_write_to_file(content, tmp_enc_file);
5904 /* Now write the unencrypted body. */
5905 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5906 procmime_write_mimeinfo(mimemsg, tmpfp);
5909 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5911 outbox = folder_get_default_outbox();
5913 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5914 claws_unlink(tmp_enc_file);
5916 g_warning("Can't open file '%s'", tmp_enc_file);
5919 g_warning("couldn't get tempfile");
5922 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5923 debug_print("Couldn't encrypt mime structure: %s.\n",
5924 privacy_get_error());
5925 alertpanel_error(_("Couldn't encrypt the email: %s"),
5926 privacy_get_error());
5931 procmime_write_mimeinfo(mimemsg, fp);
5933 procmime_mimeinfo_free_all(&mimemsg);
5938 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5940 GtkTextBuffer *buffer;
5941 GtkTextIter start, end;
5946 if ((fp = g_fopen(file, "wb")) == NULL) {
5947 FILE_OP_ERROR(file, "fopen");
5951 /* chmod for security */
5952 if (change_file_mode_rw(fp, file) < 0) {
5953 FILE_OP_ERROR(file, "chmod");
5954 g_warning("can't change file mode");
5957 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5958 gtk_text_buffer_get_start_iter(buffer, &start);
5959 gtk_text_buffer_get_end_iter(buffer, &end);
5960 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5962 chars = conv_codeset_strdup
5963 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5972 len = strlen(chars);
5973 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5974 FILE_OP_ERROR(file, "fwrite");
5983 if (fclose(fp) == EOF) {
5984 FILE_OP_ERROR(file, "fclose");
5991 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5994 MsgInfo *msginfo = compose->targetinfo;
5996 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5997 if (!msginfo) return -1;
5999 if (!force && MSG_IS_LOCKED(msginfo->flags))
6002 item = msginfo->folder;
6003 cm_return_val_if_fail(item != NULL, -1);
6005 if (procmsg_msg_exist(msginfo) &&
6006 (folder_has_parent_of_type(item, F_QUEUE) ||
6007 folder_has_parent_of_type(item, F_DRAFT)
6008 || msginfo == compose->autosaved_draft)) {
6009 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6010 g_warning("can't remove the old message");
6013 debug_print("removed reedit target %d\n", msginfo->msgnum);
6020 static void compose_remove_draft(Compose *compose)
6023 MsgInfo *msginfo = compose->targetinfo;
6024 drafts = account_get_special_folder(compose->account, F_DRAFT);
6026 if (procmsg_msg_exist(msginfo)) {
6027 folder_item_remove_msg(drafts, msginfo->msgnum);
6032 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6033 gboolean remove_reedit_target)
6035 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6038 static gboolean compose_warn_encryption(Compose *compose)
6040 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6041 AlertValue val = G_ALERTALTERNATE;
6043 if (warning == NULL)
6046 val = alertpanel_full(_("Encryption warning"), warning,
6047 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6048 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6049 if (val & G_ALERTDISABLE) {
6050 val &= ~G_ALERTDISABLE;
6051 if (val == G_ALERTALTERNATE)
6052 privacy_inhibit_encrypt_warning(compose->privacy_system,
6056 if (val == G_ALERTALTERNATE) {
6063 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6064 gchar **msgpath, gboolean perform_checks,
6065 gboolean remove_reedit_target)
6072 PrefsAccount *mailac = NULL, *newsac = NULL;
6073 gboolean err = FALSE;
6075 debug_print("queueing message...\n");
6076 cm_return_val_if_fail(compose->account != NULL, -1);
6078 if (compose_check_entries(compose, perform_checks) == FALSE) {
6079 if (compose->batch) {
6080 gtk_widget_show_all(compose->window);
6085 if (!compose->to_list && !compose->newsgroup_list) {
6086 g_warning("can't get recipient list.");
6090 if (compose->to_list) {
6091 if (compose->account->protocol != A_NNTP)
6092 mailac = compose->account;
6093 else if (cur_account && cur_account->protocol != A_NNTP)
6094 mailac = cur_account;
6095 else if (!(mailac = compose_current_mail_account())) {
6096 alertpanel_error(_("No account for sending mails available!"));
6101 if (compose->newsgroup_list) {
6102 if (compose->account->protocol == A_NNTP)
6103 newsac = compose->account;
6105 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6110 /* write queue header */
6111 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6112 G_DIR_SEPARATOR, compose, (guint) rand());
6113 debug_print("queuing to %s\n", tmp);
6114 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6115 FILE_OP_ERROR(tmp, "fopen");
6120 if (change_file_mode_rw(fp, tmp) < 0) {
6121 FILE_OP_ERROR(tmp, "chmod");
6122 g_warning("can't change file mode");
6125 /* queueing variables */
6126 err |= (fprintf(fp, "AF:\n") < 0);
6127 err |= (fprintf(fp, "NF:0\n") < 0);
6128 err |= (fprintf(fp, "PS:10\n") < 0);
6129 err |= (fprintf(fp, "SRH:1\n") < 0);
6130 err |= (fprintf(fp, "SFN:\n") < 0);
6131 err |= (fprintf(fp, "DSR:\n") < 0);
6133 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6135 err |= (fprintf(fp, "MID:\n") < 0);
6136 err |= (fprintf(fp, "CFG:\n") < 0);
6137 err |= (fprintf(fp, "PT:0\n") < 0);
6138 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6139 err |= (fprintf(fp, "RQ:\n") < 0);
6141 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6143 err |= (fprintf(fp, "SSV:\n") < 0);
6145 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6147 err |= (fprintf(fp, "NSV:\n") < 0);
6148 err |= (fprintf(fp, "SSH:\n") < 0);
6149 /* write recepient list */
6150 if (compose->to_list) {
6151 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6152 for (cur = compose->to_list->next; cur != NULL;
6154 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6155 err |= (fprintf(fp, "\n") < 0);
6157 /* write newsgroup list */
6158 if (compose->newsgroup_list) {
6159 err |= (fprintf(fp, "NG:") < 0);
6160 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6161 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6162 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6163 err |= (fprintf(fp, "\n") < 0);
6165 /* Sylpheed account IDs */
6167 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6169 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6172 if (compose->privacy_system != NULL) {
6173 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6174 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6175 if (compose->use_encryption) {
6176 if (!compose_warn_encryption(compose)) {
6182 if (mailac && mailac->encrypt_to_self) {
6183 GSList *tmp_list = g_slist_copy(compose->to_list);
6184 tmp_list = g_slist_append(tmp_list, compose->account->address);
6185 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6186 g_slist_free(tmp_list);
6188 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6190 if (compose->encdata != NULL) {
6191 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6192 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6193 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6194 compose->encdata) < 0);
6195 } /* else we finally dont want to encrypt */
6197 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6198 /* and if encdata was null, it means there's been a problem in
6201 g_warning("failed to write queue message");
6210 /* Save copy folder */
6211 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6212 gchar *savefolderid;
6214 savefolderid = compose_get_save_to(compose);
6215 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6216 g_free(savefolderid);
6218 /* Save copy folder */
6219 if (compose->return_receipt) {
6220 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6222 /* Message-ID of message replying to */
6223 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6224 gchar *folderid = NULL;
6226 if (compose->replyinfo->folder)
6227 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6228 if (folderid == NULL)
6229 folderid = g_strdup("NULL");
6231 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6234 /* Message-ID of message forwarding to */
6235 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6236 gchar *folderid = NULL;
6238 if (compose->fwdinfo->folder)
6239 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6240 if (folderid == NULL)
6241 folderid = g_strdup("NULL");
6243 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6247 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6248 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6250 /* end of headers */
6251 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6253 if (compose->redirect_filename != NULL) {
6254 if (compose_redirect_write_to_file(compose, fp) < 0) {
6262 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6266 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6270 g_warning("failed to write queue message");
6276 if (fclose(fp) == EOF) {
6277 FILE_OP_ERROR(tmp, "fclose");
6283 if (item && *item) {
6286 queue = account_get_special_folder(compose->account, F_QUEUE);
6289 g_warning("can't find queue folder");
6294 folder_item_scan(queue);
6295 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6296 g_warning("can't queue the message");
6302 if (msgpath == NULL) {
6308 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6309 compose_remove_reedit_target(compose, FALSE);
6312 if ((msgnum != NULL) && (item != NULL)) {
6320 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6323 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6326 gchar *type, *subtype;
6327 GtkTreeModel *model;
6330 model = gtk_tree_view_get_model(tree_view);
6332 if (!gtk_tree_model_get_iter_first(model, &iter))
6335 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6337 if (!is_file_exist(ainfo->file)) {
6338 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6339 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6340 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6342 if (val == G_ALERTDEFAULT) {
6347 if (g_stat(ainfo->file, &statbuf) < 0)
6350 mimepart = procmime_mimeinfo_new();
6351 mimepart->content = MIMECONTENT_FILE;
6352 mimepart->data.filename = g_strdup(ainfo->file);
6353 mimepart->tmp = FALSE; /* or we destroy our attachment */
6354 mimepart->offset = 0;
6355 mimepart->length = statbuf.st_size;
6357 type = g_strdup(ainfo->content_type);
6359 if (!strchr(type, '/')) {
6361 type = g_strdup("application/octet-stream");
6364 subtype = strchr(type, '/') + 1;
6365 *(subtype - 1) = '\0';
6366 mimepart->type = procmime_get_media_type(type);
6367 mimepart->subtype = g_strdup(subtype);
6370 if (mimepart->type == MIMETYPE_MESSAGE &&
6371 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6372 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6373 } else if (mimepart->type == MIMETYPE_TEXT) {
6374 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6375 /* Text parts with no name come from multipart/alternative
6376 * forwards. Make sure the recipient won't look at the
6377 * original HTML part by mistake. */
6378 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6379 ainfo->name = g_strdup_printf(_("Original %s part"),
6383 g_hash_table_insert(mimepart->typeparameters,
6384 g_strdup("charset"), g_strdup(ainfo->charset));
6386 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6387 if (mimepart->type == MIMETYPE_APPLICATION &&
6388 !strcmp2(mimepart->subtype, "octet-stream"))
6389 g_hash_table_insert(mimepart->typeparameters,
6390 g_strdup("name"), g_strdup(ainfo->name));
6391 g_hash_table_insert(mimepart->dispositionparameters,
6392 g_strdup("filename"), g_strdup(ainfo->name));
6393 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6396 if (mimepart->type == MIMETYPE_MESSAGE
6397 || mimepart->type == MIMETYPE_MULTIPART)
6398 ainfo->encoding = ENC_BINARY;
6399 else if (compose->use_signing) {
6400 if (ainfo->encoding == ENC_7BIT)
6401 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6402 else if (ainfo->encoding == ENC_8BIT)
6403 ainfo->encoding = ENC_BASE64;
6406 procmime_encode_content(mimepart, ainfo->encoding);
6408 g_node_append(parent->node, mimepart->node);
6409 } while (gtk_tree_model_iter_next(model, &iter));
6414 static gchar *compose_quote_list_of_addresses(gchar *str)
6416 GSList *list = NULL, *item = NULL;
6417 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6419 list = address_list_append_with_comments(list, str);
6420 for (item = list; item != NULL; item = item->next) {
6421 gchar *spec = item->data;
6422 gchar *endofname = strstr(spec, " <");
6423 if (endofname != NULL) {
6426 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6427 qqname = escape_internal_quotes(qname, '"');
6429 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6430 gchar *addr = g_strdup(endofname);
6431 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6432 faddr = g_strconcat(name, addr, NULL);
6435 debug_print("new auto-quoted address: '%s'\n", faddr);
6439 result = g_strdup((faddr != NULL)? faddr: spec);
6441 result = g_strconcat(result,
6443 (faddr != NULL)? faddr: spec,
6446 if (faddr != NULL) {
6451 slist_free_strings_full(list);
6456 #define IS_IN_CUSTOM_HEADER(header) \
6457 (compose->account->add_customhdr && \
6458 custom_header_find(compose->account->customhdr_list, header) != NULL)
6460 static const gchar *compose_untranslated_header_name(gchar *header_name)
6462 /* return the untranslated header name, if header_name is a known
6463 header name, in either its translated or untranslated form, with
6464 or without trailing colon. otherwise, returns header_name. */
6465 gchar *translated_header_name;
6466 gchar *translated_header_name_wcolon;
6467 const gchar *untranslated_header_name;
6468 const gchar *untranslated_header_name_wcolon;
6471 cm_return_val_if_fail(header_name != NULL, NULL);
6473 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6474 untranslated_header_name = HEADERS[i].header_name;
6475 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6477 translated_header_name = gettext(untranslated_header_name);
6478 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6480 if (!strcmp(header_name, untranslated_header_name) ||
6481 !strcmp(header_name, translated_header_name)) {
6482 return untranslated_header_name;
6484 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6485 !strcmp(header_name, translated_header_name_wcolon)) {
6486 return untranslated_header_name_wcolon;
6490 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6494 static void compose_add_headerfield_from_headerlist(Compose *compose,
6496 const gchar *fieldname,
6497 const gchar *seperator)
6499 gchar *str, *fieldname_w_colon;
6500 gboolean add_field = FALSE;
6502 ComposeHeaderEntry *headerentry;
6503 const gchar *headerentryname;
6504 const gchar *trans_fieldname;
6507 if (IS_IN_CUSTOM_HEADER(fieldname))
6510 debug_print("Adding %s-fields\n", fieldname);
6512 fieldstr = g_string_sized_new(64);
6514 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6515 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6517 for (list = compose->header_list; list; list = list->next) {
6518 headerentry = ((ComposeHeaderEntry *)list->data);
6519 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6521 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6522 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6524 str = compose_quote_list_of_addresses(ustr);
6526 if (str != NULL && str[0] != '\0') {
6528 g_string_append(fieldstr, seperator);
6529 g_string_append(fieldstr, str);
6538 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6539 compose_convert_header
6540 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6541 strlen(fieldname) + 2, TRUE);
6542 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6546 g_free(fieldname_w_colon);
6547 g_string_free(fieldstr, TRUE);
6552 static gchar *compose_get_manual_headers_info(Compose *compose)
6554 GString *sh_header = g_string_new(" ");
6556 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6558 for (list = compose->header_list; list; list = list->next) {
6559 ComposeHeaderEntry *headerentry;
6562 gchar *headername_wcolon;
6563 const gchar *headername_trans;
6565 gboolean standard_header = FALSE;
6567 headerentry = ((ComposeHeaderEntry *)list->data);
6569 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6571 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6576 if (!strstr(tmp, ":")) {
6577 headername_wcolon = g_strconcat(tmp, ":", NULL);
6578 headername = g_strdup(tmp);
6580 headername_wcolon = g_strdup(tmp);
6581 headername = g_strdup(strtok(tmp, ":"));
6585 string = std_headers;
6586 while (*string != NULL) {
6587 headername_trans = prefs_common_translated_header_name(*string);
6588 if (!strcmp(headername_trans, headername_wcolon))
6589 standard_header = TRUE;
6592 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6593 g_string_append_printf(sh_header, "%s ", headername);
6595 g_free(headername_wcolon);
6597 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6598 return g_string_free(sh_header, FALSE);
6601 static gchar *compose_get_header(Compose *compose)
6603 gchar date[RFC822_DATE_BUFFSIZE];
6604 gchar buf[BUFFSIZE];
6605 const gchar *entry_str;
6609 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6611 gchar *from_name = NULL, *from_address = NULL;
6614 cm_return_val_if_fail(compose->account != NULL, NULL);
6615 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6617 header = g_string_sized_new(64);
6620 if (prefs_common.hide_timezone)
6621 get_rfc822_date_hide_tz(date, sizeof(date));
6623 get_rfc822_date(date, sizeof(date));
6624 g_string_append_printf(header, "Date: %s\n", date);
6628 if (compose->account->name && *compose->account->name) {
6630 QUOTE_IF_REQUIRED(buf, compose->account->name);
6631 tmp = g_strdup_printf("%s <%s>",
6632 buf, compose->account->address);
6634 tmp = g_strdup_printf("%s",
6635 compose->account->address);
6637 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6638 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6640 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6641 from_address = g_strdup(compose->account->address);
6643 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6644 /* extract name and address */
6645 if (strstr(spec, " <") && strstr(spec, ">")) {
6646 from_address = g_strdup(strrchr(spec, '<')+1);
6647 *(strrchr(from_address, '>')) = '\0';
6648 from_name = g_strdup(spec);
6649 *(strrchr(from_name, '<')) = '\0';
6652 from_address = g_strdup(spec);
6659 if (from_name && *from_name) {
6661 compose_convert_header
6662 (compose, buf, sizeof(buf), from_name,
6663 strlen("From: "), TRUE);
6664 QUOTE_IF_REQUIRED(name, buf);
6665 qname = escape_internal_quotes(name, '"');
6667 g_string_append_printf(header, "From: %s <%s>\n",
6668 qname, from_address);
6669 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6670 compose->return_receipt) {
6671 compose_convert_header(compose, buf, sizeof(buf), from_name,
6672 strlen("Disposition-Notification-To: "),
6674 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6679 g_string_append_printf(header, "From: %s\n", from_address);
6680 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6681 compose->return_receipt)
6682 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6686 g_free(from_address);
6689 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6692 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6695 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6699 * If this account is a NNTP account remove Bcc header from
6700 * message body since it otherwise will be publicly shown
6702 if (compose->account->protocol != A_NNTP)
6703 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6706 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6708 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6711 compose_convert_header(compose, buf, sizeof(buf), str,
6712 strlen("Subject: "), FALSE);
6713 g_string_append_printf(header, "Subject: %s\n", buf);
6719 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6720 g_string_append_printf(header, "Message-ID: <%s>\n",
6724 if (compose->remove_references == FALSE) {
6726 if (compose->inreplyto && compose->to_list)
6727 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6730 if (compose->references)
6731 g_string_append_printf(header, "References: %s\n", compose->references);
6735 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6738 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6741 if (compose->account->organization &&
6742 strlen(compose->account->organization) &&
6743 !IS_IN_CUSTOM_HEADER("Organization")) {
6744 compose_convert_header(compose, buf, sizeof(buf),
6745 compose->account->organization,
6746 strlen("Organization: "), FALSE);
6747 g_string_append_printf(header, "Organization: %s\n", buf);
6750 /* Program version and system info */
6751 if (compose->account->gen_xmailer &&
6752 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6753 !compose->newsgroup_list) {
6754 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6756 gtk_major_version, gtk_minor_version, gtk_micro_version,
6759 if (compose->account->gen_xmailer &&
6760 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6761 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6763 gtk_major_version, gtk_minor_version, gtk_micro_version,
6767 /* custom headers */
6768 if (compose->account->add_customhdr) {
6771 for (cur = compose->account->customhdr_list; cur != NULL;
6773 CustomHeader *chdr = (CustomHeader *)cur->data;
6775 if (custom_header_is_allowed(chdr->name)
6776 && chdr->value != NULL
6777 && *(chdr->value) != '\0') {
6778 compose_convert_header
6779 (compose, buf, sizeof(buf),
6781 strlen(chdr->name) + 2, FALSE);
6782 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6787 /* Automatic Faces and X-Faces */
6788 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6789 g_string_append_printf(header, "X-Face: %s\n", buf);
6791 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6792 g_string_append_printf(header, "X-Face: %s\n", buf);
6794 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6795 g_string_append_printf(header, "Face: %s\n", buf);
6797 else if (get_default_face (buf, sizeof(buf)) == 0) {
6798 g_string_append_printf(header, "Face: %s\n", buf);
6802 switch (compose->priority) {
6803 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6804 "X-Priority: 1 (Highest)\n");
6806 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6807 "X-Priority: 2 (High)\n");
6809 case PRIORITY_NORMAL: break;
6810 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6811 "X-Priority: 4 (Low)\n");
6813 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6814 "X-Priority: 5 (Lowest)\n");
6816 default: debug_print("compose: priority unknown : %d\n",
6820 /* get special headers */
6821 for (list = compose->header_list; list; list = list->next) {
6822 ComposeHeaderEntry *headerentry;
6825 gchar *headername_wcolon;
6826 const gchar *headername_trans;
6829 gboolean standard_header = FALSE;
6831 headerentry = ((ComposeHeaderEntry *)list->data);
6833 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6835 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6840 if (!strstr(tmp, ":")) {
6841 headername_wcolon = g_strconcat(tmp, ":", NULL);
6842 headername = g_strdup(tmp);
6844 headername_wcolon = g_strdup(tmp);
6845 headername = g_strdup(strtok(tmp, ":"));
6849 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6850 Xstrdup_a(headervalue, entry_str, return NULL);
6851 subst_char(headervalue, '\r', ' ');
6852 subst_char(headervalue, '\n', ' ');
6853 g_strstrip(headervalue);
6854 if (*headervalue != '\0') {
6855 string = std_headers;
6856 while (*string != NULL && !standard_header) {
6857 headername_trans = prefs_common_translated_header_name(*string);
6858 /* support mixed translated and untranslated headers */
6859 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6860 standard_header = TRUE;
6863 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6864 /* store untranslated header name */
6865 g_string_append_printf(header, "%s %s\n",
6866 compose_untranslated_header_name(headername_wcolon), headervalue);
6870 g_free(headername_wcolon);
6874 g_string_free(header, FALSE);
6879 #undef IS_IN_CUSTOM_HEADER
6881 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6882 gint header_len, gboolean addr_field)
6884 gchar *tmpstr = NULL;
6885 const gchar *out_codeset = NULL;
6887 cm_return_if_fail(src != NULL);
6888 cm_return_if_fail(dest != NULL);
6890 if (len < 1) return;
6892 tmpstr = g_strdup(src);
6894 subst_char(tmpstr, '\n', ' ');
6895 subst_char(tmpstr, '\r', ' ');
6898 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6899 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6900 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6905 codeconv_set_strict(TRUE);
6906 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6907 conv_get_charset_str(compose->out_encoding));
6908 codeconv_set_strict(FALSE);
6910 if (!dest || *dest == '\0') {
6911 gchar *test_conv_global_out = NULL;
6912 gchar *test_conv_reply = NULL;
6914 /* automatic mode. be automatic. */
6915 codeconv_set_strict(TRUE);
6917 out_codeset = conv_get_outgoing_charset_str();
6919 debug_print("trying to convert to %s\n", out_codeset);
6920 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6923 if (!test_conv_global_out && compose->orig_charset
6924 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6925 out_codeset = compose->orig_charset;
6926 debug_print("failure; trying to convert to %s\n", out_codeset);
6927 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6930 if (!test_conv_global_out && !test_conv_reply) {
6932 out_codeset = CS_INTERNAL;
6933 debug_print("finally using %s\n", out_codeset);
6935 g_free(test_conv_global_out);
6936 g_free(test_conv_reply);
6937 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6939 codeconv_set_strict(FALSE);
6944 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6948 cm_return_if_fail(user_data != NULL);
6950 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6951 g_strstrip(address);
6952 if (*address != '\0') {
6953 gchar *name = procheader_get_fromname(address);
6954 extract_address(address);
6955 #ifndef USE_ALT_ADDRBOOK
6956 addressbook_add_contact(name, address, NULL, NULL);
6958 debug_print("%s: %s\n", name, address);
6959 if (addressadd_selection(name, address, NULL, NULL)) {
6960 debug_print( "addressbook_add_contact - added\n" );
6967 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6969 GtkWidget *menuitem;
6972 cm_return_if_fail(menu != NULL);
6973 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6975 menuitem = gtk_separator_menu_item_new();
6976 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6977 gtk_widget_show(menuitem);
6979 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6980 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6982 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6983 g_strstrip(address);
6984 if (*address == '\0') {
6985 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6988 g_signal_connect(G_OBJECT(menuitem), "activate",
6989 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6990 gtk_widget_show(menuitem);
6993 void compose_add_extra_header(gchar *header, GtkListStore *model)
6996 if (strcmp(header, "")) {
6997 COMBOBOX_ADD(model, header, COMPOSE_TO);
7001 void compose_add_extra_header_entries(GtkListStore *model)
7005 gchar buf[BUFFSIZE];
7008 if (extra_headers == NULL) {
7009 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7010 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7011 debug_print("extra headers file not found\n");
7012 goto extra_headers_done;
7014 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7015 lastc = strlen(buf) - 1; /* remove trailing control chars */
7016 while (lastc >= 0 && buf[lastc] != ':')
7017 buf[lastc--] = '\0';
7018 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7019 buf[lastc] = '\0'; /* remove trailing : for comparison */
7020 if (custom_header_is_allowed(buf)) {
7022 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7025 g_message("disallowed extra header line: %s\n", buf);
7029 g_message("invalid extra header line: %s\n", buf);
7035 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7036 extra_headers = g_slist_reverse(extra_headers);
7038 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7041 static void compose_create_header_entry(Compose *compose)
7043 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7050 const gchar *header = NULL;
7051 ComposeHeaderEntry *headerentry;
7052 gboolean standard_header = FALSE;
7053 GtkListStore *model;
7056 headerentry = g_new0(ComposeHeaderEntry, 1);
7058 /* Combo box model */
7059 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7060 #if !GTK_CHECK_VERSION(2, 24, 0)
7061 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7063 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7065 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7067 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7069 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7070 COMPOSE_NEWSGROUPS);
7071 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7073 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7074 COMPOSE_FOLLOWUPTO);
7075 compose_add_extra_header_entries(model);
7078 #if GTK_CHECK_VERSION(2, 24, 0)
7079 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7080 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7081 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7082 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7083 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7085 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7086 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7087 G_CALLBACK(compose_grab_focus_cb), compose);
7088 gtk_widget_show(combo);
7090 /* Putting only the combobox child into focus chain of its parent causes
7091 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7092 * This eliminates need to pres Tab twice in order to really get from the
7093 * combobox to next widget. */
7095 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7096 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7099 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7100 compose->header_nextrow, compose->header_nextrow+1,
7101 GTK_SHRINK, GTK_FILL, 0, 0);
7102 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7103 const gchar *last_header_entry = gtk_entry_get_text(
7104 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7106 while (*string != NULL) {
7107 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7108 standard_header = TRUE;
7111 if (standard_header)
7112 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7114 if (!compose->header_last || !standard_header) {
7115 switch(compose->account->protocol) {
7117 header = prefs_common_translated_header_name("Newsgroups:");
7120 header = prefs_common_translated_header_name("To:");
7125 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7127 gtk_editable_set_editable(
7128 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7129 prefs_common.type_any_header);
7131 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7132 G_CALLBACK(compose_grab_focus_cb), compose);
7134 /* Entry field with cleanup button */
7135 button = gtk_button_new();
7136 gtk_button_set_image(GTK_BUTTON(button),
7137 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7138 gtk_widget_show(button);
7139 CLAWS_SET_TIP(button,
7140 _("Delete entry contents"));
7141 entry = gtk_entry_new();
7142 gtk_widget_show(entry);
7143 CLAWS_SET_TIP(entry,
7144 _("Use <tab> to autocomplete from addressbook"));
7145 hbox = gtk_hbox_new (FALSE, 0);
7146 gtk_widget_show(hbox);
7147 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7148 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7149 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7150 compose->header_nextrow, compose->header_nextrow+1,
7151 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7153 g_signal_connect(G_OBJECT(entry), "key-press-event",
7154 G_CALLBACK(compose_headerentry_key_press_event_cb),
7156 g_signal_connect(G_OBJECT(entry), "changed",
7157 G_CALLBACK(compose_headerentry_changed_cb),
7159 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7160 G_CALLBACK(compose_grab_focus_cb), compose);
7162 g_signal_connect(G_OBJECT(button), "clicked",
7163 G_CALLBACK(compose_headerentry_button_clicked_cb),
7167 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7168 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7169 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7170 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7171 G_CALLBACK(compose_header_drag_received_cb),
7173 g_signal_connect(G_OBJECT(entry), "drag-drop",
7174 G_CALLBACK(compose_drag_drop),
7176 g_signal_connect(G_OBJECT(entry), "populate-popup",
7177 G_CALLBACK(compose_entry_popup_extend),
7180 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7182 headerentry->compose = compose;
7183 headerentry->combo = combo;
7184 headerentry->entry = entry;
7185 headerentry->button = button;
7186 headerentry->hbox = hbox;
7187 headerentry->headernum = compose->header_nextrow;
7188 headerentry->type = PREF_NONE;
7190 compose->header_nextrow++;
7191 compose->header_last = headerentry;
7192 compose->header_list =
7193 g_slist_append(compose->header_list,
7197 static void compose_add_header_entry(Compose *compose, const gchar *header,
7198 gchar *text, ComposePrefType pref_type)
7200 ComposeHeaderEntry *last_header = compose->header_last;
7201 gchar *tmp = g_strdup(text), *email;
7202 gboolean replyto_hdr;
7204 replyto_hdr = (!strcasecmp(header,
7205 prefs_common_translated_header_name("Reply-To:")) ||
7207 prefs_common_translated_header_name("Followup-To:")) ||
7209 prefs_common_translated_header_name("In-Reply-To:")));
7211 extract_address(tmp);
7212 email = g_utf8_strdown(tmp, -1);
7214 if (replyto_hdr == FALSE &&
7215 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7217 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7218 header, text, (gint) pref_type);
7224 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7225 gtk_entry_set_text(GTK_ENTRY(
7226 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7228 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7229 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7230 last_header->type = pref_type;
7232 if (replyto_hdr == FALSE)
7233 g_hash_table_insert(compose->email_hashtable, email,
7234 GUINT_TO_POINTER(1));
7241 static void compose_destroy_headerentry(Compose *compose,
7242 ComposeHeaderEntry *headerentry)
7244 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7247 extract_address(text);
7248 email = g_utf8_strdown(text, -1);
7249 g_hash_table_remove(compose->email_hashtable, email);
7253 gtk_widget_destroy(headerentry->combo);
7254 gtk_widget_destroy(headerentry->entry);
7255 gtk_widget_destroy(headerentry->button);
7256 gtk_widget_destroy(headerentry->hbox);
7257 g_free(headerentry);
7260 static void compose_remove_header_entries(Compose *compose)
7263 for (list = compose->header_list; list; list = list->next)
7264 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7266 compose->header_last = NULL;
7267 g_slist_free(compose->header_list);
7268 compose->header_list = NULL;
7269 compose->header_nextrow = 1;
7270 compose_create_header_entry(compose);
7273 static GtkWidget *compose_create_header(Compose *compose)
7275 GtkWidget *from_optmenu_hbox;
7276 GtkWidget *header_table_main;
7277 GtkWidget *header_scrolledwin;
7278 GtkWidget *header_table;
7280 /* parent with account selection and from header */
7281 header_table_main = gtk_table_new(2, 2, FALSE);
7282 gtk_widget_show(header_table_main);
7283 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7285 from_optmenu_hbox = compose_account_option_menu_create(compose);
7286 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7287 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7289 /* child with header labels and entries */
7290 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7291 gtk_widget_show(header_scrolledwin);
7292 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7294 header_table = gtk_table_new(2, 2, FALSE);
7295 gtk_widget_show(header_table);
7296 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7297 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7298 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7299 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7300 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7302 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7303 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7305 compose->header_table = header_table;
7306 compose->header_list = NULL;
7307 compose->header_nextrow = 0;
7309 compose_create_header_entry(compose);
7311 compose->table = NULL;
7313 return header_table_main;
7316 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7318 Compose *compose = (Compose *)data;
7319 GdkEventButton event;
7322 event.time = gtk_get_current_event_time();
7324 return attach_button_pressed(compose->attach_clist, &event, compose);
7327 static GtkWidget *compose_create_attach(Compose *compose)
7329 GtkWidget *attach_scrwin;
7330 GtkWidget *attach_clist;
7332 GtkListStore *store;
7333 GtkCellRenderer *renderer;
7334 GtkTreeViewColumn *column;
7335 GtkTreeSelection *selection;
7337 /* attachment list */
7338 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7339 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7340 GTK_POLICY_AUTOMATIC,
7341 GTK_POLICY_AUTOMATIC);
7342 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7344 store = gtk_list_store_new(N_ATTACH_COLS,
7350 G_TYPE_AUTO_POINTER,
7352 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7353 (GTK_TREE_MODEL(store)));
7354 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7355 g_object_unref(store);
7357 renderer = gtk_cell_renderer_text_new();
7358 column = gtk_tree_view_column_new_with_attributes
7359 (_("Mime type"), renderer, "text",
7360 COL_MIMETYPE, NULL);
7361 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7363 renderer = gtk_cell_renderer_text_new();
7364 column = gtk_tree_view_column_new_with_attributes
7365 (_("Size"), renderer, "text",
7367 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7369 renderer = gtk_cell_renderer_text_new();
7370 column = gtk_tree_view_column_new_with_attributes
7371 (_("Name"), renderer, "text",
7373 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7375 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7376 prefs_common.use_stripes_everywhere);
7377 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7378 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7380 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7381 G_CALLBACK(attach_selected), compose);
7382 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7383 G_CALLBACK(attach_button_pressed), compose);
7384 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7385 G_CALLBACK(popup_attach_button_pressed), compose);
7386 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7387 G_CALLBACK(attach_key_pressed), compose);
7390 gtk_drag_dest_set(attach_clist,
7391 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7392 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7393 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7394 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7395 G_CALLBACK(compose_attach_drag_received_cb),
7397 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7398 G_CALLBACK(compose_drag_drop),
7401 compose->attach_scrwin = attach_scrwin;
7402 compose->attach_clist = attach_clist;
7404 return attach_scrwin;
7407 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7408 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7410 static GtkWidget *compose_create_others(Compose *compose)
7413 GtkWidget *savemsg_checkbtn;
7414 GtkWidget *savemsg_combo;
7415 GtkWidget *savemsg_select;
7418 gchar *folderidentifier;
7420 /* Table for settings */
7421 table = gtk_table_new(3, 1, FALSE);
7422 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7423 gtk_widget_show(table);
7424 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7427 /* Save Message to folder */
7428 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7429 gtk_widget_show(savemsg_checkbtn);
7430 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7431 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7432 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7434 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7435 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7437 #if !GTK_CHECK_VERSION(2, 24, 0)
7438 savemsg_combo = gtk_combo_box_entry_new_text();
7440 savemsg_combo = gtk_combo_box_text_new_with_entry();
7442 compose->savemsg_checkbtn = savemsg_checkbtn;
7443 compose->savemsg_combo = savemsg_combo;
7444 gtk_widget_show(savemsg_combo);
7446 if (prefs_common.compose_save_to_history)
7447 #if !GTK_CHECK_VERSION(2, 24, 0)
7448 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7449 prefs_common.compose_save_to_history);
7451 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7452 prefs_common.compose_save_to_history);
7454 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7455 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7456 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7457 G_CALLBACK(compose_grab_focus_cb), compose);
7458 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7459 folderidentifier = folder_item_get_identifier(account_get_special_folder
7460 (compose->account, F_OUTBOX));
7461 compose_set_save_to(compose, folderidentifier);
7462 g_free(folderidentifier);
7465 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7466 gtk_widget_show(savemsg_select);
7467 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7468 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7469 G_CALLBACK(compose_savemsg_select_cb),
7475 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7477 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7478 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7481 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7486 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7487 _("Select folder to save message to"));
7490 path = folder_item_get_identifier(dest);
7492 compose_set_save_to(compose, path);
7496 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7497 GdkAtom clip, GtkTextIter *insert_place);
7500 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7504 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7506 if (event->button == 3) {
7508 GtkTextIter sel_start, sel_end;
7509 gboolean stuff_selected;
7511 /* move the cursor to allow GtkAspell to check the word
7512 * under the mouse */
7513 if (event->x && event->y) {
7514 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7515 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7517 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7520 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7521 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7524 stuff_selected = gtk_text_buffer_get_selection_bounds(
7526 &sel_start, &sel_end);
7528 gtk_text_buffer_place_cursor (buffer, &iter);
7529 /* reselect stuff */
7531 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7532 gtk_text_buffer_select_range(buffer,
7533 &sel_start, &sel_end);
7535 return FALSE; /* pass the event so that the right-click goes through */
7538 if (event->button == 2) {
7543 /* get the middle-click position to paste at the correct place */
7544 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7545 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7547 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7550 entry_paste_clipboard(compose, text,
7551 prefs_common.linewrap_pastes,
7552 GDK_SELECTION_PRIMARY, &iter);
7560 static void compose_spell_menu_changed(void *data)
7562 Compose *compose = (Compose *)data;
7564 GtkWidget *menuitem;
7565 GtkWidget *parent_item;
7566 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7569 if (compose->gtkaspell == NULL)
7572 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7573 "/Menu/Spelling/Options");
7575 /* setting the submenu removes /Spelling/Options from the factory
7576 * so we need to save it */
7578 if (parent_item == NULL) {
7579 parent_item = compose->aspell_options_menu;
7580 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7582 compose->aspell_options_menu = parent_item;
7584 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7586 spell_menu = g_slist_reverse(spell_menu);
7587 for (items = spell_menu;
7588 items; items = items->next) {
7589 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7590 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7591 gtk_widget_show(GTK_WIDGET(menuitem));
7593 g_slist_free(spell_menu);
7595 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7596 gtk_widget_show(parent_item);
7599 static void compose_dict_changed(void *data)
7601 Compose *compose = (Compose *) data;
7603 if(!compose->gtkaspell)
7605 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7608 gtkaspell_highlight_all(compose->gtkaspell);
7609 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7613 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7615 Compose *compose = (Compose *)data;
7616 GdkEventButton event;
7619 event.time = gtk_get_current_event_time();
7623 return text_clicked(compose->text, &event, compose);
7626 static gboolean compose_force_window_origin = TRUE;
7627 static Compose *compose_create(PrefsAccount *account,
7636 GtkWidget *handlebox;
7638 GtkWidget *notebook;
7640 GtkWidget *attach_hbox;
7641 GtkWidget *attach_lab1;
7642 GtkWidget *attach_lab2;
7647 GtkWidget *subject_hbox;
7648 GtkWidget *subject_frame;
7649 GtkWidget *subject_entry;
7653 GtkWidget *edit_vbox;
7654 GtkWidget *ruler_hbox;
7656 GtkWidget *scrolledwin;
7658 GtkTextBuffer *buffer;
7659 GtkClipboard *clipboard;
7661 UndoMain *undostruct;
7663 GtkWidget *popupmenu;
7664 GtkWidget *tmpl_menu;
7665 GtkActionGroup *action_group = NULL;
7668 GtkAspell * gtkaspell = NULL;
7671 static GdkGeometry geometry;
7673 cm_return_val_if_fail(account != NULL, NULL);
7675 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7676 &default_header_bgcolor);
7677 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7678 &default_header_color);
7680 debug_print("Creating compose window...\n");
7681 compose = g_new0(Compose, 1);
7683 compose->batch = batch;
7684 compose->account = account;
7685 compose->folder = folder;
7687 compose->mutex = cm_mutex_new();
7688 compose->set_cursor_pos = -1;
7690 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7692 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7693 gtk_widget_set_size_request(window, prefs_common.compose_width,
7694 prefs_common.compose_height);
7696 if (!geometry.max_width) {
7697 geometry.max_width = gdk_screen_width();
7698 geometry.max_height = gdk_screen_height();
7701 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7702 &geometry, GDK_HINT_MAX_SIZE);
7703 if (!geometry.min_width) {
7704 geometry.min_width = 600;
7705 geometry.min_height = 440;
7707 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7708 &geometry, GDK_HINT_MIN_SIZE);
7710 #ifndef GENERIC_UMPC
7711 if (compose_force_window_origin)
7712 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7713 prefs_common.compose_y);
7715 g_signal_connect(G_OBJECT(window), "delete_event",
7716 G_CALLBACK(compose_delete_cb), compose);
7717 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7718 gtk_widget_realize(window);
7720 gtkut_widget_set_composer_icon(window);
7722 vbox = gtk_vbox_new(FALSE, 0);
7723 gtk_container_add(GTK_CONTAINER(window), vbox);
7725 compose->ui_manager = gtk_ui_manager_new();
7726 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7727 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7728 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7729 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7730 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7731 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7732 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7733 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7734 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7735 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7751 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7752 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7759 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7760 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7764 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7765 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7766 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7768 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7780 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7781 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7782 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7826 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7854 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)
7855 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)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7861 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)
7862 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)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7867 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)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7871 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)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7877 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)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7884 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)
7885 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)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7898 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)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7916 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7917 gtk_widget_show_all(menubar);
7919 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7920 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7922 if (prefs_common.toolbar_detachable) {
7923 handlebox = gtk_handle_box_new();
7925 handlebox = gtk_hbox_new(FALSE, 0);
7927 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7929 gtk_widget_realize(handlebox);
7930 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7933 vbox2 = gtk_vbox_new(FALSE, 2);
7934 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7935 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7938 notebook = gtk_notebook_new();
7939 gtk_widget_show(notebook);
7941 /* header labels and entries */
7942 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7943 compose_create_header(compose),
7944 gtk_label_new_with_mnemonic(_("Hea_der")));
7945 /* attachment list */
7946 attach_hbox = gtk_hbox_new(FALSE, 0);
7947 gtk_widget_show(attach_hbox);
7949 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7950 gtk_widget_show(attach_lab1);
7951 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7953 attach_lab2 = gtk_label_new("");
7954 gtk_widget_show(attach_lab2);
7955 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7957 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7958 compose_create_attach(compose),
7961 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7962 compose_create_others(compose),
7963 gtk_label_new_with_mnemonic(_("Othe_rs")));
7966 subject_hbox = gtk_hbox_new(FALSE, 0);
7967 gtk_widget_show(subject_hbox);
7969 subject_frame = gtk_frame_new(NULL);
7970 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7971 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7972 gtk_widget_show(subject_frame);
7974 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7975 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7976 gtk_widget_show(subject);
7978 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7979 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7980 gtk_widget_show(label);
7983 subject_entry = claws_spell_entry_new();
7985 subject_entry = gtk_entry_new();
7987 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7988 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7989 G_CALLBACK(compose_grab_focus_cb), compose);
7990 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7991 gtk_widget_show(subject_entry);
7992 compose->subject_entry = subject_entry;
7993 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7995 edit_vbox = gtk_vbox_new(FALSE, 0);
7997 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8000 ruler_hbox = gtk_hbox_new(FALSE, 0);
8001 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8003 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8004 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8005 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8009 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8010 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8011 GTK_POLICY_AUTOMATIC,
8012 GTK_POLICY_AUTOMATIC);
8013 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8015 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8017 text = gtk_text_view_new();
8018 if (prefs_common.show_compose_margin) {
8019 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8020 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8022 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8023 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8024 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8025 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8026 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8028 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8029 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8030 G_CALLBACK(compose_edit_size_alloc),
8032 g_signal_connect(G_OBJECT(buffer), "changed",
8033 G_CALLBACK(compose_changed_cb), compose);
8034 g_signal_connect(G_OBJECT(text), "grab_focus",
8035 G_CALLBACK(compose_grab_focus_cb), compose);
8036 g_signal_connect(G_OBJECT(buffer), "insert_text",
8037 G_CALLBACK(text_inserted), compose);
8038 g_signal_connect(G_OBJECT(text), "button_press_event",
8039 G_CALLBACK(text_clicked), compose);
8040 g_signal_connect(G_OBJECT(text), "popup-menu",
8041 G_CALLBACK(compose_popup_menu), compose);
8042 g_signal_connect(G_OBJECT(subject_entry), "changed",
8043 G_CALLBACK(compose_changed_cb), compose);
8044 g_signal_connect(G_OBJECT(subject_entry), "activate",
8045 G_CALLBACK(compose_subject_entry_activated), compose);
8048 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8049 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8050 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8051 g_signal_connect(G_OBJECT(text), "drag_data_received",
8052 G_CALLBACK(compose_insert_drag_received_cb),
8054 g_signal_connect(G_OBJECT(text), "drag-drop",
8055 G_CALLBACK(compose_drag_drop),
8057 g_signal_connect(G_OBJECT(text), "key-press-event",
8058 G_CALLBACK(completion_set_focus_to_subject),
8060 gtk_widget_show_all(vbox);
8062 /* pane between attach clist and text */
8063 paned = gtk_vpaned_new();
8064 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8065 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8066 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8067 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8068 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8069 G_CALLBACK(compose_notebook_size_alloc), paned);
8071 gtk_widget_show_all(paned);
8074 if (prefs_common.textfont) {
8075 PangoFontDescription *font_desc;
8077 font_desc = pango_font_description_from_string
8078 (prefs_common.textfont);
8080 gtk_widget_modify_font(text, font_desc);
8081 pango_font_description_free(font_desc);
8085 gtk_action_group_add_actions(action_group, compose_popup_entries,
8086 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8087 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8088 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8089 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8090 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8091 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8092 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8094 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8096 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8097 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8098 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8100 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8102 undostruct = undo_init(text);
8103 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8106 address_completion_start(window);
8108 compose->window = window;
8109 compose->vbox = vbox;
8110 compose->menubar = menubar;
8111 compose->handlebox = handlebox;
8113 compose->vbox2 = vbox2;
8115 compose->paned = paned;
8117 compose->attach_label = attach_lab2;
8119 compose->notebook = notebook;
8120 compose->edit_vbox = edit_vbox;
8121 compose->ruler_hbox = ruler_hbox;
8122 compose->ruler = ruler;
8123 compose->scrolledwin = scrolledwin;
8124 compose->text = text;
8126 compose->focused_editable = NULL;
8128 compose->popupmenu = popupmenu;
8130 compose->tmpl_menu = tmpl_menu;
8132 compose->mode = mode;
8133 compose->rmode = mode;
8135 compose->targetinfo = NULL;
8136 compose->replyinfo = NULL;
8137 compose->fwdinfo = NULL;
8139 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8140 g_str_equal, (GDestroyNotify) g_free, NULL);
8142 compose->replyto = NULL;
8144 compose->bcc = NULL;
8145 compose->followup_to = NULL;
8147 compose->ml_post = NULL;
8149 compose->inreplyto = NULL;
8150 compose->references = NULL;
8151 compose->msgid = NULL;
8152 compose->boundary = NULL;
8154 compose->autowrap = prefs_common.autowrap;
8155 compose->autoindent = prefs_common.auto_indent;
8156 compose->use_signing = FALSE;
8157 compose->use_encryption = FALSE;
8158 compose->privacy_system = NULL;
8159 compose->encdata = NULL;
8161 compose->modified = FALSE;
8163 compose->return_receipt = FALSE;
8165 compose->to_list = NULL;
8166 compose->newsgroup_list = NULL;
8168 compose->undostruct = undostruct;
8170 compose->sig_str = NULL;
8172 compose->exteditor_file = NULL;
8173 compose->exteditor_pid = -1;
8174 compose->exteditor_tag = -1;
8175 compose->exteditor_socket = NULL;
8176 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8178 compose->folder_update_callback_id =
8179 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8180 compose_update_folder_hook,
8181 (gpointer) compose);
8184 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8185 if (mode != COMPOSE_REDIRECT) {
8186 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8187 strcmp(prefs_common.dictionary, "")) {
8188 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8189 prefs_common.alt_dictionary,
8190 conv_get_locale_charset_str(),
8191 prefs_common.misspelled_col,
8192 prefs_common.check_while_typing,
8193 prefs_common.recheck_when_changing_dict,
8194 prefs_common.use_alternate,
8195 prefs_common.use_both_dicts,
8196 GTK_TEXT_VIEW(text),
8197 GTK_WINDOW(compose->window),
8198 compose_dict_changed,
8199 compose_spell_menu_changed,
8202 alertpanel_error(_("Spell checker could not "
8204 gtkaspell_checkers_strerror());
8205 gtkaspell_checkers_reset_error();
8207 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8211 compose->gtkaspell = gtkaspell;
8212 compose_spell_menu_changed(compose);
8213 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8216 compose_select_account(compose, account, TRUE);
8218 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8219 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8221 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8222 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8224 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8225 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8227 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8228 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8230 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8231 if (account->protocol != A_NNTP)
8232 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8233 prefs_common_translated_header_name("To:"));
8235 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8236 prefs_common_translated_header_name("Newsgroups:"));
8238 #ifndef USE_ALT_ADDRBOOK
8239 addressbook_set_target_compose(compose);
8241 if (mode != COMPOSE_REDIRECT)
8242 compose_set_template_menu(compose);
8244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8247 compose_list = g_list_append(compose_list, compose);
8249 if (!prefs_common.show_ruler)
8250 gtk_widget_hide(ruler_hbox);
8252 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8255 compose->priority = PRIORITY_NORMAL;
8256 compose_update_priority_menu_item(compose);
8258 compose_set_out_encoding(compose);
8261 compose_update_actions_menu(compose);
8263 /* Privacy Systems menu */
8264 compose_update_privacy_systems_menu(compose);
8266 activate_privacy_system(compose, account, TRUE);
8267 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8269 gtk_widget_realize(window);
8271 gtk_widget_show(window);
8277 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8282 GtkWidget *optmenubox;
8283 GtkWidget *fromlabel;
8286 GtkWidget *from_name = NULL;
8288 gint num = 0, def_menu = 0;
8290 accounts = account_get_list();
8291 cm_return_val_if_fail(accounts != NULL, NULL);
8293 optmenubox = gtk_event_box_new();
8294 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8295 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8297 hbox = gtk_hbox_new(FALSE, 4);
8298 from_name = gtk_entry_new();
8300 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8301 G_CALLBACK(compose_grab_focus_cb), compose);
8302 g_signal_connect_after(G_OBJECT(from_name), "activate",
8303 G_CALLBACK(from_name_activate_cb), optmenu);
8305 for (; accounts != NULL; accounts = accounts->next, num++) {
8306 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8307 gchar *name, *from = NULL;
8309 if (ac == compose->account) def_menu = num;
8311 name = g_markup_printf_escaped("<i>%s</i>",
8314 if (ac == compose->account) {
8315 if (ac->name && *ac->name) {
8317 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8318 from = g_strdup_printf("%s <%s>",
8320 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8322 from = g_strdup_printf("%s",
8324 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8326 if (cur_account != compose->account) {
8327 gtk_widget_modify_base(
8328 GTK_WIDGET(from_name),
8329 GTK_STATE_NORMAL, &default_header_bgcolor);
8330 gtk_widget_modify_text(
8331 GTK_WIDGET(from_name),
8332 GTK_STATE_NORMAL, &default_header_color);
8335 COMBOBOX_ADD(menu, name, ac->account_id);
8340 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8342 g_signal_connect(G_OBJECT(optmenu), "changed",
8343 G_CALLBACK(account_activated),
8345 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8346 G_CALLBACK(compose_entry_popup_extend),
8349 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8350 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8352 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8353 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8354 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8356 /* Putting only the GtkEntry into focus chain of parent hbox causes
8357 * the account selector combobox next to it to be unreachable when
8358 * navigating widgets in GtkTable with up/down arrow keys.
8359 * Note: gtk_widget_set_can_focus() was not enough. */
8361 l = g_list_prepend(l, from_name);
8362 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8365 CLAWS_SET_TIP(optmenubox,
8366 _("Account to use for this email"));
8367 CLAWS_SET_TIP(from_name,
8368 _("Sender address to be used"));
8370 compose->account_combo = optmenu;
8371 compose->from_name = from_name;
8376 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8378 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8379 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8380 Compose *compose = (Compose *) data;
8382 compose->priority = value;
8386 static void compose_reply_change_mode(Compose *compose,
8389 gboolean was_modified = compose->modified;
8391 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8393 cm_return_if_fail(compose->replyinfo != NULL);
8395 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8397 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8399 if (action == COMPOSE_REPLY_TO_ALL)
8401 if (action == COMPOSE_REPLY_TO_SENDER)
8403 if (action == COMPOSE_REPLY_TO_LIST)
8406 compose_remove_header_entries(compose);
8407 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8408 if (compose->account->set_autocc && compose->account->auto_cc)
8409 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8411 if (compose->account->set_autobcc && compose->account->auto_bcc)
8412 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8414 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8415 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8416 compose_show_first_last_header(compose, TRUE);
8417 compose->modified = was_modified;
8418 compose_set_title(compose);
8421 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8423 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8424 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8425 Compose *compose = (Compose *) data;
8428 compose_reply_change_mode(compose, value);
8431 static void compose_update_priority_menu_item(Compose * compose)
8433 GtkWidget *menuitem = NULL;
8434 switch (compose->priority) {
8435 case PRIORITY_HIGHEST:
8436 menuitem = gtk_ui_manager_get_widget
8437 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8440 menuitem = gtk_ui_manager_get_widget
8441 (compose->ui_manager, "/Menu/Options/Priority/High");
8443 case PRIORITY_NORMAL:
8444 menuitem = gtk_ui_manager_get_widget
8445 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8448 menuitem = gtk_ui_manager_get_widget
8449 (compose->ui_manager, "/Menu/Options/Priority/Low");
8451 case PRIORITY_LOWEST:
8452 menuitem = gtk_ui_manager_get_widget
8453 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8456 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8459 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8461 Compose *compose = (Compose *) data;
8463 gboolean can_sign = FALSE, can_encrypt = FALSE;
8465 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8467 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8470 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8471 g_free(compose->privacy_system);
8472 compose->privacy_system = NULL;
8473 g_free(compose->encdata);
8474 compose->encdata = NULL;
8475 if (systemid != NULL) {
8476 compose->privacy_system = g_strdup(systemid);
8478 can_sign = privacy_system_can_sign(systemid);
8479 can_encrypt = privacy_system_can_encrypt(systemid);
8482 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8484 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8485 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8486 if (compose->toolbar->privacy_sign_btn != NULL) {
8487 gtk_widget_set_sensitive(
8488 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8490 gtk_toggle_tool_button_set_active(
8491 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8492 can_sign ? compose->use_signing : FALSE);
8494 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8495 gtk_widget_set_sensitive(
8496 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8498 gtk_toggle_tool_button_set_active(
8499 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8500 can_encrypt ? compose->use_encryption : FALSE);
8504 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8506 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8507 GtkWidget *menuitem = NULL;
8508 GList *children, *amenu;
8509 gboolean can_sign = FALSE, can_encrypt = FALSE;
8510 gboolean found = FALSE;
8512 if (compose->privacy_system != NULL) {
8514 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8515 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8516 cm_return_if_fail(menuitem != NULL);
8518 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8521 while (amenu != NULL) {
8522 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8523 if (systemid != NULL) {
8524 if (strcmp(systemid, compose->privacy_system) == 0 &&
8525 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8526 menuitem = GTK_WIDGET(amenu->data);
8528 can_sign = privacy_system_can_sign(systemid);
8529 can_encrypt = privacy_system_can_encrypt(systemid);
8533 } else if (strlen(compose->privacy_system) == 0 &&
8534 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8535 menuitem = GTK_WIDGET(amenu->data);
8538 can_encrypt = FALSE;
8543 amenu = amenu->next;
8545 g_list_free(children);
8546 if (menuitem != NULL)
8547 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8549 if (warn && !found && strlen(compose->privacy_system)) {
8550 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8551 "will not be able to sign or encrypt this message."),
8552 compose->privacy_system);
8556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8557 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8558 if (compose->toolbar->privacy_sign_btn != NULL) {
8559 gtk_widget_set_sensitive(
8560 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8563 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8564 gtk_widget_set_sensitive(
8565 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8570 static void compose_set_out_encoding(Compose *compose)
8572 CharSet out_encoding;
8573 const gchar *branch = NULL;
8574 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8576 switch(out_encoding) {
8577 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8578 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8579 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8580 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8581 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8582 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8583 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8584 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8585 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8586 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8587 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8588 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8589 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8590 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8591 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8592 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8593 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8594 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8595 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8596 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8597 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8598 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8599 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8600 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8601 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8602 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8603 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8604 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8605 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8606 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8607 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8608 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8609 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8610 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8612 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8615 static void compose_set_template_menu(Compose *compose)
8617 GSList *tmpl_list, *cur;
8621 tmpl_list = template_get_config();
8623 menu = gtk_menu_new();
8625 gtk_menu_set_accel_group (GTK_MENU (menu),
8626 gtk_ui_manager_get_accel_group(compose->ui_manager));
8627 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8628 Template *tmpl = (Template *)cur->data;
8629 gchar *accel_path = NULL;
8630 item = gtk_menu_item_new_with_label(tmpl->name);
8631 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8632 g_signal_connect(G_OBJECT(item), "activate",
8633 G_CALLBACK(compose_template_activate_cb),
8635 g_object_set_data(G_OBJECT(item), "template", tmpl);
8636 gtk_widget_show(item);
8637 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8638 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8642 gtk_widget_show(menu);
8643 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8646 void compose_update_actions_menu(Compose *compose)
8648 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8651 static void compose_update_privacy_systems_menu(Compose *compose)
8653 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8654 GSList *systems, *cur;
8656 GtkWidget *system_none;
8658 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8659 GtkWidget *privacy_menu = gtk_menu_new();
8661 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8662 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8664 g_signal_connect(G_OBJECT(system_none), "activate",
8665 G_CALLBACK(compose_set_privacy_system_cb), compose);
8667 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8668 gtk_widget_show(system_none);
8670 systems = privacy_get_system_ids();
8671 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8672 gchar *systemid = cur->data;
8674 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8675 widget = gtk_radio_menu_item_new_with_label(group,
8676 privacy_system_get_name(systemid));
8677 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8678 g_strdup(systemid), g_free);
8679 g_signal_connect(G_OBJECT(widget), "activate",
8680 G_CALLBACK(compose_set_privacy_system_cb), compose);
8682 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8683 gtk_widget_show(widget);
8686 g_slist_free(systems);
8687 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8688 gtk_widget_show_all(privacy_menu);
8689 gtk_widget_show_all(privacy_menuitem);
8692 void compose_reflect_prefs_all(void)
8697 for (cur = compose_list; cur != NULL; cur = cur->next) {
8698 compose = (Compose *)cur->data;
8699 compose_set_template_menu(compose);
8703 void compose_reflect_prefs_pixmap_theme(void)
8708 for (cur = compose_list; cur != NULL; cur = cur->next) {
8709 compose = (Compose *)cur->data;
8710 toolbar_update(TOOLBAR_COMPOSE, compose);
8714 static const gchar *compose_quote_char_from_context(Compose *compose)
8716 const gchar *qmark = NULL;
8718 cm_return_val_if_fail(compose != NULL, NULL);
8720 switch (compose->mode) {
8721 /* use forward-specific quote char */
8722 case COMPOSE_FORWARD:
8723 case COMPOSE_FORWARD_AS_ATTACH:
8724 case COMPOSE_FORWARD_INLINE:
8725 if (compose->folder && compose->folder->prefs &&
8726 compose->folder->prefs->forward_with_format)
8727 qmark = compose->folder->prefs->forward_quotemark;
8728 else if (compose->account->forward_with_format)
8729 qmark = compose->account->forward_quotemark;
8731 qmark = prefs_common.fw_quotemark;
8734 /* use reply-specific quote char in all other modes */
8736 if (compose->folder && compose->folder->prefs &&
8737 compose->folder->prefs->reply_with_format)
8738 qmark = compose->folder->prefs->reply_quotemark;
8739 else if (compose->account->reply_with_format)
8740 qmark = compose->account->reply_quotemark;
8742 qmark = prefs_common.quotemark;
8746 if (qmark == NULL || *qmark == '\0')
8752 static void compose_template_apply(Compose *compose, Template *tmpl,
8756 GtkTextBuffer *buffer;
8760 gchar *parsed_str = NULL;
8761 gint cursor_pos = 0;
8762 const gchar *err_msg = _("The body of the template has an error at line %d.");
8765 /* process the body */
8767 text = GTK_TEXT_VIEW(compose->text);
8768 buffer = gtk_text_view_get_buffer(text);
8771 qmark = compose_quote_char_from_context(compose);
8773 if (compose->replyinfo != NULL) {
8776 gtk_text_buffer_set_text(buffer, "", -1);
8777 mark = gtk_text_buffer_get_insert(buffer);
8778 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8780 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8781 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8783 } else if (compose->fwdinfo != NULL) {
8786 gtk_text_buffer_set_text(buffer, "", -1);
8787 mark = gtk_text_buffer_get_insert(buffer);
8788 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8790 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8791 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8794 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8796 GtkTextIter start, end;
8799 gtk_text_buffer_get_start_iter(buffer, &start);
8800 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8801 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8803 /* clear the buffer now */
8805 gtk_text_buffer_set_text(buffer, "", -1);
8807 parsed_str = compose_quote_fmt(compose, dummyinfo,
8808 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8809 procmsg_msginfo_free( &dummyinfo );
8815 gtk_text_buffer_set_text(buffer, "", -1);
8816 mark = gtk_text_buffer_get_insert(buffer);
8817 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8820 if (replace && parsed_str && compose->account->auto_sig)
8821 compose_insert_sig(compose, FALSE);
8823 if (replace && parsed_str) {
8824 gtk_text_buffer_get_start_iter(buffer, &iter);
8825 gtk_text_buffer_place_cursor(buffer, &iter);
8829 cursor_pos = quote_fmt_get_cursor_pos();
8830 compose->set_cursor_pos = cursor_pos;
8831 if (cursor_pos == -1)
8833 gtk_text_buffer_get_start_iter(buffer, &iter);
8834 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8835 gtk_text_buffer_place_cursor(buffer, &iter);
8838 /* process the other fields */
8840 compose_template_apply_fields(compose, tmpl);
8841 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8842 quote_fmt_reset_vartable();
8843 quote_fmtlex_destroy();
8845 compose_changed_cb(NULL, compose);
8848 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8849 gtkaspell_highlight_all(compose->gtkaspell);
8853 static void compose_template_apply_fields_error(const gchar *header)
8858 tr = g_strdup(C_("'%s' stands for a header name",
8859 "Template '%s' format error."));
8860 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8861 alertpanel_error("%s", text);
8867 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8869 MsgInfo* dummyinfo = NULL;
8870 MsgInfo *msginfo = NULL;
8873 if (compose->replyinfo != NULL)
8874 msginfo = compose->replyinfo;
8875 else if (compose->fwdinfo != NULL)
8876 msginfo = compose->fwdinfo;
8878 dummyinfo = compose_msginfo_new_from_compose(compose);
8879 msginfo = dummyinfo;
8882 if (tmpl->from && *tmpl->from != '\0') {
8884 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8885 compose->gtkaspell);
8887 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8889 quote_fmt_scan_string(tmpl->from);
8892 buf = quote_fmt_get_buffer();
8894 compose_template_apply_fields_error("From");
8896 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8899 quote_fmt_reset_vartable();
8900 quote_fmtlex_destroy();
8903 if (tmpl->to && *tmpl->to != '\0') {
8905 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8906 compose->gtkaspell);
8908 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8910 quote_fmt_scan_string(tmpl->to);
8913 buf = quote_fmt_get_buffer();
8915 compose_template_apply_fields_error("To");
8917 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8920 quote_fmt_reset_vartable();
8921 quote_fmtlex_destroy();
8924 if (tmpl->cc && *tmpl->cc != '\0') {
8926 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8927 compose->gtkaspell);
8929 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8931 quote_fmt_scan_string(tmpl->cc);
8934 buf = quote_fmt_get_buffer();
8936 compose_template_apply_fields_error("Cc");
8938 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8941 quote_fmt_reset_vartable();
8942 quote_fmtlex_destroy();
8945 if (tmpl->bcc && *tmpl->bcc != '\0') {
8947 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8948 compose->gtkaspell);
8950 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8952 quote_fmt_scan_string(tmpl->bcc);
8955 buf = quote_fmt_get_buffer();
8957 compose_template_apply_fields_error("Bcc");
8959 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8962 quote_fmt_reset_vartable();
8963 quote_fmtlex_destroy();
8966 if (tmpl->replyto && *tmpl->replyto != '\0') {
8968 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8969 compose->gtkaspell);
8971 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8973 quote_fmt_scan_string(tmpl->replyto);
8976 buf = quote_fmt_get_buffer();
8978 compose_template_apply_fields_error("Reply-To");
8980 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8983 quote_fmt_reset_vartable();
8984 quote_fmtlex_destroy();
8987 /* process the subject */
8988 if (tmpl->subject && *tmpl->subject != '\0') {
8990 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8991 compose->gtkaspell);
8993 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8995 quote_fmt_scan_string(tmpl->subject);
8998 buf = quote_fmt_get_buffer();
9000 compose_template_apply_fields_error("Subject");
9002 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9005 quote_fmt_reset_vartable();
9006 quote_fmtlex_destroy();
9009 procmsg_msginfo_free( &dummyinfo );
9012 static void compose_destroy(Compose *compose)
9014 GtkAllocation allocation;
9015 GtkTextBuffer *buffer;
9016 GtkClipboard *clipboard;
9018 compose_list = g_list_remove(compose_list, compose);
9020 if (compose->updating) {
9021 debug_print("danger, not destroying anything now\n");
9022 compose->deferred_destroy = TRUE;
9026 /* NOTE: address_completion_end() does nothing with the window
9027 * however this may change. */
9028 address_completion_end(compose->window);
9030 slist_free_strings_full(compose->to_list);
9031 slist_free_strings_full(compose->newsgroup_list);
9032 slist_free_strings_full(compose->header_list);
9034 slist_free_strings_full(extra_headers);
9035 extra_headers = NULL;
9037 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9039 g_hash_table_destroy(compose->email_hashtable);
9041 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9042 compose->folder_update_callback_id);
9044 procmsg_msginfo_free(&(compose->targetinfo));
9045 procmsg_msginfo_free(&(compose->replyinfo));
9046 procmsg_msginfo_free(&(compose->fwdinfo));
9048 g_free(compose->replyto);
9049 g_free(compose->cc);
9050 g_free(compose->bcc);
9051 g_free(compose->newsgroups);
9052 g_free(compose->followup_to);
9054 g_free(compose->ml_post);
9056 g_free(compose->inreplyto);
9057 g_free(compose->references);
9058 g_free(compose->msgid);
9059 g_free(compose->boundary);
9061 g_free(compose->redirect_filename);
9062 if (compose->undostruct)
9063 undo_destroy(compose->undostruct);
9065 g_free(compose->sig_str);
9067 g_free(compose->exteditor_file);
9069 g_free(compose->orig_charset);
9071 g_free(compose->privacy_system);
9072 g_free(compose->encdata);
9074 #ifndef USE_ALT_ADDRBOOK
9075 if (addressbook_get_target_compose() == compose)
9076 addressbook_set_target_compose(NULL);
9079 if (compose->gtkaspell) {
9080 gtkaspell_delete(compose->gtkaspell);
9081 compose->gtkaspell = NULL;
9085 if (!compose->batch) {
9086 gtk_widget_get_allocation(compose->window, &allocation);
9087 prefs_common.compose_width = allocation.width;
9088 prefs_common.compose_height = allocation.height;
9091 if (!gtk_widget_get_parent(compose->paned))
9092 gtk_widget_destroy(compose->paned);
9093 gtk_widget_destroy(compose->popupmenu);
9095 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9096 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9097 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9099 gtk_widget_destroy(compose->window);
9100 toolbar_destroy(compose->toolbar);
9101 g_free(compose->toolbar);
9102 cm_mutex_free(compose->mutex);
9106 static void compose_attach_info_free(AttachInfo *ainfo)
9108 g_free(ainfo->file);
9109 g_free(ainfo->content_type);
9110 g_free(ainfo->name);
9111 g_free(ainfo->charset);
9115 static void compose_attach_update_label(Compose *compose)
9120 GtkTreeModel *model;
9124 if (compose == NULL)
9127 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9128 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9129 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9133 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9134 total_size = ainfo->size;
9135 while(gtk_tree_model_iter_next(model, &iter)) {
9136 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9137 total_size += ainfo->size;
9140 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9141 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9145 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9147 Compose *compose = (Compose *)data;
9148 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9149 GtkTreeSelection *selection;
9151 GtkTreeModel *model;
9153 selection = gtk_tree_view_get_selection(tree_view);
9154 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9159 for (cur = sel; cur != NULL; cur = cur->next) {
9160 GtkTreePath *path = cur->data;
9161 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9164 gtk_tree_path_free(path);
9167 for (cur = sel; cur != NULL; cur = cur->next) {
9168 GtkTreeRowReference *ref = cur->data;
9169 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9172 if (gtk_tree_model_get_iter(model, &iter, path))
9173 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9175 gtk_tree_path_free(path);
9176 gtk_tree_row_reference_free(ref);
9180 compose_attach_update_label(compose);
9183 static struct _AttachProperty
9186 GtkWidget *mimetype_entry;
9187 GtkWidget *encoding_optmenu;
9188 GtkWidget *path_entry;
9189 GtkWidget *filename_entry;
9191 GtkWidget *cancel_btn;
9194 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9196 gtk_tree_path_free((GtkTreePath *)ptr);
9199 static void compose_attach_property(GtkAction *action, gpointer data)
9201 Compose *compose = (Compose *)data;
9202 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9204 GtkComboBox *optmenu;
9205 GtkTreeSelection *selection;
9207 GtkTreeModel *model;
9210 static gboolean cancelled;
9212 /* only if one selected */
9213 selection = gtk_tree_view_get_selection(tree_view);
9214 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9217 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9221 path = (GtkTreePath *) sel->data;
9222 gtk_tree_model_get_iter(model, &iter, path);
9223 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9226 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9232 if (!attach_prop.window)
9233 compose_attach_property_create(&cancelled);
9234 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9235 gtk_widget_grab_focus(attach_prop.ok_btn);
9236 gtk_widget_show(attach_prop.window);
9237 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9238 GTK_WINDOW(compose->window));
9240 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9241 if (ainfo->encoding == ENC_UNKNOWN)
9242 combobox_select_by_data(optmenu, ENC_BASE64);
9244 combobox_select_by_data(optmenu, ainfo->encoding);
9246 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9247 ainfo->content_type ? ainfo->content_type : "");
9248 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9249 ainfo->file ? ainfo->file : "");
9250 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9251 ainfo->name ? ainfo->name : "");
9254 const gchar *entry_text;
9256 gchar *cnttype = NULL;
9263 gtk_widget_hide(attach_prop.window);
9264 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9269 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9270 if (*entry_text != '\0') {
9273 text = g_strstrip(g_strdup(entry_text));
9274 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9275 cnttype = g_strdup(text);
9278 alertpanel_error(_("Invalid MIME type."));
9284 ainfo->encoding = combobox_get_active_data(optmenu);
9286 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9287 if (*entry_text != '\0') {
9288 if (is_file_exist(entry_text) &&
9289 (size = get_file_size(entry_text)) > 0)
9290 file = g_strdup(entry_text);
9293 (_("File doesn't exist or is empty."));
9299 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9300 if (*entry_text != '\0') {
9301 g_free(ainfo->name);
9302 ainfo->name = g_strdup(entry_text);
9306 g_free(ainfo->content_type);
9307 ainfo->content_type = cnttype;
9310 g_free(ainfo->file);
9314 ainfo->size = (goffset)size;
9316 /* update tree store */
9317 text = to_human_readable(ainfo->size);
9318 gtk_tree_model_get_iter(model, &iter, path);
9319 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9320 COL_MIMETYPE, ainfo->content_type,
9322 COL_NAME, ainfo->name,
9323 COL_CHARSET, ainfo->charset,
9329 gtk_tree_path_free(path);
9332 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9334 label = gtk_label_new(str); \
9335 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9336 GTK_FILL, 0, 0, 0); \
9337 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9339 entry = gtk_entry_new(); \
9340 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9341 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9344 static void compose_attach_property_create(gboolean *cancelled)
9350 GtkWidget *mimetype_entry;
9353 GtkListStore *optmenu_menu;
9354 GtkWidget *path_entry;
9355 GtkWidget *filename_entry;
9358 GtkWidget *cancel_btn;
9359 GList *mime_type_list, *strlist;
9362 debug_print("Creating attach_property window...\n");
9364 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9365 gtk_widget_set_size_request(window, 480, -1);
9366 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9367 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9368 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9369 g_signal_connect(G_OBJECT(window), "delete_event",
9370 G_CALLBACK(attach_property_delete_event),
9372 g_signal_connect(G_OBJECT(window), "key_press_event",
9373 G_CALLBACK(attach_property_key_pressed),
9376 vbox = gtk_vbox_new(FALSE, 8);
9377 gtk_container_add(GTK_CONTAINER(window), vbox);
9379 table = gtk_table_new(4, 2, FALSE);
9380 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9381 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9382 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9384 label = gtk_label_new(_("MIME type"));
9385 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9387 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9388 #if !GTK_CHECK_VERSION(2, 24, 0)
9389 mimetype_entry = gtk_combo_box_entry_new_text();
9391 mimetype_entry = gtk_combo_box_text_new_with_entry();
9393 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9394 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9396 /* stuff with list */
9397 mime_type_list = procmime_get_mime_type_list();
9399 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9400 MimeType *type = (MimeType *) mime_type_list->data;
9403 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9405 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9408 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9409 (GCompareFunc)strcmp2);
9412 for (mime_type_list = strlist; mime_type_list != NULL;
9413 mime_type_list = mime_type_list->next) {
9414 #if !GTK_CHECK_VERSION(2, 24, 0)
9415 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9417 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9419 g_free(mime_type_list->data);
9421 g_list_free(strlist);
9422 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9423 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9425 label = gtk_label_new(_("Encoding"));
9426 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9428 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9430 hbox = gtk_hbox_new(FALSE, 0);
9431 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9432 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9434 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9435 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9437 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9438 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9439 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9440 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9441 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9443 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9445 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9446 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9448 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9449 &ok_btn, GTK_STOCK_OK,
9451 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9452 gtk_widget_grab_default(ok_btn);
9454 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9455 G_CALLBACK(attach_property_ok),
9457 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9458 G_CALLBACK(attach_property_cancel),
9461 gtk_widget_show_all(vbox);
9463 attach_prop.window = window;
9464 attach_prop.mimetype_entry = mimetype_entry;
9465 attach_prop.encoding_optmenu = optmenu;
9466 attach_prop.path_entry = path_entry;
9467 attach_prop.filename_entry = filename_entry;
9468 attach_prop.ok_btn = ok_btn;
9469 attach_prop.cancel_btn = cancel_btn;
9472 #undef SET_LABEL_AND_ENTRY
9474 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9480 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9486 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9487 gboolean *cancelled)
9495 static gboolean attach_property_key_pressed(GtkWidget *widget,
9497 gboolean *cancelled)
9499 if (event && event->keyval == GDK_KEY_Escape) {
9503 if (event && event->keyval == GDK_KEY_Return) {
9511 static void compose_exec_ext_editor(Compose *compose)
9516 GdkNativeWindow socket_wid = 0;
9520 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9521 G_DIR_SEPARATOR, compose);
9523 if (compose_get_ext_editor_uses_socket()) {
9524 /* Only allow one socket */
9525 if (compose->exteditor_socket != NULL) {
9526 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9527 /* Move the focus off of the socket */
9528 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9533 /* Create the receiving GtkSocket */
9534 socket = gtk_socket_new ();
9535 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9536 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9538 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9539 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9540 /* Realize the socket so that we can use its ID */
9541 gtk_widget_realize(socket);
9542 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9543 compose->exteditor_socket = socket;
9546 if (pipe(pipe_fds) < 0) {
9552 if ((pid = fork()) < 0) {
9559 /* close the write side of the pipe */
9562 compose->exteditor_file = g_strdup(tmp);
9563 compose->exteditor_pid = pid;
9565 compose_set_ext_editor_sensitive(compose, FALSE);
9568 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9570 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9572 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9576 } else { /* process-monitoring process */
9582 /* close the read side of the pipe */
9585 if (compose_write_body_to_file(compose, tmp) < 0) {
9586 fd_write_all(pipe_fds[1], "2\n", 2);
9590 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9592 fd_write_all(pipe_fds[1], "1\n", 2);
9596 /* wait until editor is terminated */
9597 waitpid(pid_ed, NULL, 0);
9599 fd_write_all(pipe_fds[1], "0\n", 2);
9606 #endif /* G_OS_UNIX */
9609 static gboolean compose_can_autosave(Compose *compose)
9611 if (compose->privacy_system && compose->use_encryption)
9612 return prefs_common.autosave && prefs_common.autosave_encrypted;
9614 return prefs_common.autosave;
9618 static gboolean compose_get_ext_editor_cmd_valid()
9620 gboolean has_s = FALSE;
9621 gboolean has_w = FALSE;
9622 const gchar *p = prefs_common_get_ext_editor_cmd();
9625 while ((p = strchr(p, '%'))) {
9631 } else if (*p == 'w') {
9642 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9649 cm_return_val_if_fail(file != NULL, -1);
9651 if ((pid = fork()) < 0) {
9656 if (pid != 0) return pid;
9658 /* grandchild process */
9660 if (setpgid(0, getppid()))
9663 if (compose_get_ext_editor_cmd_valid()) {
9664 if (compose_get_ext_editor_uses_socket()) {
9665 p = g_strdup(prefs_common_get_ext_editor_cmd());
9666 s = strstr(p, "%w");
9668 if (strstr(p, "%s") < s)
9669 buf = g_strdup_printf(p, file, socket_wid);
9671 buf = g_strdup_printf(p, socket_wid, file);
9674 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9677 if (prefs_common_get_ext_editor_cmd())
9678 g_warning("External editor command-line is invalid: '%s'",
9679 prefs_common_get_ext_editor_cmd());
9680 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9683 cmdline = strsplit_with_quote(buf, " ", 0);
9685 execvp(cmdline[0], cmdline);
9688 g_strfreev(cmdline);
9693 static gboolean compose_ext_editor_kill(Compose *compose)
9695 pid_t pgid = compose->exteditor_pid * -1;
9698 ret = kill(pgid, 0);
9700 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9704 msg = g_strdup_printf
9705 (_("The external editor is still working.\n"
9706 "Force terminating the process?\n"
9707 "process group id: %d"), -pgid);
9708 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9709 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9713 if (val == G_ALERTALTERNATE) {
9714 g_source_remove(compose->exteditor_tag);
9715 g_io_channel_shutdown(compose->exteditor_ch,
9717 g_io_channel_unref(compose->exteditor_ch);
9719 if (kill(pgid, SIGTERM) < 0) perror("kill");
9720 waitpid(compose->exteditor_pid, NULL, 0);
9722 g_warning("Terminated process group id: %d. "
9723 "Temporary file: %s", -pgid, compose->exteditor_file);
9725 compose_set_ext_editor_sensitive(compose, TRUE);
9727 g_free(compose->exteditor_file);
9728 compose->exteditor_file = NULL;
9729 compose->exteditor_pid = -1;
9730 compose->exteditor_ch = NULL;
9731 compose->exteditor_tag = -1;
9739 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9743 Compose *compose = (Compose *)data;
9746 debug_print("Compose: input from monitoring process\n");
9748 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9753 g_io_channel_shutdown(source, FALSE, NULL);
9754 g_io_channel_unref(source);
9756 waitpid(compose->exteditor_pid, NULL, 0);
9758 if (buf[0] == '0') { /* success */
9759 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9760 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9761 GtkTextIter start, end;
9764 gtk_text_buffer_set_text(buffer, "", -1);
9765 compose_insert_file(compose, compose->exteditor_file);
9766 compose_changed_cb(NULL, compose);
9768 /* Check if we should save the draft or not */
9769 if (compose_can_autosave(compose))
9770 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9772 if (claws_unlink(compose->exteditor_file) < 0)
9773 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9775 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9776 gtk_text_buffer_get_start_iter(buffer, &start);
9777 gtk_text_buffer_get_end_iter(buffer, &end);
9778 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9779 if (chars && strlen(chars) > 0)
9780 compose->modified = TRUE;
9782 } else if (buf[0] == '1') { /* failed */
9783 g_warning("Couldn't exec external editor");
9784 if (claws_unlink(compose->exteditor_file) < 0)
9785 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9786 } else if (buf[0] == '2') {
9787 g_warning("Couldn't write to file");
9788 } else if (buf[0] == '3') {
9789 g_warning("Pipe read failed");
9792 compose_set_ext_editor_sensitive(compose, TRUE);
9794 g_free(compose->exteditor_file);
9795 compose->exteditor_file = NULL;
9796 compose->exteditor_pid = -1;
9797 compose->exteditor_ch = NULL;
9798 compose->exteditor_tag = -1;
9799 if (compose->exteditor_socket) {
9800 gtk_widget_destroy(compose->exteditor_socket);
9801 compose->exteditor_socket = NULL;
9808 static char *ext_editor_menu_entries[] = {
9809 "Menu/Message/Send",
9810 "Menu/Message/SendLater",
9811 "Menu/Message/InsertFile",
9812 "Menu/Message/InsertSig",
9813 "Menu/Message/ReplaceSig",
9814 "Menu/Message/Save",
9815 "Menu/Message/Print",
9820 "Menu/Tools/ShowRuler",
9821 "Menu/Tools/Actions",
9826 static void compose_set_ext_editor_sensitive(Compose *compose,
9831 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9832 cm_menu_set_sensitive_full(compose->ui_manager,
9833 ext_editor_menu_entries[i], sensitive);
9836 if (compose_get_ext_editor_uses_socket()) {
9838 if (compose->exteditor_socket)
9839 gtk_widget_hide(compose->exteditor_socket);
9840 gtk_widget_show(compose->scrolledwin);
9841 if (prefs_common.show_ruler)
9842 gtk_widget_show(compose->ruler_hbox);
9843 /* Fix the focus, as it doesn't go anywhere when the
9844 * socket is hidden or destroyed */
9845 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9847 g_assert (compose->exteditor_socket != NULL);
9848 /* Fix the focus, as it doesn't go anywhere when the
9849 * edit box is hidden */
9850 if (gtk_widget_is_focus(compose->text))
9851 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9852 gtk_widget_hide(compose->scrolledwin);
9853 gtk_widget_hide(compose->ruler_hbox);
9854 gtk_widget_show(compose->exteditor_socket);
9857 gtk_widget_set_sensitive(compose->text, sensitive);
9859 if (compose->toolbar->send_btn)
9860 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9861 if (compose->toolbar->sendl_btn)
9862 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9863 if (compose->toolbar->draft_btn)
9864 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9865 if (compose->toolbar->insert_btn)
9866 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9867 if (compose->toolbar->sig_btn)
9868 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9869 if (compose->toolbar->exteditor_btn)
9870 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9871 if (compose->toolbar->linewrap_current_btn)
9872 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9873 if (compose->toolbar->linewrap_all_btn)
9874 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9877 static gboolean compose_get_ext_editor_uses_socket()
9879 return (prefs_common_get_ext_editor_cmd() &&
9880 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9883 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9885 compose->exteditor_socket = NULL;
9886 /* returning FALSE allows destruction of the socket */
9889 #endif /* G_OS_UNIX */
9892 * compose_undo_state_changed:
9894 * Change the sensivity of the menuentries undo and redo
9896 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9897 gint redo_state, gpointer data)
9899 Compose *compose = (Compose *)data;
9901 switch (undo_state) {
9902 case UNDO_STATE_TRUE:
9903 if (!undostruct->undo_state) {
9904 undostruct->undo_state = TRUE;
9905 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9908 case UNDO_STATE_FALSE:
9909 if (undostruct->undo_state) {
9910 undostruct->undo_state = FALSE;
9911 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9914 case UNDO_STATE_UNCHANGED:
9916 case UNDO_STATE_REFRESH:
9917 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9920 g_warning("Undo state not recognized");
9924 switch (redo_state) {
9925 case UNDO_STATE_TRUE:
9926 if (!undostruct->redo_state) {
9927 undostruct->redo_state = TRUE;
9928 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9931 case UNDO_STATE_FALSE:
9932 if (undostruct->redo_state) {
9933 undostruct->redo_state = FALSE;
9934 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9937 case UNDO_STATE_UNCHANGED:
9939 case UNDO_STATE_REFRESH:
9940 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9943 g_warning("Redo state not recognized");
9948 /* callback functions */
9950 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9951 GtkAllocation *allocation,
9954 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9957 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9958 * includes "non-client" (windows-izm) in calculation, so this calculation
9959 * may not be accurate.
9961 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9962 GtkAllocation *allocation,
9963 GtkSHRuler *shruler)
9965 if (prefs_common.show_ruler) {
9966 gint char_width = 0, char_height = 0;
9967 gint line_width_in_chars;
9969 gtkut_get_font_size(GTK_WIDGET(widget),
9970 &char_width, &char_height);
9971 line_width_in_chars =
9972 (allocation->width - allocation->x) / char_width;
9974 /* got the maximum */
9975 gtk_shruler_set_range(GTK_SHRULER(shruler),
9976 0.0, line_width_in_chars, 0);
9985 ComposePrefType type;
9986 gboolean entry_marked;
9989 static void account_activated(GtkComboBox *optmenu, gpointer data)
9991 Compose *compose = (Compose *)data;
9994 gchar *folderidentifier;
9995 gint account_id = 0;
9998 GSList *list, *saved_list = NULL;
9999 HeaderEntryState *state;
10001 /* Get ID of active account in the combo box */
10002 menu = gtk_combo_box_get_model(optmenu);
10003 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10004 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10006 ac = account_find_from_id(account_id);
10007 cm_return_if_fail(ac != NULL);
10009 if (ac != compose->account) {
10010 compose_select_account(compose, ac, FALSE);
10012 for (list = compose->header_list; list; list = list->next) {
10013 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10015 if (hentry->type == PREF_ACCOUNT || !list->next) {
10016 compose_destroy_headerentry(compose, hentry);
10019 state = g_malloc0(sizeof(HeaderEntryState));
10020 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10021 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10022 state->entry = gtk_editable_get_chars(
10023 GTK_EDITABLE(hentry->entry), 0, -1);
10024 state->type = hentry->type;
10026 saved_list = g_slist_append(saved_list, state);
10027 compose_destroy_headerentry(compose, hentry);
10030 compose->header_last = NULL;
10031 g_slist_free(compose->header_list);
10032 compose->header_list = NULL;
10033 compose->header_nextrow = 1;
10034 compose_create_header_entry(compose);
10036 if (ac->set_autocc && ac->auto_cc)
10037 compose_entry_append(compose, ac->auto_cc,
10038 COMPOSE_CC, PREF_ACCOUNT);
10039 if (ac->set_autobcc && ac->auto_bcc)
10040 compose_entry_append(compose, ac->auto_bcc,
10041 COMPOSE_BCC, PREF_ACCOUNT);
10042 if (ac->set_autoreplyto && ac->auto_replyto)
10043 compose_entry_append(compose, ac->auto_replyto,
10044 COMPOSE_REPLYTO, PREF_ACCOUNT);
10046 for (list = saved_list; list; list = list->next) {
10047 state = (HeaderEntryState *) list->data;
10049 compose_add_header_entry(compose, state->header,
10050 state->entry, state->type);
10052 g_free(state->header);
10053 g_free(state->entry);
10056 g_slist_free(saved_list);
10058 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10059 (ac->protocol == A_NNTP) ?
10060 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10063 /* Set message save folder */
10064 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10065 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10067 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10068 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10070 compose_set_save_to(compose, NULL);
10071 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10072 folderidentifier = folder_item_get_identifier(account_get_special_folder
10073 (compose->account, F_OUTBOX));
10074 compose_set_save_to(compose, folderidentifier);
10075 g_free(folderidentifier);
10079 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10080 GtkTreeViewColumn *column, Compose *compose)
10082 compose_attach_property(NULL, compose);
10085 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10088 Compose *compose = (Compose *)data;
10089 GtkTreeSelection *attach_selection;
10090 gint attach_nr_selected;
10093 if (!event) return FALSE;
10095 if (event->button == 3) {
10096 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10097 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10099 /* If no rows, or just one row is selected, right-click should
10100 * open menu relevant to the row being right-clicked on. We
10101 * achieve that by selecting the clicked row first. If more
10102 * than one row is selected, we shouldn't modify the selection,
10103 * as user may want to remove selected rows (attachments). */
10104 if (attach_nr_selected < 2) {
10105 gtk_tree_selection_unselect_all(attach_selection);
10106 attach_nr_selected = 0;
10107 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10108 event->x, event->y, &path, NULL, NULL, NULL);
10109 if (path != NULL) {
10110 gtk_tree_selection_select_path(attach_selection, path);
10111 gtk_tree_path_free(path);
10112 attach_nr_selected++;
10116 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10117 /* Properties menu item makes no sense with more than one row
10118 * selected, the properties dialog can only edit one attachment. */
10119 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10121 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10122 NULL, NULL, event->button, event->time);
10129 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10132 Compose *compose = (Compose *)data;
10134 if (!event) return FALSE;
10136 switch (event->keyval) {
10137 case GDK_KEY_Delete:
10138 compose_attach_remove_selected(NULL, compose);
10144 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10146 toolbar_comp_set_sensitive(compose, allow);
10147 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10148 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10150 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10152 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10153 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10154 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10156 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10160 static void compose_send_cb(GtkAction *action, gpointer data)
10162 Compose *compose = (Compose *)data;
10165 if (compose->exteditor_tag != -1) {
10166 debug_print("ignoring send: external editor still open\n");
10170 if (prefs_common.work_offline &&
10171 !inc_offline_should_override(TRUE,
10172 _("Claws Mail needs network access in order "
10173 "to send this email.")))
10176 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10177 g_source_remove(compose->draft_timeout_tag);
10178 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10181 compose_send(compose);
10184 static void compose_send_later_cb(GtkAction *action, gpointer data)
10186 Compose *compose = (Compose *)data;
10190 compose_allow_user_actions(compose, FALSE);
10191 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10192 compose_allow_user_actions(compose, TRUE);
10196 compose_close(compose);
10197 } else if (val == -1) {
10198 alertpanel_error(_("Could not queue message."));
10199 } else if (val == -2) {
10200 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10201 } else if (val == -3) {
10202 if (privacy_peek_error())
10203 alertpanel_error(_("Could not queue message for sending:\n\n"
10204 "Signature failed: %s"), privacy_get_error());
10205 } else if (val == -4) {
10206 alertpanel_error(_("Could not queue message for sending:\n\n"
10207 "Charset conversion failed."));
10208 } else if (val == -5) {
10209 alertpanel_error(_("Could not queue message for sending:\n\n"
10210 "Couldn't get recipient encryption key."));
10211 } else if (val == -6) {
10214 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10217 #define DRAFTED_AT_EXIT "drafted_at_exit"
10218 static void compose_register_draft(MsgInfo *info)
10220 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10221 DRAFTED_AT_EXIT, NULL);
10222 FILE *fp = g_fopen(filepath, "ab");
10225 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10233 gboolean compose_draft (gpointer data, guint action)
10235 Compose *compose = (Compose *)data;
10240 MsgFlags flag = {0, 0};
10241 static gboolean lock = FALSE;
10242 MsgInfo *newmsginfo;
10244 gboolean target_locked = FALSE;
10245 gboolean err = FALSE;
10247 if (lock) return FALSE;
10249 if (compose->sending)
10252 draft = account_get_special_folder(compose->account, F_DRAFT);
10253 cm_return_val_if_fail(draft != NULL, FALSE);
10255 if (!g_mutex_trylock(compose->mutex)) {
10256 /* we don't want to lock the mutex once it's available,
10257 * because as the only other part of compose.c locking
10258 * it is compose_close - which means once unlocked,
10259 * the compose struct will be freed */
10260 debug_print("couldn't lock mutex, probably sending\n");
10266 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10267 G_DIR_SEPARATOR, compose);
10268 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10269 FILE_OP_ERROR(tmp, "fopen");
10273 /* chmod for security */
10274 if (change_file_mode_rw(fp, tmp) < 0) {
10275 FILE_OP_ERROR(tmp, "chmod");
10276 g_warning("can't change file mode");
10279 /* Save draft infos */
10280 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10281 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10283 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10284 gchar *savefolderid;
10286 savefolderid = compose_get_save_to(compose);
10287 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10288 g_free(savefolderid);
10290 if (compose->return_receipt) {
10291 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10293 if (compose->privacy_system) {
10294 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10295 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10296 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10299 /* Message-ID of message replying to */
10300 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10301 gchar *folderid = NULL;
10303 if (compose->replyinfo->folder)
10304 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10305 if (folderid == NULL)
10306 folderid = g_strdup("NULL");
10308 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10311 /* Message-ID of message forwarding to */
10312 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10313 gchar *folderid = NULL;
10315 if (compose->fwdinfo->folder)
10316 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10317 if (folderid == NULL)
10318 folderid = g_strdup("NULL");
10320 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10324 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10325 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10327 sheaders = compose_get_manual_headers_info(compose);
10328 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10331 /* end of headers */
10332 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10339 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10343 if (fclose(fp) == EOF) {
10347 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10348 if (compose->targetinfo) {
10349 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10351 flag.perm_flags |= MSG_LOCKED;
10353 flag.tmp_flags = MSG_DRAFT;
10355 folder_item_scan(draft);
10356 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10357 MsgInfo *tmpinfo = NULL;
10358 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10359 if (compose->msgid) {
10360 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10363 msgnum = tmpinfo->msgnum;
10364 procmsg_msginfo_free(&tmpinfo);
10365 debug_print("got draft msgnum %d from scanning\n", msgnum);
10367 debug_print("didn't get draft msgnum after scanning\n");
10370 debug_print("got draft msgnum %d from adding\n", msgnum);
10376 if (action != COMPOSE_AUTO_SAVE) {
10377 if (action != COMPOSE_DRAFT_FOR_EXIT)
10378 alertpanel_error(_("Could not save draft."));
10381 gtkut_window_popup(compose->window);
10382 val = alertpanel_full(_("Could not save draft"),
10383 _("Could not save draft.\n"
10384 "Do you want to cancel exit or discard this email?"),
10385 _("_Cancel exit"), _("_Discard email"), NULL,
10386 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10387 if (val == G_ALERTALTERNATE) {
10389 g_mutex_unlock(compose->mutex); /* must be done before closing */
10390 compose_close(compose);
10394 g_mutex_unlock(compose->mutex); /* must be done before closing */
10403 if (compose->mode == COMPOSE_REEDIT) {
10404 compose_remove_reedit_target(compose, TRUE);
10407 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10410 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10412 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10414 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10415 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10416 procmsg_msginfo_set_flags(newmsginfo, 0,
10417 MSG_HAS_ATTACHMENT);
10419 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10420 compose_register_draft(newmsginfo);
10422 procmsg_msginfo_free(&newmsginfo);
10425 folder_item_scan(draft);
10427 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10429 g_mutex_unlock(compose->mutex); /* must be done before closing */
10430 compose_close(compose);
10436 path = folder_item_fetch_msg(draft, msgnum);
10437 if (path == NULL) {
10438 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10441 if (g_stat(path, &s) < 0) {
10442 FILE_OP_ERROR(path, "stat");
10448 procmsg_msginfo_free(&(compose->targetinfo));
10449 compose->targetinfo = procmsg_msginfo_new();
10450 compose->targetinfo->msgnum = msgnum;
10451 compose->targetinfo->size = (goffset)s.st_size;
10452 compose->targetinfo->mtime = s.st_mtime;
10453 compose->targetinfo->folder = draft;
10455 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10456 compose->mode = COMPOSE_REEDIT;
10458 if (action == COMPOSE_AUTO_SAVE) {
10459 compose->autosaved_draft = compose->targetinfo;
10461 compose->modified = FALSE;
10462 compose_set_title(compose);
10466 g_mutex_unlock(compose->mutex);
10470 void compose_clear_exit_drafts(void)
10472 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10473 DRAFTED_AT_EXIT, NULL);
10474 if (is_file_exist(filepath))
10475 claws_unlink(filepath);
10480 void compose_reopen_exit_drafts(void)
10482 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10483 DRAFTED_AT_EXIT, NULL);
10484 FILE *fp = g_fopen(filepath, "rb");
10488 while (fgets(buf, sizeof(buf), fp)) {
10489 gchar **parts = g_strsplit(buf, "\t", 2);
10490 const gchar *folder = parts[0];
10491 int msgnum = parts[1] ? atoi(parts[1]):-1;
10493 if (folder && *folder && msgnum > -1) {
10494 FolderItem *item = folder_find_item_from_identifier(folder);
10495 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10497 compose_reedit(info, FALSE);
10504 compose_clear_exit_drafts();
10507 static void compose_save_cb(GtkAction *action, gpointer data)
10509 Compose *compose = (Compose *)data;
10510 compose_draft(compose, COMPOSE_KEEP_EDITING);
10511 compose->rmode = COMPOSE_REEDIT;
10514 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10516 if (compose && file_list) {
10519 for ( tmp = file_list; tmp; tmp = tmp->next) {
10520 gchar *file = (gchar *) tmp->data;
10521 gchar *utf8_filename = conv_filename_to_utf8(file);
10522 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10523 compose_changed_cb(NULL, compose);
10528 g_free(utf8_filename);
10533 static void compose_attach_cb(GtkAction *action, gpointer data)
10535 Compose *compose = (Compose *)data;
10538 if (compose->redirect_filename != NULL)
10541 /* Set focus_window properly, in case we were called via popup menu,
10542 * which unsets it (via focus_out_event callback on compose window). */
10543 manage_window_focus_in(compose->window, NULL, NULL);
10545 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10548 compose_attach_from_list(compose, file_list, TRUE);
10549 g_list_free(file_list);
10553 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10555 Compose *compose = (Compose *)data;
10557 gint files_inserted = 0;
10559 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10564 for ( tmp = file_list; tmp; tmp = tmp->next) {
10565 gchar *file = (gchar *) tmp->data;
10566 gchar *filedup = g_strdup(file);
10567 gchar *shortfile = g_path_get_basename(filedup);
10568 ComposeInsertResult res;
10569 /* insert the file if the file is short or if the user confirmed that
10570 he/she wants to insert the large file */
10571 res = compose_insert_file(compose, file);
10572 if (res == COMPOSE_INSERT_READ_ERROR) {
10573 alertpanel_error(_("File '%s' could not be read."), shortfile);
10574 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10575 alertpanel_error(_("File '%s' contained invalid characters\n"
10576 "for the current encoding, insertion may be incorrect."),
10578 } else if (res == COMPOSE_INSERT_SUCCESS)
10585 g_list_free(file_list);
10589 if (files_inserted > 0 && compose->gtkaspell &&
10590 compose->gtkaspell->check_while_typing)
10591 gtkaspell_highlight_all(compose->gtkaspell);
10595 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10597 Compose *compose = (Compose *)data;
10599 compose_insert_sig(compose, FALSE);
10602 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10604 Compose *compose = (Compose *)data;
10606 compose_insert_sig(compose, TRUE);
10609 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10613 Compose *compose = (Compose *)data;
10615 gtkut_widget_get_uposition(widget, &x, &y);
10616 if (!compose->batch) {
10617 prefs_common.compose_x = x;
10618 prefs_common.compose_y = y;
10620 if (compose->sending || compose->updating)
10622 compose_close_cb(NULL, compose);
10626 void compose_close_toolbar(Compose *compose)
10628 compose_close_cb(NULL, compose);
10631 static void compose_close_cb(GtkAction *action, gpointer data)
10633 Compose *compose = (Compose *)data;
10637 if (compose->exteditor_tag != -1) {
10638 if (!compose_ext_editor_kill(compose))
10643 if (compose->modified) {
10644 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10645 if (!g_mutex_trylock(compose->mutex)) {
10646 /* we don't want to lock the mutex once it's available,
10647 * because as the only other part of compose.c locking
10648 * it is compose_close - which means once unlocked,
10649 * the compose struct will be freed */
10650 debug_print("couldn't lock mutex, probably sending\n");
10654 val = alertpanel(_("Discard message"),
10655 _("This message has been modified. Discard it?"),
10656 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10658 val = alertpanel(_("Save changes"),
10659 _("This message has been modified. Save the latest changes?"),
10660 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10663 g_mutex_unlock(compose->mutex);
10665 case G_ALERTDEFAULT:
10666 if (compose_can_autosave(compose) && !reedit)
10667 compose_remove_draft(compose);
10669 case G_ALERTALTERNATE:
10670 compose_draft(data, COMPOSE_QUIT_EDITING);
10677 compose_close(compose);
10680 static void compose_print_cb(GtkAction *action, gpointer data)
10682 Compose *compose = (Compose *) data;
10684 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10685 if (compose->targetinfo)
10686 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10689 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10691 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10692 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10693 Compose *compose = (Compose *) data;
10696 compose->out_encoding = (CharSet)value;
10699 static void compose_address_cb(GtkAction *action, gpointer data)
10701 Compose *compose = (Compose *)data;
10703 #ifndef USE_ALT_ADDRBOOK
10704 addressbook_open(compose);
10706 GError* error = NULL;
10707 addressbook_connect_signals(compose);
10708 addressbook_dbus_open(TRUE, &error);
10710 g_warning("%s", error->message);
10711 g_error_free(error);
10716 static void about_show_cb(GtkAction *action, gpointer data)
10721 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10723 Compose *compose = (Compose *)data;
10728 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10729 cm_return_if_fail(tmpl != NULL);
10731 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10733 val = alertpanel(_("Apply template"), msg,
10734 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10737 if (val == G_ALERTDEFAULT)
10738 compose_template_apply(compose, tmpl, TRUE);
10739 else if (val == G_ALERTALTERNATE)
10740 compose_template_apply(compose, tmpl, FALSE);
10743 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10745 Compose *compose = (Compose *)data;
10748 if (compose->exteditor_tag != -1) {
10749 debug_print("ignoring open external editor: external editor still open\n");
10753 compose_exec_ext_editor(compose);
10756 static void compose_undo_cb(GtkAction *action, gpointer data)
10758 Compose *compose = (Compose *)data;
10759 gboolean prev_autowrap = compose->autowrap;
10761 compose->autowrap = FALSE;
10762 undo_undo(compose->undostruct);
10763 compose->autowrap = prev_autowrap;
10766 static void compose_redo_cb(GtkAction *action, gpointer data)
10768 Compose *compose = (Compose *)data;
10769 gboolean prev_autowrap = compose->autowrap;
10771 compose->autowrap = FALSE;
10772 undo_redo(compose->undostruct);
10773 compose->autowrap = prev_autowrap;
10776 static void entry_cut_clipboard(GtkWidget *entry)
10778 if (GTK_IS_EDITABLE(entry))
10779 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10780 else if (GTK_IS_TEXT_VIEW(entry))
10781 gtk_text_buffer_cut_clipboard(
10782 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10783 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10787 static void entry_copy_clipboard(GtkWidget *entry)
10789 if (GTK_IS_EDITABLE(entry))
10790 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10791 else if (GTK_IS_TEXT_VIEW(entry))
10792 gtk_text_buffer_copy_clipboard(
10793 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10794 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10797 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10798 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10800 if (GTK_IS_TEXT_VIEW(entry)) {
10801 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10802 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10803 GtkTextIter start_iter, end_iter;
10805 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10807 if (contents == NULL)
10810 /* we shouldn't delete the selection when middle-click-pasting, or we
10811 * can't mid-click-paste our own selection */
10812 if (clip != GDK_SELECTION_PRIMARY) {
10813 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10814 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10817 if (insert_place == NULL) {
10818 /* if insert_place isn't specified, insert at the cursor.
10819 * used for Ctrl-V pasting */
10820 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10821 start = gtk_text_iter_get_offset(&start_iter);
10822 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10824 /* if insert_place is specified, paste here.
10825 * used for mid-click-pasting */
10826 start = gtk_text_iter_get_offset(insert_place);
10827 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10828 if (prefs_common.primary_paste_unselects)
10829 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10833 /* paste unwrapped: mark the paste so it's not wrapped later */
10834 end = start + strlen(contents);
10835 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10836 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10837 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10838 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10839 /* rewrap paragraph now (after a mid-click-paste) */
10840 mark_start = gtk_text_buffer_get_insert(buffer);
10841 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10842 gtk_text_iter_backward_char(&start_iter);
10843 compose_beautify_paragraph(compose, &start_iter, TRUE);
10845 } else if (GTK_IS_EDITABLE(entry))
10846 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10848 compose->modified = TRUE;
10851 static void entry_allsel(GtkWidget *entry)
10853 if (GTK_IS_EDITABLE(entry))
10854 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10855 else if (GTK_IS_TEXT_VIEW(entry)) {
10856 GtkTextIter startiter, enditer;
10857 GtkTextBuffer *textbuf;
10859 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10860 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10861 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10863 gtk_text_buffer_move_mark_by_name(textbuf,
10864 "selection_bound", &startiter);
10865 gtk_text_buffer_move_mark_by_name(textbuf,
10866 "insert", &enditer);
10870 static void compose_cut_cb(GtkAction *action, gpointer data)
10872 Compose *compose = (Compose *)data;
10873 if (compose->focused_editable
10874 #ifndef GENERIC_UMPC
10875 && gtk_widget_has_focus(compose->focused_editable)
10878 entry_cut_clipboard(compose->focused_editable);
10881 static void compose_copy_cb(GtkAction *action, gpointer data)
10883 Compose *compose = (Compose *)data;
10884 if (compose->focused_editable
10885 #ifndef GENERIC_UMPC
10886 && gtk_widget_has_focus(compose->focused_editable)
10889 entry_copy_clipboard(compose->focused_editable);
10892 static void compose_paste_cb(GtkAction *action, gpointer data)
10894 Compose *compose = (Compose *)data;
10895 gint prev_autowrap;
10896 GtkTextBuffer *buffer;
10898 if (compose->focused_editable &&
10899 #ifndef GENERIC_UMPC
10900 gtk_widget_has_focus(compose->focused_editable)
10903 entry_paste_clipboard(compose, compose->focused_editable,
10904 prefs_common.linewrap_pastes,
10905 GDK_SELECTION_CLIPBOARD, NULL);
10910 #ifndef GENERIC_UMPC
10911 gtk_widget_has_focus(compose->text) &&
10913 compose->gtkaspell &&
10914 compose->gtkaspell->check_while_typing)
10915 gtkaspell_highlight_all(compose->gtkaspell);
10919 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10921 Compose *compose = (Compose *)data;
10922 gint wrap_quote = prefs_common.linewrap_quote;
10923 if (compose->focused_editable
10924 #ifndef GENERIC_UMPC
10925 && gtk_widget_has_focus(compose->focused_editable)
10928 /* let text_insert() (called directly or at a later time
10929 * after the gtk_editable_paste_clipboard) know that
10930 * text is to be inserted as a quotation. implemented
10931 * by using a simple refcount... */
10932 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10933 G_OBJECT(compose->focused_editable),
10934 "paste_as_quotation"));
10935 g_object_set_data(G_OBJECT(compose->focused_editable),
10936 "paste_as_quotation",
10937 GINT_TO_POINTER(paste_as_quotation + 1));
10938 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10939 entry_paste_clipboard(compose, compose->focused_editable,
10940 prefs_common.linewrap_pastes,
10941 GDK_SELECTION_CLIPBOARD, NULL);
10942 prefs_common.linewrap_quote = wrap_quote;
10946 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10948 Compose *compose = (Compose *)data;
10949 gint prev_autowrap;
10950 GtkTextBuffer *buffer;
10952 if (compose->focused_editable
10953 #ifndef GENERIC_UMPC
10954 && gtk_widget_has_focus(compose->focused_editable)
10957 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10958 GDK_SELECTION_CLIPBOARD, NULL);
10963 #ifndef GENERIC_UMPC
10964 gtk_widget_has_focus(compose->text) &&
10966 compose->gtkaspell &&
10967 compose->gtkaspell->check_while_typing)
10968 gtkaspell_highlight_all(compose->gtkaspell);
10972 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10974 Compose *compose = (Compose *)data;
10975 gint prev_autowrap;
10976 GtkTextBuffer *buffer;
10978 if (compose->focused_editable
10979 #ifndef GENERIC_UMPC
10980 && gtk_widget_has_focus(compose->focused_editable)
10983 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10984 GDK_SELECTION_CLIPBOARD, NULL);
10989 #ifndef GENERIC_UMPC
10990 gtk_widget_has_focus(compose->text) &&
10992 compose->gtkaspell &&
10993 compose->gtkaspell->check_while_typing)
10994 gtkaspell_highlight_all(compose->gtkaspell);
10998 static void compose_allsel_cb(GtkAction *action, gpointer data)
11000 Compose *compose = (Compose *)data;
11001 if (compose->focused_editable
11002 #ifndef GENERIC_UMPC
11003 && gtk_widget_has_focus(compose->focused_editable)
11006 entry_allsel(compose->focused_editable);
11009 static void textview_move_beginning_of_line (GtkTextView *text)
11011 GtkTextBuffer *buffer;
11015 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11017 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11018 mark = gtk_text_buffer_get_insert(buffer);
11019 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11020 gtk_text_iter_set_line_offset(&ins, 0);
11021 gtk_text_buffer_place_cursor(buffer, &ins);
11024 static void textview_move_forward_character (GtkTextView *text)
11026 GtkTextBuffer *buffer;
11030 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11032 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11033 mark = gtk_text_buffer_get_insert(buffer);
11034 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11035 if (gtk_text_iter_forward_cursor_position(&ins))
11036 gtk_text_buffer_place_cursor(buffer, &ins);
11039 static void textview_move_backward_character (GtkTextView *text)
11041 GtkTextBuffer *buffer;
11045 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11047 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11048 mark = gtk_text_buffer_get_insert(buffer);
11049 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11050 if (gtk_text_iter_backward_cursor_position(&ins))
11051 gtk_text_buffer_place_cursor(buffer, &ins);
11054 static void textview_move_forward_word (GtkTextView *text)
11056 GtkTextBuffer *buffer;
11061 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11063 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11064 mark = gtk_text_buffer_get_insert(buffer);
11065 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11066 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11067 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11068 gtk_text_iter_backward_word_start(&ins);
11069 gtk_text_buffer_place_cursor(buffer, &ins);
11073 static void textview_move_backward_word (GtkTextView *text)
11075 GtkTextBuffer *buffer;
11079 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11081 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11082 mark = gtk_text_buffer_get_insert(buffer);
11083 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11084 if (gtk_text_iter_backward_word_starts(&ins, 1))
11085 gtk_text_buffer_place_cursor(buffer, &ins);
11088 static void textview_move_end_of_line (GtkTextView *text)
11090 GtkTextBuffer *buffer;
11094 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11096 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11097 mark = gtk_text_buffer_get_insert(buffer);
11098 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11099 if (gtk_text_iter_forward_to_line_end(&ins))
11100 gtk_text_buffer_place_cursor(buffer, &ins);
11103 static void textview_move_next_line (GtkTextView *text)
11105 GtkTextBuffer *buffer;
11110 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11112 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11113 mark = gtk_text_buffer_get_insert(buffer);
11114 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11115 offset = gtk_text_iter_get_line_offset(&ins);
11116 if (gtk_text_iter_forward_line(&ins)) {
11117 gtk_text_iter_set_line_offset(&ins, offset);
11118 gtk_text_buffer_place_cursor(buffer, &ins);
11122 static void textview_move_previous_line (GtkTextView *text)
11124 GtkTextBuffer *buffer;
11129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11132 mark = gtk_text_buffer_get_insert(buffer);
11133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11134 offset = gtk_text_iter_get_line_offset(&ins);
11135 if (gtk_text_iter_backward_line(&ins)) {
11136 gtk_text_iter_set_line_offset(&ins, offset);
11137 gtk_text_buffer_place_cursor(buffer, &ins);
11141 static void textview_delete_forward_character (GtkTextView *text)
11143 GtkTextBuffer *buffer;
11145 GtkTextIter ins, end_iter;
11147 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11149 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11150 mark = gtk_text_buffer_get_insert(buffer);
11151 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11153 if (gtk_text_iter_forward_char(&end_iter)) {
11154 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11158 static void textview_delete_backward_character (GtkTextView *text)
11160 GtkTextBuffer *buffer;
11162 GtkTextIter ins, end_iter;
11164 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11166 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11167 mark = gtk_text_buffer_get_insert(buffer);
11168 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11170 if (gtk_text_iter_backward_char(&end_iter)) {
11171 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11175 static void textview_delete_forward_word (GtkTextView *text)
11177 GtkTextBuffer *buffer;
11179 GtkTextIter ins, end_iter;
11181 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11183 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11184 mark = gtk_text_buffer_get_insert(buffer);
11185 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11187 if (gtk_text_iter_forward_word_end(&end_iter)) {
11188 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11192 static void textview_delete_backward_word (GtkTextView *text)
11194 GtkTextBuffer *buffer;
11196 GtkTextIter ins, end_iter;
11198 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11200 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11201 mark = gtk_text_buffer_get_insert(buffer);
11202 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11204 if (gtk_text_iter_backward_word_start(&end_iter)) {
11205 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11209 static void textview_delete_line (GtkTextView *text)
11211 GtkTextBuffer *buffer;
11213 GtkTextIter ins, start_iter, end_iter;
11215 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11217 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11218 mark = gtk_text_buffer_get_insert(buffer);
11219 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11222 gtk_text_iter_set_line_offset(&start_iter, 0);
11225 if (gtk_text_iter_ends_line(&end_iter)){
11226 if (!gtk_text_iter_forward_char(&end_iter))
11227 gtk_text_iter_backward_char(&start_iter);
11230 gtk_text_iter_forward_to_line_end(&end_iter);
11231 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11234 static void textview_delete_to_line_end (GtkTextView *text)
11236 GtkTextBuffer *buffer;
11238 GtkTextIter ins, end_iter;
11240 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11242 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11243 mark = gtk_text_buffer_get_insert(buffer);
11244 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11246 if (gtk_text_iter_ends_line(&end_iter))
11247 gtk_text_iter_forward_char(&end_iter);
11249 gtk_text_iter_forward_to_line_end(&end_iter);
11250 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11253 #define DO_ACTION(name, act) { \
11254 if(!strcmp(name, a_name)) { \
11258 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11260 const gchar *a_name = gtk_action_get_name(action);
11261 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11262 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11263 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11264 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11265 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11266 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11267 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11268 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11269 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11270 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11271 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11272 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11273 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11274 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11275 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11278 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11280 Compose *compose = (Compose *)data;
11281 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11282 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11284 action = compose_call_advanced_action_from_path(gaction);
11287 void (*do_action) (GtkTextView *text);
11288 } action_table[] = {
11289 {textview_move_beginning_of_line},
11290 {textview_move_forward_character},
11291 {textview_move_backward_character},
11292 {textview_move_forward_word},
11293 {textview_move_backward_word},
11294 {textview_move_end_of_line},
11295 {textview_move_next_line},
11296 {textview_move_previous_line},
11297 {textview_delete_forward_character},
11298 {textview_delete_backward_character},
11299 {textview_delete_forward_word},
11300 {textview_delete_backward_word},
11301 {textview_delete_line},
11302 {textview_delete_to_line_end}
11305 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11307 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11308 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11309 if (action_table[action].do_action)
11310 action_table[action].do_action(text);
11312 g_warning("Not implemented yet.");
11316 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11318 GtkAllocation allocation;
11322 if (GTK_IS_EDITABLE(widget)) {
11323 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11324 gtk_editable_set_position(GTK_EDITABLE(widget),
11327 if ((parent = gtk_widget_get_parent(widget))
11328 && (parent = gtk_widget_get_parent(parent))
11329 && (parent = gtk_widget_get_parent(parent))) {
11330 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11331 gtk_widget_get_allocation(widget, &allocation);
11332 gint y = allocation.y;
11333 gint height = allocation.height;
11334 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11335 (GTK_SCROLLED_WINDOW(parent));
11337 gfloat value = gtk_adjustment_get_value(shown);
11338 gfloat upper = gtk_adjustment_get_upper(shown);
11339 gfloat page_size = gtk_adjustment_get_page_size(shown);
11340 if (y < (int)value) {
11341 gtk_adjustment_set_value(shown, y - 1);
11343 if ((y + height) > ((int)value + (int)page_size)) {
11344 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11345 gtk_adjustment_set_value(shown,
11346 y + height - (int)page_size - 1);
11348 gtk_adjustment_set_value(shown,
11349 (int)upper - (int)page_size - 1);
11356 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11357 compose->focused_editable = widget;
11359 #ifdef GENERIC_UMPC
11360 if (GTK_IS_TEXT_VIEW(widget)
11361 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11362 g_object_ref(compose->notebook);
11363 g_object_ref(compose->edit_vbox);
11364 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11365 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11366 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11367 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11368 g_object_unref(compose->notebook);
11369 g_object_unref(compose->edit_vbox);
11370 g_signal_handlers_block_by_func(G_OBJECT(widget),
11371 G_CALLBACK(compose_grab_focus_cb),
11373 gtk_widget_grab_focus(widget);
11374 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11375 G_CALLBACK(compose_grab_focus_cb),
11377 } else if (!GTK_IS_TEXT_VIEW(widget)
11378 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11379 g_object_ref(compose->notebook);
11380 g_object_ref(compose->edit_vbox);
11381 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11382 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11383 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11384 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11385 g_object_unref(compose->notebook);
11386 g_object_unref(compose->edit_vbox);
11387 g_signal_handlers_block_by_func(G_OBJECT(widget),
11388 G_CALLBACK(compose_grab_focus_cb),
11390 gtk_widget_grab_focus(widget);
11391 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11392 G_CALLBACK(compose_grab_focus_cb),
11398 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11400 compose->modified = TRUE;
11401 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11402 #ifndef GENERIC_UMPC
11403 compose_set_title(compose);
11407 static void compose_wrap_cb(GtkAction *action, gpointer data)
11409 Compose *compose = (Compose *)data;
11410 compose_beautify_paragraph(compose, NULL, TRUE);
11413 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11415 Compose *compose = (Compose *)data;
11416 compose_wrap_all_full(compose, TRUE);
11419 static void compose_find_cb(GtkAction *action, gpointer data)
11421 Compose *compose = (Compose *)data;
11423 message_search_compose(compose);
11426 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11429 Compose *compose = (Compose *)data;
11430 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11431 if (compose->autowrap)
11432 compose_wrap_all_full(compose, TRUE);
11433 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11436 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11439 Compose *compose = (Compose *)data;
11440 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11443 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11445 Compose *compose = (Compose *)data;
11447 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11448 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11451 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11453 Compose *compose = (Compose *)data;
11455 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11456 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11459 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11461 g_free(compose->privacy_system);
11462 g_free(compose->encdata);
11464 compose->privacy_system = g_strdup(account->default_privacy_system);
11465 compose_update_privacy_system_menu_item(compose, warn);
11468 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11470 Compose *compose = (Compose *)data;
11472 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11473 gtk_widget_show(compose->ruler_hbox);
11474 prefs_common.show_ruler = TRUE;
11476 gtk_widget_hide(compose->ruler_hbox);
11477 gtk_widget_queue_resize(compose->edit_vbox);
11478 prefs_common.show_ruler = FALSE;
11482 static void compose_attach_drag_received_cb (GtkWidget *widget,
11483 GdkDragContext *context,
11486 GtkSelectionData *data,
11489 gpointer user_data)
11491 Compose *compose = (Compose *)user_data;
11495 type = gtk_selection_data_get_data_type(data);
11496 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11497 && gtk_drag_get_source_widget(context) !=
11498 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11499 list = uri_list_extract_filenames(
11500 (const gchar *)gtk_selection_data_get_data(data));
11501 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11502 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11503 compose_attach_append
11504 (compose, (const gchar *)tmp->data,
11505 utf8_filename, NULL, NULL);
11506 g_free(utf8_filename);
11508 if (list) compose_changed_cb(NULL, compose);
11509 list_free_strings(list);
11511 } else if (gtk_drag_get_source_widget(context)
11512 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11513 /* comes from our summaryview */
11514 SummaryView * summaryview = NULL;
11515 GSList * list = NULL, *cur = NULL;
11517 if (mainwindow_get_mainwindow())
11518 summaryview = mainwindow_get_mainwindow()->summaryview;
11521 list = summary_get_selected_msg_list(summaryview);
11523 for (cur = list; cur; cur = cur->next) {
11524 MsgInfo *msginfo = (MsgInfo *)cur->data;
11525 gchar *file = NULL;
11527 file = procmsg_get_message_file_full(msginfo,
11530 compose_attach_append(compose, (const gchar *)file,
11531 (const gchar *)file, "message/rfc822", NULL);
11535 g_slist_free(list);
11539 static gboolean compose_drag_drop(GtkWidget *widget,
11540 GdkDragContext *drag_context,
11542 guint time, gpointer user_data)
11544 /* not handling this signal makes compose_insert_drag_received_cb
11549 static gboolean completion_set_focus_to_subject
11550 (GtkWidget *widget,
11551 GdkEventKey *event,
11554 cm_return_val_if_fail(compose != NULL, FALSE);
11556 /* make backtab move to subject field */
11557 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11558 gtk_widget_grab_focus(compose->subject_entry);
11564 static void compose_insert_drag_received_cb (GtkWidget *widget,
11565 GdkDragContext *drag_context,
11568 GtkSelectionData *data,
11571 gpointer user_data)
11573 Compose *compose = (Compose *)user_data;
11579 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11581 type = gtk_selection_data_get_data_type(data);
11582 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11583 AlertValue val = G_ALERTDEFAULT;
11584 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11586 list = uri_list_extract_filenames(ddata);
11587 num_files = g_list_length(list);
11588 if (list == NULL && strstr(ddata, "://")) {
11589 /* Assume a list of no files, and data has ://, is a remote link */
11590 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11591 gchar *tmpfile = get_tmp_file();
11592 str_write_to_file(tmpdata, tmpfile);
11594 compose_insert_file(compose, tmpfile);
11595 claws_unlink(tmpfile);
11597 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11598 compose_beautify_paragraph(compose, NULL, TRUE);
11601 switch (prefs_common.compose_dnd_mode) {
11602 case COMPOSE_DND_ASK:
11603 msg = g_strdup_printf(
11605 "Do you want to insert the contents of the file "
11606 "into the message body, or attach it to the email?",
11607 "Do you want to insert the contents of the %d files "
11608 "into the message body, or attach them to the email?",
11611 val = alertpanel_full(_("Insert or attach?"), msg,
11612 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11613 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11616 case COMPOSE_DND_INSERT:
11617 val = G_ALERTALTERNATE;
11619 case COMPOSE_DND_ATTACH:
11620 val = G_ALERTOTHER;
11623 /* unexpected case */
11624 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11627 if (val & G_ALERTDISABLE) {
11628 val &= ~G_ALERTDISABLE;
11629 /* remember what action to perform by default, only if we don't click Cancel */
11630 if (val == G_ALERTALTERNATE)
11631 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11632 else if (val == G_ALERTOTHER)
11633 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11636 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11637 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11638 list_free_strings(list);
11641 } else if (val == G_ALERTOTHER) {
11642 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11643 list_free_strings(list);
11648 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11649 compose_insert_file(compose, (const gchar *)tmp->data);
11651 list_free_strings(list);
11653 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11658 static void compose_header_drag_received_cb (GtkWidget *widget,
11659 GdkDragContext *drag_context,
11662 GtkSelectionData *data,
11665 gpointer user_data)
11667 GtkEditable *entry = (GtkEditable *)user_data;
11668 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11670 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11673 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11674 gchar *decoded=g_new(gchar, strlen(email));
11677 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11678 gtk_editable_delete_text(entry, 0, -1);
11679 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11680 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11684 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11687 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11689 Compose *compose = (Compose *)data;
11691 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11692 compose->return_receipt = TRUE;
11694 compose->return_receipt = FALSE;
11697 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11699 Compose *compose = (Compose *)data;
11701 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11702 compose->remove_references = TRUE;
11704 compose->remove_references = FALSE;
11707 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11708 ComposeHeaderEntry *headerentry)
11710 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11711 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11712 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11716 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11717 GdkEventKey *event,
11718 ComposeHeaderEntry *headerentry)
11720 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11721 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11722 !(event->state & GDK_MODIFIER_MASK) &&
11723 (event->keyval == GDK_KEY_BackSpace) &&
11724 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11725 gtk_container_remove
11726 (GTK_CONTAINER(headerentry->compose->header_table),
11727 headerentry->combo);
11728 gtk_container_remove
11729 (GTK_CONTAINER(headerentry->compose->header_table),
11730 headerentry->entry);
11731 headerentry->compose->header_list =
11732 g_slist_remove(headerentry->compose->header_list,
11734 g_free(headerentry);
11735 } else if (event->keyval == GDK_KEY_Tab) {
11736 if (headerentry->compose->header_last == headerentry) {
11737 /* Override default next focus, and give it to subject_entry
11738 * instead of notebook tabs
11740 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11741 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11748 static gboolean scroll_postpone(gpointer data)
11750 Compose *compose = (Compose *)data;
11752 if (compose->batch)
11755 GTK_EVENTS_FLUSH();
11756 compose_show_first_last_header(compose, FALSE);
11760 static void compose_headerentry_changed_cb(GtkWidget *entry,
11761 ComposeHeaderEntry *headerentry)
11763 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11764 compose_create_header_entry(headerentry->compose);
11765 g_signal_handlers_disconnect_matched
11766 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11767 0, 0, NULL, NULL, headerentry);
11769 if (!headerentry->compose->batch)
11770 g_timeout_add(0, scroll_postpone, headerentry->compose);
11774 static gboolean compose_defer_auto_save_draft(Compose *compose)
11776 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11777 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11781 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11783 GtkAdjustment *vadj;
11785 cm_return_if_fail(compose);
11790 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11791 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11792 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11793 gtk_widget_get_parent(compose->header_table)));
11794 gtk_adjustment_set_value(vadj, (show_first ?
11795 gtk_adjustment_get_lower(vadj) :
11796 (gtk_adjustment_get_upper(vadj) -
11797 gtk_adjustment_get_page_size(vadj))));
11798 gtk_adjustment_changed(vadj);
11801 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11802 const gchar *text, gint len, Compose *compose)
11804 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11805 (G_OBJECT(compose->text), "paste_as_quotation"));
11808 cm_return_if_fail(text != NULL);
11810 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11811 G_CALLBACK(text_inserted),
11813 if (paste_as_quotation) {
11815 const gchar *qmark;
11817 GtkTextIter start_iter;
11820 len = strlen(text);
11822 new_text = g_strndup(text, len);
11824 qmark = compose_quote_char_from_context(compose);
11826 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11827 gtk_text_buffer_place_cursor(buffer, iter);
11829 pos = gtk_text_iter_get_offset(iter);
11831 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11832 _("Quote format error at line %d."));
11833 quote_fmt_reset_vartable();
11835 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11836 GINT_TO_POINTER(paste_as_quotation - 1));
11838 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11839 gtk_text_buffer_place_cursor(buffer, iter);
11840 gtk_text_buffer_delete_mark(buffer, mark);
11842 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11843 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11844 compose_beautify_paragraph(compose, &start_iter, FALSE);
11845 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11846 gtk_text_buffer_delete_mark(buffer, mark);
11848 if (strcmp(text, "\n") || compose->automatic_break
11849 || gtk_text_iter_starts_line(iter)) {
11850 GtkTextIter before_ins;
11851 gtk_text_buffer_insert(buffer, iter, text, len);
11852 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11853 before_ins = *iter;
11854 gtk_text_iter_backward_chars(&before_ins, len);
11855 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11858 /* check if the preceding is just whitespace or quote */
11859 GtkTextIter start_line;
11860 gchar *tmp = NULL, *quote = NULL;
11861 gint quote_len = 0, is_normal = 0;
11862 start_line = *iter;
11863 gtk_text_iter_set_line_offset(&start_line, 0);
11864 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11867 if (*tmp == '\0') {
11870 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11878 gtk_text_buffer_insert(buffer, iter, text, len);
11880 gtk_text_buffer_insert_with_tags_by_name(buffer,
11881 iter, text, len, "no_join", NULL);
11886 if (!paste_as_quotation) {
11887 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11888 compose_beautify_paragraph(compose, iter, FALSE);
11889 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11890 gtk_text_buffer_delete_mark(buffer, mark);
11893 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11894 G_CALLBACK(text_inserted),
11896 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11898 if (compose_can_autosave(compose) &&
11899 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11900 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11901 compose->draft_timeout_tag = g_timeout_add
11902 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11906 static void compose_check_all(GtkAction *action, gpointer data)
11908 Compose *compose = (Compose *)data;
11909 if (!compose->gtkaspell)
11912 if (gtk_widget_has_focus(compose->subject_entry))
11913 claws_spell_entry_check_all(
11914 CLAWS_SPELL_ENTRY(compose->subject_entry));
11916 gtkaspell_check_all(compose->gtkaspell);
11919 static void compose_highlight_all(GtkAction *action, gpointer data)
11921 Compose *compose = (Compose *)data;
11922 if (compose->gtkaspell) {
11923 claws_spell_entry_recheck_all(
11924 CLAWS_SPELL_ENTRY(compose->subject_entry));
11925 gtkaspell_highlight_all(compose->gtkaspell);
11929 static void compose_check_backwards(GtkAction *action, gpointer data)
11931 Compose *compose = (Compose *)data;
11932 if (!compose->gtkaspell) {
11933 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11937 if (gtk_widget_has_focus(compose->subject_entry))
11938 claws_spell_entry_check_backwards(
11939 CLAWS_SPELL_ENTRY(compose->subject_entry));
11941 gtkaspell_check_backwards(compose->gtkaspell);
11944 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11946 Compose *compose = (Compose *)data;
11947 if (!compose->gtkaspell) {
11948 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11952 if (gtk_widget_has_focus(compose->subject_entry))
11953 claws_spell_entry_check_forwards_go(
11954 CLAWS_SPELL_ENTRY(compose->subject_entry));
11956 gtkaspell_check_forwards_go(compose->gtkaspell);
11961 *\brief Guess originating forward account from MsgInfo and several
11962 * "common preference" settings. Return NULL if no guess.
11964 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11966 PrefsAccount *account = NULL;
11968 cm_return_val_if_fail(msginfo, NULL);
11969 cm_return_val_if_fail(msginfo->folder, NULL);
11970 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11972 if (msginfo->folder->prefs->enable_default_account)
11973 account = account_find_from_id(msginfo->folder->prefs->default_account);
11975 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11977 Xstrdup_a(to, msginfo->to, return NULL);
11978 extract_address(to);
11979 account = account_find_from_address(to, FALSE);
11982 if (!account && prefs_common.forward_account_autosel) {
11984 if (!procheader_get_header_from_msginfo
11985 (msginfo, &cc, "Cc:")) {
11986 gchar *buf = cc + strlen("Cc:");
11987 extract_address(buf);
11988 account = account_find_from_address(buf, FALSE);
11993 if (!account && prefs_common.forward_account_autosel) {
11994 gchar *deliveredto = NULL;
11995 if (!procheader_get_header_from_msginfo
11996 (msginfo, &deliveredto, "Delivered-To:")) {
11997 gchar *buf = deliveredto + strlen("Delivered-To:");
11998 extract_address(buf);
11999 account = account_find_from_address(buf, FALSE);
12000 g_free(deliveredto);
12005 account = msginfo->folder->folder->account;
12010 gboolean compose_close(Compose *compose)
12014 cm_return_val_if_fail(compose, FALSE);
12016 if (!g_mutex_trylock(compose->mutex)) {
12017 /* we have to wait for the (possibly deferred by auto-save)
12018 * drafting to be done, before destroying the compose under
12020 debug_print("waiting for drafting to finish...\n");
12021 compose_allow_user_actions(compose, FALSE);
12022 if (compose->close_timeout_tag == 0) {
12023 compose->close_timeout_tag =
12024 g_timeout_add (500, (GSourceFunc) compose_close,
12030 if (compose->draft_timeout_tag >= 0) {
12031 g_source_remove(compose->draft_timeout_tag);
12032 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12035 gtkut_widget_get_uposition(compose->window, &x, &y);
12036 if (!compose->batch) {
12037 prefs_common.compose_x = x;
12038 prefs_common.compose_y = y;
12040 g_mutex_unlock(compose->mutex);
12041 compose_destroy(compose);
12046 * Add entry field for each address in list.
12047 * \param compose E-Mail composition object.
12048 * \param listAddress List of (formatted) E-Mail addresses.
12050 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12053 node = listAddress;
12055 addr = ( gchar * ) node->data;
12056 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12057 node = g_list_next( node );
12061 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12062 guint action, gboolean opening_multiple)
12064 gchar *body = NULL;
12065 GSList *new_msglist = NULL;
12066 MsgInfo *tmp_msginfo = NULL;
12067 gboolean originally_enc = FALSE;
12068 gboolean originally_sig = FALSE;
12069 Compose *compose = NULL;
12070 gchar *s_system = NULL;
12072 cm_return_if_fail(msgview != NULL);
12074 cm_return_if_fail(msginfo_list != NULL);
12076 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12077 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12078 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12080 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12081 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12082 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12083 orig_msginfo, mimeinfo);
12084 if (tmp_msginfo != NULL) {
12085 new_msglist = g_slist_append(NULL, tmp_msginfo);
12087 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12088 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12089 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12091 tmp_msginfo->folder = orig_msginfo->folder;
12092 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12093 if (orig_msginfo->tags) {
12094 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12095 tmp_msginfo->folder->tags_dirty = TRUE;
12101 if (!opening_multiple)
12102 body = messageview_get_selection(msgview);
12105 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12106 procmsg_msginfo_free(&tmp_msginfo);
12107 g_slist_free(new_msglist);
12109 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12111 if (compose && originally_enc) {
12112 compose_force_encryption(compose, compose->account, FALSE, s_system);
12115 if (compose && originally_sig && compose->account->default_sign_reply) {
12116 compose_force_signing(compose, compose->account, s_system);
12120 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12123 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12126 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12127 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12128 GSList *cur = msginfo_list;
12129 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12130 "messages. Opening the windows "
12131 "could take some time. Do you "
12132 "want to continue?"),
12133 g_slist_length(msginfo_list));
12134 if (g_slist_length(msginfo_list) > 9
12135 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12136 != G_ALERTALTERNATE) {
12141 /* We'll open multiple compose windows */
12142 /* let the WM place the next windows */
12143 compose_force_window_origin = FALSE;
12144 for (; cur; cur = cur->next) {
12146 tmplist.data = cur->data;
12147 tmplist.next = NULL;
12148 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12150 compose_force_window_origin = TRUE;
12152 /* forwarding multiple mails as attachments is done via a
12153 * single compose window */
12154 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12158 void compose_check_for_email_account(Compose *compose)
12160 PrefsAccount *ac = NULL, *curr = NULL;
12166 if (compose->account && compose->account->protocol == A_NNTP) {
12167 ac = account_get_cur_account();
12168 if (ac->protocol == A_NNTP) {
12169 list = account_get_list();
12171 for( ; list != NULL ; list = g_list_next(list)) {
12172 curr = (PrefsAccount *) list->data;
12173 if (curr->protocol != A_NNTP) {
12179 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12184 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12185 const gchar *address)
12187 GSList *msginfo_list = NULL;
12188 gchar *body = messageview_get_selection(msgview);
12191 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12193 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12194 compose_check_for_email_account(compose);
12195 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12196 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12197 compose_reply_set_subject(compose, msginfo);
12200 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12203 void compose_set_position(Compose *compose, gint pos)
12205 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12207 gtkut_text_view_set_position(text, pos);
12210 gboolean compose_search_string(Compose *compose,
12211 const gchar *str, gboolean case_sens)
12213 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12215 return gtkut_text_view_search_string(text, str, case_sens);
12218 gboolean compose_search_string_backward(Compose *compose,
12219 const gchar *str, gboolean case_sens)
12221 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12223 return gtkut_text_view_search_string_backward(text, str, case_sens);
12226 /* allocate a msginfo structure and populate its data from a compose data structure */
12227 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12229 MsgInfo *newmsginfo;
12231 gchar date[RFC822_DATE_BUFFSIZE];
12233 cm_return_val_if_fail( compose != NULL, NULL );
12235 newmsginfo = procmsg_msginfo_new();
12238 get_rfc822_date(date, sizeof(date));
12239 newmsginfo->date = g_strdup(date);
12242 if (compose->from_name) {
12243 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12244 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12248 if (compose->subject_entry)
12249 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12251 /* to, cc, reply-to, newsgroups */
12252 for (list = compose->header_list; list; list = list->next) {
12253 gchar *header = gtk_editable_get_chars(
12255 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12256 gchar *entry = gtk_editable_get_chars(
12257 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12259 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12260 if ( newmsginfo->to == NULL ) {
12261 newmsginfo->to = g_strdup(entry);
12262 } else if (entry && *entry) {
12263 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12264 g_free(newmsginfo->to);
12265 newmsginfo->to = tmp;
12268 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12269 if ( newmsginfo->cc == NULL ) {
12270 newmsginfo->cc = g_strdup(entry);
12271 } else if (entry && *entry) {
12272 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12273 g_free(newmsginfo->cc);
12274 newmsginfo->cc = tmp;
12277 if ( strcasecmp(header,
12278 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12279 if ( newmsginfo->newsgroups == NULL ) {
12280 newmsginfo->newsgroups = g_strdup(entry);
12281 } else if (entry && *entry) {
12282 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12283 g_free(newmsginfo->newsgroups);
12284 newmsginfo->newsgroups = tmp;
12292 /* other data is unset */
12298 /* update compose's dictionaries from folder dict settings */
12299 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12300 FolderItem *folder_item)
12302 cm_return_if_fail(compose != NULL);
12304 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12305 FolderItemPrefs *prefs = folder_item->prefs;
12307 if (prefs->enable_default_dictionary)
12308 gtkaspell_change_dict(compose->gtkaspell,
12309 prefs->default_dictionary, FALSE);
12310 if (folder_item->prefs->enable_default_alt_dictionary)
12311 gtkaspell_change_alt_dict(compose->gtkaspell,
12312 prefs->default_alt_dictionary);
12313 if (prefs->enable_default_dictionary
12314 || prefs->enable_default_alt_dictionary)
12315 compose_spell_menu_changed(compose);
12320 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12322 Compose *compose = (Compose *)data;
12324 cm_return_if_fail(compose != NULL);
12326 gtk_widget_grab_focus(compose->text);
12329 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12331 gtk_combo_box_popup(GTK_COMBO_BOX(data));