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"
111 #include "password.h"
112 #include "ldapserver.h"
126 #define N_ATTACH_COLS (N_COL_COLUMNS)
130 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
141 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
142 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
145 } ComposeCallAdvancedAction;
149 PRIORITY_HIGHEST = 1,
158 COMPOSE_INSERT_SUCCESS,
159 COMPOSE_INSERT_READ_ERROR,
160 COMPOSE_INSERT_INVALID_CHARACTER,
161 COMPOSE_INSERT_NO_FILE
162 } ComposeInsertResult;
166 COMPOSE_WRITE_FOR_SEND,
167 COMPOSE_WRITE_FOR_STORE
172 COMPOSE_QUOTE_FORCED,
179 SUBJECT_FIELD_PRESENT,
184 #define B64_LINE_SIZE 57
185 #define B64_BUFFSIZE 77
187 #define MAX_REFERENCES_LEN 999
189 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
190 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
192 static GdkColor default_header_bgcolor = {
199 static GdkColor default_header_color = {
206 static GList *compose_list = NULL;
207 static GSList *extra_headers = NULL;
209 static Compose *compose_generic_new (PrefsAccount *account,
213 GList *listAddress );
215 static Compose *compose_create (PrefsAccount *account,
220 static void compose_entry_indicate (Compose *compose,
221 const gchar *address);
222 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
223 ComposeQuoteMode quote_mode,
227 static Compose *compose_forward_multiple (PrefsAccount *account,
228 GSList *msginfo_list);
229 static Compose *compose_reply (MsgInfo *msginfo,
230 ComposeQuoteMode quote_mode,
235 static Compose *compose_reply_mode (ComposeMode mode,
236 GSList *msginfo_list,
238 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
239 static void compose_update_privacy_systems_menu(Compose *compose);
241 static GtkWidget *compose_account_option_menu_create
243 static void compose_set_out_encoding (Compose *compose);
244 static void compose_set_template_menu (Compose *compose);
245 static void compose_destroy (Compose *compose);
247 static MailField compose_entries_set (Compose *compose,
249 ComposeEntryType to_type);
250 static gint compose_parse_header (Compose *compose,
252 static gint compose_parse_manual_headers (Compose *compose,
254 HeaderEntry *entries);
255 static gchar *compose_parse_references (const gchar *ref,
258 static gchar *compose_quote_fmt (Compose *compose,
264 gboolean need_unescape,
265 const gchar *err_msg);
267 static void compose_reply_set_entry (Compose *compose,
273 followup_and_reply_to);
274 static void compose_reedit_set_entry (Compose *compose,
277 static void compose_insert_sig (Compose *compose,
279 static ComposeInsertResult compose_insert_file (Compose *compose,
282 static gboolean compose_attach_append (Compose *compose,
285 const gchar *content_type,
286 const gchar *charset);
287 static void compose_attach_parts (Compose *compose,
290 static gboolean compose_beautify_paragraph (Compose *compose,
291 GtkTextIter *par_iter,
293 static void compose_wrap_all (Compose *compose);
294 static void compose_wrap_all_full (Compose *compose,
297 static void compose_set_title (Compose *compose);
298 static void compose_select_account (Compose *compose,
299 PrefsAccount *account,
302 static PrefsAccount *compose_current_mail_account(void);
303 /* static gint compose_send (Compose *compose); */
304 static gboolean compose_check_for_valid_recipient
306 static gboolean compose_check_entries (Compose *compose,
307 gboolean check_everything);
308 static gint compose_write_to_file (Compose *compose,
311 gboolean attach_parts);
312 static gint compose_write_body_to_file (Compose *compose,
314 static gint compose_remove_reedit_target (Compose *compose,
316 static void compose_remove_draft (Compose *compose);
317 static gint compose_queue_sub (Compose *compose,
321 gboolean perform_checks,
322 gboolean remove_reedit_target);
323 static int compose_add_attachments (Compose *compose,
325 static gchar *compose_get_header (Compose *compose);
326 static gchar *compose_get_manual_headers_info (Compose *compose);
328 static void compose_convert_header (Compose *compose,
333 gboolean addr_field);
335 static void compose_attach_info_free (AttachInfo *ainfo);
336 static void compose_attach_remove_selected (GtkAction *action,
339 static void compose_template_apply (Compose *compose,
342 static void compose_attach_property (GtkAction *action,
344 static void compose_attach_property_create (gboolean *cancelled);
345 static void attach_property_ok (GtkWidget *widget,
346 gboolean *cancelled);
347 static void attach_property_cancel (GtkWidget *widget,
348 gboolean *cancelled);
349 static gint attach_property_delete_event (GtkWidget *widget,
351 gboolean *cancelled);
352 static gboolean attach_property_key_pressed (GtkWidget *widget,
354 gboolean *cancelled);
356 static void compose_exec_ext_editor (Compose *compose);
358 static gint compose_exec_ext_editor_real (const gchar *file,
359 GdkNativeWindow socket_wid);
360 static gboolean compose_ext_editor_kill (Compose *compose);
361 static gboolean compose_input_cb (GIOChannel *source,
362 GIOCondition condition,
364 static void compose_set_ext_editor_sensitive (Compose *compose,
366 static gboolean compose_get_ext_editor_cmd_valid();
367 static gboolean compose_get_ext_editor_uses_socket();
368 static gboolean compose_ext_editor_plug_removed_cb
371 #endif /* G_OS_UNIX */
373 static void compose_undo_state_changed (UndoMain *undostruct,
378 static void compose_create_header_entry (Compose *compose);
379 static void compose_add_header_entry (Compose *compose, const gchar *header,
380 gchar *text, ComposePrefType pref_type);
381 static void compose_remove_header_entries(Compose *compose);
383 static void compose_update_priority_menu_item(Compose * compose);
385 static void compose_spell_menu_changed (void *data);
386 static void compose_dict_changed (void *data);
388 static void compose_add_field_list ( Compose *compose,
389 GList *listAddress );
391 /* callback functions */
393 static void compose_notebook_size_alloc (GtkNotebook *notebook,
394 GtkAllocation *allocation,
396 static gboolean compose_edit_size_alloc (GtkEditable *widget,
397 GtkAllocation *allocation,
398 GtkSHRuler *shruler);
399 static void account_activated (GtkComboBox *optmenu,
401 static void attach_selected (GtkTreeView *tree_view,
402 GtkTreePath *tree_path,
403 GtkTreeViewColumn *column,
405 static gboolean attach_button_pressed (GtkWidget *widget,
406 GdkEventButton *event,
408 static gboolean attach_key_pressed (GtkWidget *widget,
411 static void compose_send_cb (GtkAction *action, gpointer data);
412 static void compose_send_later_cb (GtkAction *action, gpointer data);
414 static void compose_save_cb (GtkAction *action,
417 static void compose_attach_cb (GtkAction *action,
419 static void compose_insert_file_cb (GtkAction *action,
421 static void compose_insert_sig_cb (GtkAction *action,
423 static void compose_replace_sig_cb (GtkAction *action,
426 static void compose_close_cb (GtkAction *action,
428 static void compose_print_cb (GtkAction *action,
431 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
433 static void compose_address_cb (GtkAction *action,
435 static void about_show_cb (GtkAction *action,
437 static void compose_template_activate_cb(GtkWidget *widget,
440 static void compose_ext_editor_cb (GtkAction *action,
443 static gint compose_delete_cb (GtkWidget *widget,
447 static void compose_undo_cb (GtkAction *action,
449 static void compose_redo_cb (GtkAction *action,
451 static void compose_cut_cb (GtkAction *action,
453 static void compose_copy_cb (GtkAction *action,
455 static void compose_paste_cb (GtkAction *action,
457 static void compose_paste_as_quote_cb (GtkAction *action,
459 static void compose_paste_no_wrap_cb (GtkAction *action,
461 static void compose_paste_wrap_cb (GtkAction *action,
463 static void compose_allsel_cb (GtkAction *action,
466 static void compose_advanced_action_cb (GtkAction *action,
469 static void compose_grab_focus_cb (GtkWidget *widget,
472 static void compose_changed_cb (GtkTextBuffer *textbuf,
475 static void compose_wrap_cb (GtkAction *action,
477 static void compose_wrap_all_cb (GtkAction *action,
479 static void compose_find_cb (GtkAction *action,
481 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
483 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
486 static void compose_toggle_ruler_cb (GtkToggleAction *action,
488 static void compose_toggle_sign_cb (GtkToggleAction *action,
490 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
492 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
493 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
494 static void activate_privacy_system (Compose *compose,
495 PrefsAccount *account,
497 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
499 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
501 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
502 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
503 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
505 static void compose_attach_drag_received_cb (GtkWidget *widget,
506 GdkDragContext *drag_context,
509 GtkSelectionData *data,
513 static void compose_insert_drag_received_cb (GtkWidget *widget,
514 GdkDragContext *drag_context,
517 GtkSelectionData *data,
521 static void compose_header_drag_received_cb (GtkWidget *widget,
522 GdkDragContext *drag_context,
525 GtkSelectionData *data,
530 static gboolean compose_drag_drop (GtkWidget *widget,
531 GdkDragContext *drag_context,
533 guint time, gpointer user_data);
534 static gboolean completion_set_focus_to_subject
539 static void text_inserted (GtkTextBuffer *buffer,
544 static Compose *compose_generic_reply(MsgInfo *msginfo,
545 ComposeQuoteMode quote_mode,
549 gboolean followup_and_reply_to,
552 static void compose_headerentry_changed_cb (GtkWidget *entry,
553 ComposeHeaderEntry *headerentry);
554 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
556 ComposeHeaderEntry *headerentry);
557 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
558 ComposeHeaderEntry *headerentry);
560 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
562 static void compose_allow_user_actions (Compose *compose, gboolean allow);
564 static void compose_nothing_cb (GtkAction *action, gpointer data)
570 static void compose_check_all (GtkAction *action, gpointer data);
571 static void compose_highlight_all (GtkAction *action, gpointer data);
572 static void compose_check_backwards (GtkAction *action, gpointer data);
573 static void compose_check_forwards_go (GtkAction *action, gpointer data);
576 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
578 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
581 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
582 FolderItem *folder_item);
584 static void compose_attach_update_label(Compose *compose);
585 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
586 gboolean respect_default_to);
587 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
588 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
590 static GtkActionEntry compose_popup_entries[] =
592 {"Compose", NULL, "Compose", NULL, NULL, NULL },
593 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
594 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
595 {"Compose/---", NULL, "---", NULL, NULL, NULL },
596 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
599 static GtkActionEntry compose_entries[] =
601 {"Menu", NULL, "Menu", NULL, NULL, NULL },
603 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
604 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
606 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
608 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
609 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
610 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
612 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
613 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
614 {"Message/---", NULL, "---", NULL, NULL, NULL },
616 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
617 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
618 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
619 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
620 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
621 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
622 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
623 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
624 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
625 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
628 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
629 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
630 {"Edit/---", NULL, "---", NULL, NULL, NULL },
632 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
633 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
634 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
636 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
637 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
638 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
639 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
641 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
643 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
644 {"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*/
645 {"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*/
646 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
647 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
648 {"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*/
649 {"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*/
650 {"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*/
651 {"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*/
652 {"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*/
653 {"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*/
654 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
655 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
656 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
657 {"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*/
659 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
660 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
662 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
663 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
664 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
665 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
666 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
669 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
670 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
671 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
672 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
674 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
675 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
679 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
680 {"Options/---", NULL, "---", NULL, NULL, NULL },
681 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
682 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
684 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
685 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
687 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
688 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
689 #define ENC_ACTION(cs_char,c_char,string) \
690 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
692 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
693 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
694 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
695 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
696 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
697 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
698 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
699 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
700 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
703 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
705 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
706 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
707 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
708 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
711 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
714 static GtkToggleActionEntry compose_toggle_entries[] =
716 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
717 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
718 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
719 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
720 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
721 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
722 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
725 static GtkRadioActionEntry compose_radio_rm_entries[] =
727 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
728 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
729 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
730 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
733 static GtkRadioActionEntry compose_radio_prio_entries[] =
735 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
738 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
742 static GtkRadioActionEntry compose_radio_enc_entries[] =
744 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
779 static GtkTargetEntry compose_mime_types[] =
781 {"text/uri-list", 0, 0},
782 {"UTF8_STRING", 0, 0},
786 static gboolean compose_put_existing_to_front(MsgInfo *info)
788 const GList *compose_list = compose_get_compose_list();
789 const GList *elem = NULL;
792 for (elem = compose_list; elem != NULL && elem->data != NULL;
794 Compose *c = (Compose*)elem->data;
796 if (!c->targetinfo || !c->targetinfo->msgid ||
800 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
801 gtkut_window_popup(c->window);
809 static GdkColor quote_color1 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
811 static GdkColor quote_color2 =
812 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 static GdkColor quote_color3 =
814 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor1 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 static GdkColor quote_bgcolor2 =
819 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
820 static GdkColor quote_bgcolor3 =
821 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
823 static GdkColor signature_color = {
830 static GdkColor uri_color = {
837 static void compose_create_tags(GtkTextView *text, Compose *compose)
839 GtkTextBuffer *buffer;
840 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
841 #if !GTK_CHECK_VERSION(2, 24, 0)
848 buffer = gtk_text_view_get_buffer(text);
850 if (prefs_common.enable_color) {
851 /* grab the quote colors, converting from an int to a GdkColor */
852 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
854 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
856 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
858 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
860 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
862 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
864 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
866 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
869 signature_color = quote_color1 = quote_color2 = quote_color3 =
870 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
873 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
874 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
875 "foreground-gdk", "e_color1,
876 "paragraph-background-gdk", "e_bgcolor1,
878 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
879 "foreground-gdk", "e_color2,
880 "paragraph-background-gdk", "e_bgcolor2,
882 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
883 "foreground-gdk", "e_color3,
884 "paragraph-background-gdk", "e_bgcolor3,
887 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
888 "foreground-gdk", "e_color1,
890 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
891 "foreground-gdk", "e_color2,
893 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
894 "foreground-gdk", "e_color3,
898 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
899 "foreground-gdk", &signature_color,
902 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
903 "foreground-gdk", &uri_color,
905 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
906 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
908 #if !GTK_CHECK_VERSION(2, 24, 0)
909 color[0] = quote_color1;
910 color[1] = quote_color2;
911 color[2] = quote_color3;
912 color[3] = quote_bgcolor1;
913 color[4] = quote_bgcolor2;
914 color[5] = quote_bgcolor3;
915 color[6] = signature_color;
916 color[7] = uri_color;
918 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
919 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
921 for (i = 0; i < 8; i++) {
922 if (success[i] == FALSE) {
923 g_warning("Compose: color allocation failed.");
924 quote_color1 = quote_color2 = quote_color3 =
925 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
926 signature_color = uri_color = black;
932 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
935 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
938 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
940 return compose_generic_new(account, mailto, item, NULL, NULL);
943 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
945 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
948 #define SCROLL_TO_CURSOR(compose) { \
949 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
950 gtk_text_view_get_buffer( \
951 GTK_TEXT_VIEW(compose->text))); \
952 gtk_text_view_scroll_mark_onscreen( \
953 GTK_TEXT_VIEW(compose->text), \
957 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
960 if (folderidentifier) {
961 #if !GTK_CHECK_VERSION(2, 24, 0)
962 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
964 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
966 prefs_common.compose_save_to_history = add_history(
967 prefs_common.compose_save_to_history, folderidentifier);
968 #if !GTK_CHECK_VERSION(2, 24, 0)
969 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
970 prefs_common.compose_save_to_history);
972 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
973 prefs_common.compose_save_to_history);
977 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
978 if (folderidentifier)
979 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
981 gtk_entry_set_text(GTK_ENTRY(entry), "");
984 static gchar *compose_get_save_to(Compose *compose)
987 gchar *result = NULL;
988 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
989 result = gtk_editable_get_chars(entry, 0, -1);
992 #if !GTK_CHECK_VERSION(2, 24, 0)
993 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
995 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
997 prefs_common.compose_save_to_history = add_history(
998 prefs_common.compose_save_to_history, result);
999 #if !GTK_CHECK_VERSION(2, 24, 0)
1000 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
1001 prefs_common.compose_save_to_history);
1003 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1004 prefs_common.compose_save_to_history);
1010 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1011 GList *attach_files, GList *listAddress )
1014 GtkTextView *textview;
1015 GtkTextBuffer *textbuf;
1017 const gchar *subject_format = NULL;
1018 const gchar *body_format = NULL;
1019 gchar *mailto_from = NULL;
1020 PrefsAccount *mailto_account = NULL;
1021 MsgInfo* dummyinfo = NULL;
1022 gint cursor_pos = -1;
1023 MailField mfield = NO_FIELD_PRESENT;
1027 /* check if mailto defines a from */
1028 if (mailto && *mailto != '\0') {
1029 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1030 /* mailto defines a from, check if we can get account prefs from it,
1031 if not, the account prefs will be guessed using other ways, but we'll keep
1034 mailto_account = account_find_from_address(mailto_from, TRUE);
1035 if (mailto_account == NULL) {
1037 Xstrdup_a(tmp_from, mailto_from, return NULL);
1038 extract_address(tmp_from);
1039 mailto_account = account_find_from_address(tmp_from, TRUE);
1043 account = mailto_account;
1046 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1047 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1048 account = account_find_from_id(item->prefs->default_account);
1050 /* if no account prefs set, fallback to the current one */
1051 if (!account) account = cur_account;
1052 cm_return_val_if_fail(account != NULL, NULL);
1054 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1056 /* override from name if mailto asked for it */
1058 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1059 g_free(mailto_from);
1061 /* override from name according to folder properties */
1062 if (item && item->prefs &&
1063 item->prefs->compose_with_format &&
1064 item->prefs->compose_override_from_format &&
1065 *item->prefs->compose_override_from_format != '\0') {
1070 dummyinfo = compose_msginfo_new_from_compose(compose);
1072 /* decode \-escape sequences in the internal representation of the quote format */
1073 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1074 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1077 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1078 compose->gtkaspell);
1080 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1082 quote_fmt_scan_string(tmp);
1085 buf = quote_fmt_get_buffer();
1087 alertpanel_error(_("New message From format error."));
1089 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1090 quote_fmt_reset_vartable();
1091 quote_fmtlex_destroy();
1096 compose->replyinfo = NULL;
1097 compose->fwdinfo = NULL;
1099 textview = GTK_TEXT_VIEW(compose->text);
1100 textbuf = gtk_text_view_get_buffer(textview);
1101 compose_create_tags(textview, compose);
1103 undo_block(compose->undostruct);
1105 compose_set_dictionaries_from_folder_prefs(compose, item);
1108 if (account->auto_sig)
1109 compose_insert_sig(compose, FALSE);
1110 gtk_text_buffer_get_start_iter(textbuf, &iter);
1111 gtk_text_buffer_place_cursor(textbuf, &iter);
1113 if (account->protocol != A_NNTP) {
1114 if (mailto && *mailto != '\0') {
1115 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1118 compose_set_folder_prefs(compose, item, TRUE);
1120 if (item && item->ret_rcpt) {
1121 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1124 if (mailto && *mailto != '\0') {
1125 if (!strchr(mailto, '@'))
1126 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1128 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1129 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1130 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1131 mfield = TO_FIELD_PRESENT;
1134 * CLAWS: just don't allow return receipt request, even if the user
1135 * may want to send an email. simple but foolproof.
1137 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1139 compose_add_field_list( compose, listAddress );
1141 if (item && item->prefs && item->prefs->compose_with_format) {
1142 subject_format = item->prefs->compose_subject_format;
1143 body_format = item->prefs->compose_body_format;
1144 } else if (account->compose_with_format) {
1145 subject_format = account->compose_subject_format;
1146 body_format = account->compose_body_format;
1147 } else if (prefs_common.compose_with_format) {
1148 subject_format = prefs_common.compose_subject_format;
1149 body_format = prefs_common.compose_body_format;
1152 if (subject_format || body_format) {
1155 && *subject_format != '\0' )
1157 gchar *subject = NULL;
1162 dummyinfo = compose_msginfo_new_from_compose(compose);
1164 /* decode \-escape sequences in the internal representation of the quote format */
1165 tmp = g_malloc(strlen(subject_format)+1);
1166 pref_get_unescaped_pref(tmp, subject_format);
1168 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1170 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1171 compose->gtkaspell);
1173 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1175 quote_fmt_scan_string(tmp);
1178 buf = quote_fmt_get_buffer();
1180 alertpanel_error(_("New message subject format error."));
1182 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1183 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1184 quote_fmt_reset_vartable();
1185 quote_fmtlex_destroy();
1189 mfield = SUBJECT_FIELD_PRESENT;
1193 && *body_format != '\0' )
1196 GtkTextBuffer *buffer;
1197 GtkTextIter start, end;
1201 dummyinfo = compose_msginfo_new_from_compose(compose);
1203 text = GTK_TEXT_VIEW(compose->text);
1204 buffer = gtk_text_view_get_buffer(text);
1205 gtk_text_buffer_get_start_iter(buffer, &start);
1206 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1207 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1209 compose_quote_fmt(compose, dummyinfo,
1211 NULL, tmp, FALSE, TRUE,
1212 _("The body of the \"New message\" template has an error at line %d."));
1213 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1214 quote_fmt_reset_vartable();
1218 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1219 gtkaspell_highlight_all(compose->gtkaspell);
1221 mfield = BODY_FIELD_PRESENT;
1225 procmsg_msginfo_free( &dummyinfo );
1231 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1232 ainfo = (AttachInfo *) curr->data;
1234 compose_insert_file(compose, ainfo->file);
1236 compose_attach_append(compose, ainfo->file, ainfo->file,
1237 ainfo->content_type, ainfo->charset);
1241 compose_show_first_last_header(compose, TRUE);
1243 /* Set save folder */
1244 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1245 gchar *folderidentifier;
1247 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1248 folderidentifier = folder_item_get_identifier(item);
1249 compose_set_save_to(compose, folderidentifier);
1250 g_free(folderidentifier);
1253 /* Place cursor according to provided input (mfield) */
1255 case NO_FIELD_PRESENT:
1256 if (compose->header_last)
1257 gtk_widget_grab_focus(compose->header_last->entry);
1259 case TO_FIELD_PRESENT:
1260 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1262 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1265 gtk_widget_grab_focus(compose->subject_entry);
1267 case SUBJECT_FIELD_PRESENT:
1268 textview = GTK_TEXT_VIEW(compose->text);
1271 textbuf = gtk_text_view_get_buffer(textview);
1274 mark = gtk_text_buffer_get_insert(textbuf);
1275 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1276 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1278 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1279 * only defers where it comes to the variable body
1280 * is not null. If no body is present compose->text
1281 * will be null in which case you cannot place the
1282 * cursor inside the component so. An empty component
1283 * is therefore created before placing the cursor
1285 case BODY_FIELD_PRESENT:
1286 cursor_pos = quote_fmt_get_cursor_pos();
1287 if (cursor_pos == -1)
1288 gtk_widget_grab_focus(compose->header_last->entry);
1290 gtk_widget_grab_focus(compose->text);
1294 undo_unblock(compose->undostruct);
1296 if (prefs_common.auto_exteditor)
1297 compose_exec_ext_editor(compose);
1299 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1301 SCROLL_TO_CURSOR(compose);
1303 compose->modified = FALSE;
1304 compose_set_title(compose);
1306 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1311 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1312 gboolean override_pref, const gchar *system)
1314 const gchar *privacy = NULL;
1316 cm_return_if_fail(compose != NULL);
1317 cm_return_if_fail(account != NULL);
1319 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1322 if (account->default_privacy_system && strlen(account->default_privacy_system))
1323 privacy = account->default_privacy_system;
1327 GSList *privacy_avail = privacy_get_system_ids();
1328 if (privacy_avail && g_slist_length(privacy_avail)) {
1329 privacy = (gchar *)(privacy_avail->data);
1331 g_slist_free_full(privacy_avail, g_free);
1333 if (privacy != NULL) {
1335 g_free(compose->privacy_system);
1336 compose->privacy_system = NULL;
1337 g_free(compose->encdata);
1338 compose->encdata = NULL;
1340 if (compose->privacy_system == NULL)
1341 compose->privacy_system = g_strdup(privacy);
1342 else if (*(compose->privacy_system) == '\0') {
1343 g_free(compose->privacy_system);
1344 g_free(compose->encdata);
1345 compose->encdata = NULL;
1346 compose->privacy_system = g_strdup(privacy);
1348 compose_update_privacy_system_menu_item(compose, FALSE);
1349 compose_use_encryption(compose, TRUE);
1353 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1355 const gchar *privacy = NULL;
1357 if (account->default_privacy_system && strlen(account->default_privacy_system))
1358 privacy = account->default_privacy_system;
1362 GSList *privacy_avail = privacy_get_system_ids();
1363 if (privacy_avail && g_slist_length(privacy_avail)) {
1364 privacy = (gchar *)(privacy_avail->data);
1368 if (privacy != NULL) {
1370 g_free(compose->privacy_system);
1371 compose->privacy_system = NULL;
1372 g_free(compose->encdata);
1373 compose->encdata = NULL;
1375 if (compose->privacy_system == NULL)
1376 compose->privacy_system = g_strdup(privacy);
1377 compose_update_privacy_system_menu_item(compose, FALSE);
1378 compose_use_signing(compose, TRUE);
1382 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1386 Compose *compose = NULL;
1388 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1390 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1391 cm_return_val_if_fail(msginfo != NULL, NULL);
1393 list_len = g_slist_length(msginfo_list);
1397 case COMPOSE_REPLY_TO_ADDRESS:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1399 FALSE, prefs_common.default_reply_list, FALSE, body);
1401 case COMPOSE_REPLY_WITH_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1403 FALSE, prefs_common.default_reply_list, FALSE, body);
1405 case COMPOSE_REPLY_WITHOUT_QUOTE:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1407 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1409 case COMPOSE_REPLY_TO_SENDER:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1411 FALSE, FALSE, TRUE, body);
1413 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1414 compose = compose_followup_and_reply_to(msginfo,
1415 COMPOSE_QUOTE_CHECK,
1416 FALSE, FALSE, body);
1418 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1420 FALSE, FALSE, TRUE, body);
1422 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1423 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1424 FALSE, FALSE, TRUE, NULL);
1426 case COMPOSE_REPLY_TO_ALL:
1427 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1428 TRUE, FALSE, FALSE, body);
1430 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1431 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1432 TRUE, FALSE, FALSE, body);
1434 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1435 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1436 TRUE, FALSE, FALSE, NULL);
1438 case COMPOSE_REPLY_TO_LIST:
1439 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1440 FALSE, TRUE, FALSE, body);
1442 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1443 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1444 FALSE, TRUE, FALSE, body);
1446 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1447 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1448 FALSE, TRUE, FALSE, NULL);
1450 case COMPOSE_FORWARD:
1451 if (prefs_common.forward_as_attachment) {
1452 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1455 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1459 case COMPOSE_FORWARD_INLINE:
1460 /* check if we reply to more than one Message */
1461 if (list_len == 1) {
1462 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1465 /* more messages FALL THROUGH */
1466 case COMPOSE_FORWARD_AS_ATTACH:
1467 compose = compose_forward_multiple(NULL, msginfo_list);
1469 case COMPOSE_REDIRECT:
1470 compose = compose_redirect(NULL, msginfo, FALSE);
1473 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1476 if (compose == NULL) {
1477 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1481 compose->rmode = mode;
1482 switch (compose->rmode) {
1484 case COMPOSE_REPLY_WITH_QUOTE:
1485 case COMPOSE_REPLY_WITHOUT_QUOTE:
1486 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1487 debug_print("reply mode Normal\n");
1488 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1489 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1491 case COMPOSE_REPLY_TO_SENDER:
1492 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1493 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1494 debug_print("reply mode Sender\n");
1495 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1497 case COMPOSE_REPLY_TO_ALL:
1498 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1499 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1500 debug_print("reply mode All\n");
1501 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1503 case COMPOSE_REPLY_TO_LIST:
1504 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1505 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1506 debug_print("reply mode List\n");
1507 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1509 case COMPOSE_REPLY_TO_ADDRESS:
1510 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1518 static Compose *compose_reply(MsgInfo *msginfo,
1519 ComposeQuoteMode quote_mode,
1525 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1526 to_sender, FALSE, body);
1529 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1530 ComposeQuoteMode quote_mode,
1535 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1536 to_sender, TRUE, body);
1539 static void compose_extract_original_charset(Compose *compose)
1541 MsgInfo *info = NULL;
1542 if (compose->replyinfo) {
1543 info = compose->replyinfo;
1544 } else if (compose->fwdinfo) {
1545 info = compose->fwdinfo;
1546 } else if (compose->targetinfo) {
1547 info = compose->targetinfo;
1550 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1551 MimeInfo *partinfo = mimeinfo;
1552 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1553 partinfo = procmime_mimeinfo_next(partinfo);
1555 compose->orig_charset =
1556 g_strdup(procmime_mimeinfo_get_parameter(
1557 partinfo, "charset"));
1559 procmime_mimeinfo_free_all(&mimeinfo);
1563 #define SIGNAL_BLOCK(buffer) { \
1564 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1565 G_CALLBACK(compose_changed_cb), \
1567 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1568 G_CALLBACK(text_inserted), \
1572 #define SIGNAL_UNBLOCK(buffer) { \
1573 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1574 G_CALLBACK(compose_changed_cb), \
1576 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1577 G_CALLBACK(text_inserted), \
1581 static Compose *compose_generic_reply(MsgInfo *msginfo,
1582 ComposeQuoteMode quote_mode,
1583 gboolean to_all, gboolean to_ml,
1585 gboolean followup_and_reply_to,
1589 PrefsAccount *account = NULL;
1590 GtkTextView *textview;
1591 GtkTextBuffer *textbuf;
1592 gboolean quote = FALSE;
1593 const gchar *qmark = NULL;
1594 const gchar *body_fmt = NULL;
1595 gchar *s_system = NULL;
1597 cm_return_val_if_fail(msginfo != NULL, NULL);
1598 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1600 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1602 cm_return_val_if_fail(account != NULL, NULL);
1604 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1606 compose->updating = TRUE;
1608 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1609 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1611 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1612 if (!compose->replyinfo)
1613 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1615 compose_extract_original_charset(compose);
1617 if (msginfo->folder && msginfo->folder->ret_rcpt)
1618 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1620 /* Set save folder */
1621 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1622 gchar *folderidentifier;
1624 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1625 folderidentifier = folder_item_get_identifier(msginfo->folder);
1626 compose_set_save_to(compose, folderidentifier);
1627 g_free(folderidentifier);
1630 if (compose_parse_header(compose, msginfo) < 0) {
1631 compose->updating = FALSE;
1632 compose_destroy(compose);
1636 /* override from name according to folder properties */
1637 if (msginfo->folder && msginfo->folder->prefs &&
1638 msginfo->folder->prefs->reply_with_format &&
1639 msginfo->folder->prefs->reply_override_from_format &&
1640 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1645 /* decode \-escape sequences in the internal representation of the quote format */
1646 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1647 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1650 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1651 compose->gtkaspell);
1653 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1655 quote_fmt_scan_string(tmp);
1658 buf = quote_fmt_get_buffer();
1660 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1662 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1663 quote_fmt_reset_vartable();
1664 quote_fmtlex_destroy();
1669 textview = (GTK_TEXT_VIEW(compose->text));
1670 textbuf = gtk_text_view_get_buffer(textview);
1671 compose_create_tags(textview, compose);
1673 undo_block(compose->undostruct);
1675 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1676 gtkaspell_block_check(compose->gtkaspell);
1679 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1680 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1681 /* use the reply format of folder (if enabled), or the account's one
1682 (if enabled) or fallback to the global reply format, which is always
1683 enabled (even if empty), and use the relevant quotemark */
1685 if (msginfo->folder && msginfo->folder->prefs &&
1686 msginfo->folder->prefs->reply_with_format) {
1687 qmark = msginfo->folder->prefs->reply_quotemark;
1688 body_fmt = msginfo->folder->prefs->reply_body_format;
1690 } else if (account->reply_with_format) {
1691 qmark = account->reply_quotemark;
1692 body_fmt = account->reply_body_format;
1695 qmark = prefs_common.quotemark;
1696 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1697 body_fmt = gettext(prefs_common.quotefmt);
1704 /* empty quotemark is not allowed */
1705 if (qmark == NULL || *qmark == '\0')
1707 compose_quote_fmt(compose, compose->replyinfo,
1708 body_fmt, qmark, body, FALSE, TRUE,
1709 _("The body of the \"Reply\" template has an error at line %d."));
1710 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1711 quote_fmt_reset_vartable();
1714 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1715 compose_force_encryption(compose, account, FALSE, s_system);
1718 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1719 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1720 compose_force_signing(compose, account, s_system);
1724 SIGNAL_BLOCK(textbuf);
1726 if (account->auto_sig)
1727 compose_insert_sig(compose, FALSE);
1729 compose_wrap_all(compose);
1732 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1733 gtkaspell_highlight_all(compose->gtkaspell);
1734 gtkaspell_unblock_check(compose->gtkaspell);
1736 SIGNAL_UNBLOCK(textbuf);
1738 gtk_widget_grab_focus(compose->text);
1740 undo_unblock(compose->undostruct);
1742 if (prefs_common.auto_exteditor)
1743 compose_exec_ext_editor(compose);
1745 compose->modified = FALSE;
1746 compose_set_title(compose);
1748 compose->updating = FALSE;
1749 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1750 SCROLL_TO_CURSOR(compose);
1752 if (compose->deferred_destroy) {
1753 compose_destroy(compose);
1761 #define INSERT_FW_HEADER(var, hdr) \
1762 if (msginfo->var && *msginfo->var) { \
1763 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1764 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1765 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1768 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1769 gboolean as_attach, const gchar *body,
1770 gboolean no_extedit,
1774 GtkTextView *textview;
1775 GtkTextBuffer *textbuf;
1776 gint cursor_pos = -1;
1779 cm_return_val_if_fail(msginfo != NULL, NULL);
1780 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1782 if (!account && !(account = compose_find_account(msginfo)))
1783 account = cur_account;
1785 if (!prefs_common.forward_as_attachment)
1786 mode = COMPOSE_FORWARD_INLINE;
1788 mode = COMPOSE_FORWARD;
1789 compose = compose_create(account, msginfo->folder, mode, batch);
1791 compose->updating = TRUE;
1792 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1793 if (!compose->fwdinfo)
1794 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1796 compose_extract_original_charset(compose);
1798 if (msginfo->subject && *msginfo->subject) {
1799 gchar *buf, *buf2, *p;
1801 buf = p = g_strdup(msginfo->subject);
1802 p += subject_get_prefix_length(p);
1803 memmove(buf, p, strlen(p) + 1);
1805 buf2 = g_strdup_printf("Fw: %s", buf);
1806 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1812 /* override from name according to folder properties */
1813 if (msginfo->folder && msginfo->folder->prefs &&
1814 msginfo->folder->prefs->forward_with_format &&
1815 msginfo->folder->prefs->forward_override_from_format &&
1816 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1820 MsgInfo *full_msginfo = NULL;
1823 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1825 full_msginfo = procmsg_msginfo_copy(msginfo);
1827 /* decode \-escape sequences in the internal representation of the quote format */
1828 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1829 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1832 gtkaspell_block_check(compose->gtkaspell);
1833 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1834 compose->gtkaspell);
1836 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1838 quote_fmt_scan_string(tmp);
1841 buf = quote_fmt_get_buffer();
1843 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1845 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1846 quote_fmt_reset_vartable();
1847 quote_fmtlex_destroy();
1850 procmsg_msginfo_free(&full_msginfo);
1853 textview = GTK_TEXT_VIEW(compose->text);
1854 textbuf = gtk_text_view_get_buffer(textview);
1855 compose_create_tags(textview, compose);
1857 undo_block(compose->undostruct);
1861 msgfile = procmsg_get_message_file(msginfo);
1862 if (!is_file_exist(msgfile))
1863 g_warning("%s: file does not exist", msgfile);
1865 compose_attach_append(compose, msgfile, msgfile,
1866 "message/rfc822", NULL);
1870 const gchar *qmark = NULL;
1871 const gchar *body_fmt = NULL;
1872 MsgInfo *full_msginfo;
1874 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1876 full_msginfo = procmsg_msginfo_copy(msginfo);
1878 /* use the forward format of folder (if enabled), or the account's one
1879 (if enabled) or fallback to the global forward format, which is always
1880 enabled (even if empty), and use the relevant quotemark */
1881 if (msginfo->folder && msginfo->folder->prefs &&
1882 msginfo->folder->prefs->forward_with_format) {
1883 qmark = msginfo->folder->prefs->forward_quotemark;
1884 body_fmt = msginfo->folder->prefs->forward_body_format;
1886 } else if (account->forward_with_format) {
1887 qmark = account->forward_quotemark;
1888 body_fmt = account->forward_body_format;
1891 qmark = prefs_common.fw_quotemark;
1892 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1893 body_fmt = gettext(prefs_common.fw_quotefmt);
1898 /* empty quotemark is not allowed */
1899 if (qmark == NULL || *qmark == '\0')
1902 compose_quote_fmt(compose, full_msginfo,
1903 body_fmt, qmark, body, FALSE, TRUE,
1904 _("The body of the \"Forward\" template has an error at line %d."));
1905 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1906 quote_fmt_reset_vartable();
1907 compose_attach_parts(compose, msginfo);
1909 procmsg_msginfo_free(&full_msginfo);
1912 SIGNAL_BLOCK(textbuf);
1914 if (account->auto_sig)
1915 compose_insert_sig(compose, FALSE);
1917 compose_wrap_all(compose);
1920 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1921 gtkaspell_highlight_all(compose->gtkaspell);
1922 gtkaspell_unblock_check(compose->gtkaspell);
1924 SIGNAL_UNBLOCK(textbuf);
1926 cursor_pos = quote_fmt_get_cursor_pos();
1927 if (cursor_pos == -1)
1928 gtk_widget_grab_focus(compose->header_last->entry);
1930 gtk_widget_grab_focus(compose->text);
1932 if (!no_extedit && prefs_common.auto_exteditor)
1933 compose_exec_ext_editor(compose);
1936 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1937 gchar *folderidentifier;
1939 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1940 folderidentifier = folder_item_get_identifier(msginfo->folder);
1941 compose_set_save_to(compose, folderidentifier);
1942 g_free(folderidentifier);
1945 undo_unblock(compose->undostruct);
1947 compose->modified = FALSE;
1948 compose_set_title(compose);
1950 compose->updating = FALSE;
1951 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1952 SCROLL_TO_CURSOR(compose);
1954 if (compose->deferred_destroy) {
1955 compose_destroy(compose);
1959 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1964 #undef INSERT_FW_HEADER
1966 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1969 GtkTextView *textview;
1970 GtkTextBuffer *textbuf;
1974 gboolean single_mail = TRUE;
1976 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1978 if (g_slist_length(msginfo_list) > 1)
1979 single_mail = FALSE;
1981 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1982 if (((MsgInfo *)msginfo->data)->folder == NULL)
1985 /* guess account from first selected message */
1987 !(account = compose_find_account(msginfo_list->data)))
1988 account = cur_account;
1990 cm_return_val_if_fail(account != NULL, NULL);
1992 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1993 if (msginfo->data) {
1994 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1995 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1999 if (msginfo_list == NULL || msginfo_list->data == NULL) {
2000 g_warning("no msginfo_list");
2004 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
2006 compose->updating = TRUE;
2008 /* override from name according to folder properties */
2009 if (msginfo_list->data) {
2010 MsgInfo *msginfo = msginfo_list->data;
2012 if (msginfo->folder && msginfo->folder->prefs &&
2013 msginfo->folder->prefs->forward_with_format &&
2014 msginfo->folder->prefs->forward_override_from_format &&
2015 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2020 /* decode \-escape sequences in the internal representation of the quote format */
2021 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2022 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2025 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2026 compose->gtkaspell);
2028 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2030 quote_fmt_scan_string(tmp);
2033 buf = quote_fmt_get_buffer();
2035 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2037 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2038 quote_fmt_reset_vartable();
2039 quote_fmtlex_destroy();
2045 textview = GTK_TEXT_VIEW(compose->text);
2046 textbuf = gtk_text_view_get_buffer(textview);
2047 compose_create_tags(textview, compose);
2049 undo_block(compose->undostruct);
2050 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2051 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2053 if (!is_file_exist(msgfile))
2054 g_warning("%s: file does not exist", msgfile);
2056 compose_attach_append(compose, msgfile, msgfile,
2057 "message/rfc822", NULL);
2062 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2063 if (info->subject && *info->subject) {
2064 gchar *buf, *buf2, *p;
2066 buf = p = g_strdup(info->subject);
2067 p += subject_get_prefix_length(p);
2068 memmove(buf, p, strlen(p) + 1);
2070 buf2 = g_strdup_printf("Fw: %s", buf);
2071 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2077 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2078 _("Fw: multiple emails"));
2081 SIGNAL_BLOCK(textbuf);
2083 if (account->auto_sig)
2084 compose_insert_sig(compose, FALSE);
2086 compose_wrap_all(compose);
2088 SIGNAL_UNBLOCK(textbuf);
2090 gtk_text_buffer_get_start_iter(textbuf, &iter);
2091 gtk_text_buffer_place_cursor(textbuf, &iter);
2093 if (prefs_common.auto_exteditor)
2094 compose_exec_ext_editor(compose);
2096 gtk_widget_grab_focus(compose->header_last->entry);
2097 undo_unblock(compose->undostruct);
2098 compose->modified = FALSE;
2099 compose_set_title(compose);
2101 compose->updating = FALSE;
2102 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2103 SCROLL_TO_CURSOR(compose);
2105 if (compose->deferred_destroy) {
2106 compose_destroy(compose);
2110 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2115 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2117 GtkTextIter start = *iter;
2118 GtkTextIter end_iter;
2119 int start_pos = gtk_text_iter_get_offset(&start);
2121 if (!compose->account->sig_sep)
2124 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2125 start_pos+strlen(compose->account->sig_sep));
2127 /* check sig separator */
2128 str = gtk_text_iter_get_text(&start, &end_iter);
2129 if (!strcmp(str, compose->account->sig_sep)) {
2131 /* check end of line (\n) */
2132 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2133 start_pos+strlen(compose->account->sig_sep));
2134 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2135 start_pos+strlen(compose->account->sig_sep)+1);
2136 tmp = gtk_text_iter_get_text(&start, &end_iter);
2137 if (!strcmp(tmp,"\n")) {
2149 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2151 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2152 Compose *compose = (Compose *)data;
2153 FolderItem *old_item = NULL;
2154 FolderItem *new_item = NULL;
2155 gchar *old_id, *new_id;
2157 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2158 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2161 old_item = hookdata->item;
2162 new_item = hookdata->item2;
2164 old_id = folder_item_get_identifier(old_item);
2165 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2167 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2168 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2169 compose->targetinfo->folder = new_item;
2172 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2173 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2174 compose->replyinfo->folder = new_item;
2177 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2178 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2179 compose->fwdinfo->folder = new_item;
2187 static void compose_colorize_signature(Compose *compose)
2189 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2191 GtkTextIter end_iter;
2192 gtk_text_buffer_get_start_iter(buffer, &iter);
2193 while (gtk_text_iter_forward_line(&iter))
2194 if (compose_is_sig_separator(compose, buffer, &iter)) {
2195 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2196 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2200 #define BLOCK_WRAP() { \
2201 prev_autowrap = compose->autowrap; \
2202 buffer = gtk_text_view_get_buffer( \
2203 GTK_TEXT_VIEW(compose->text)); \
2204 compose->autowrap = FALSE; \
2206 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2207 G_CALLBACK(compose_changed_cb), \
2209 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2210 G_CALLBACK(text_inserted), \
2213 #define UNBLOCK_WRAP() { \
2214 compose->autowrap = prev_autowrap; \
2215 if (compose->autowrap) { \
2216 gint old = compose->draft_timeout_tag; \
2217 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2218 compose_wrap_all(compose); \
2219 compose->draft_timeout_tag = old; \
2222 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2223 G_CALLBACK(compose_changed_cb), \
2225 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2226 G_CALLBACK(text_inserted), \
2230 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2232 Compose *compose = NULL;
2233 PrefsAccount *account = NULL;
2234 GtkTextView *textview;
2235 GtkTextBuffer *textbuf;
2239 gboolean use_signing = FALSE;
2240 gboolean use_encryption = FALSE;
2241 gchar *privacy_system = NULL;
2242 int priority = PRIORITY_NORMAL;
2243 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2244 gboolean autowrap = prefs_common.autowrap;
2245 gboolean autoindent = prefs_common.auto_indent;
2246 HeaderEntry *manual_headers = NULL;
2248 cm_return_val_if_fail(msginfo != NULL, NULL);
2249 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2251 if (compose_put_existing_to_front(msginfo)) {
2255 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2256 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2257 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2258 gchar *queueheader_buf = NULL;
2261 /* Select Account from queue headers */
2262 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2263 "X-Claws-Account-Id:")) {
2264 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2265 account = account_find_from_id(id);
2266 g_free(queueheader_buf);
2268 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2269 "X-Sylpheed-Account-Id:")) {
2270 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2271 account = account_find_from_id(id);
2272 g_free(queueheader_buf);
2274 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2276 id = atoi(&queueheader_buf[strlen("NAID:")]);
2277 account = account_find_from_id(id);
2278 g_free(queueheader_buf);
2280 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2282 id = atoi(&queueheader_buf[strlen("MAID:")]);
2283 account = account_find_from_id(id);
2284 g_free(queueheader_buf);
2286 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2288 account = account_find_from_address(queueheader_buf, FALSE);
2289 g_free(queueheader_buf);
2291 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2293 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2294 use_signing = param;
2295 g_free(queueheader_buf);
2297 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2298 "X-Sylpheed-Sign:")) {
2299 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2300 use_signing = param;
2301 g_free(queueheader_buf);
2303 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2304 "X-Claws-Encrypt:")) {
2305 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2306 use_encryption = param;
2307 g_free(queueheader_buf);
2309 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2310 "X-Sylpheed-Encrypt:")) {
2311 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2312 use_encryption = param;
2313 g_free(queueheader_buf);
2315 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2316 "X-Claws-Auto-Wrapping:")) {
2317 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2319 g_free(queueheader_buf);
2321 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2322 "X-Claws-Auto-Indent:")) {
2323 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2325 g_free(queueheader_buf);
2327 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2328 "X-Claws-Privacy-System:")) {
2329 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2330 g_free(queueheader_buf);
2332 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2333 "X-Sylpheed-Privacy-System:")) {
2334 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2335 g_free(queueheader_buf);
2337 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2339 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2341 g_free(queueheader_buf);
2343 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2345 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2346 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2347 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2348 if (orig_item != NULL) {
2349 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2353 g_free(queueheader_buf);
2355 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2357 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2358 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2359 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2360 if (orig_item != NULL) {
2361 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2365 g_free(queueheader_buf);
2367 /* Get manual headers */
2368 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2369 "X-Claws-Manual-Headers:")) {
2370 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2371 if (listmh && *listmh != '\0') {
2372 debug_print("Got manual headers: %s\n", listmh);
2373 manual_headers = procheader_entries_from_str(listmh);
2376 g_free(queueheader_buf);
2379 account = msginfo->folder->folder->account;
2382 if (!account && prefs_common.reedit_account_autosel) {
2384 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2385 extract_address(from);
2386 account = account_find_from_address(from, FALSE);
2391 account = cur_account;
2393 cm_return_val_if_fail(account != NULL, NULL);
2395 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2397 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2398 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2399 compose->autowrap = autowrap;
2400 compose->replyinfo = replyinfo;
2401 compose->fwdinfo = fwdinfo;
2403 compose->updating = TRUE;
2404 compose->priority = priority;
2406 if (privacy_system != NULL) {
2407 compose->privacy_system = privacy_system;
2408 compose_use_signing(compose, use_signing);
2409 compose_use_encryption(compose, use_encryption);
2410 compose_update_privacy_system_menu_item(compose, FALSE);
2412 activate_privacy_system(compose, account, FALSE);
2415 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2417 compose_extract_original_charset(compose);
2419 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2420 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2421 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2422 gchar *queueheader_buf = NULL;
2424 /* Set message save folder */
2425 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2426 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2427 compose_set_save_to(compose, &queueheader_buf[4]);
2428 g_free(queueheader_buf);
2430 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2431 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2433 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2435 g_free(queueheader_buf);
2439 if (compose_parse_header(compose, msginfo) < 0) {
2440 compose->updating = FALSE;
2441 compose_destroy(compose);
2444 compose_reedit_set_entry(compose, msginfo);
2446 textview = GTK_TEXT_VIEW(compose->text);
2447 textbuf = gtk_text_view_get_buffer(textview);
2448 compose_create_tags(textview, compose);
2450 mark = gtk_text_buffer_get_insert(textbuf);
2451 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2453 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2454 G_CALLBACK(compose_changed_cb),
2457 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2458 fp = procmime_get_first_encrypted_text_content(msginfo);
2460 compose_force_encryption(compose, account, TRUE, NULL);
2463 fp = procmime_get_first_text_content(msginfo);
2466 g_warning("Can't get text part");
2470 gchar buf[BUFFSIZE];
2471 gboolean prev_autowrap;
2472 GtkTextBuffer *buffer;
2474 while (fgets(buf, sizeof(buf), fp) != NULL) {
2476 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2482 compose_attach_parts(compose, msginfo);
2484 compose_colorize_signature(compose);
2486 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2487 G_CALLBACK(compose_changed_cb),
2490 if (manual_headers != NULL) {
2491 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2492 procheader_entries_free(manual_headers);
2493 compose->updating = FALSE;
2494 compose_destroy(compose);
2497 procheader_entries_free(manual_headers);
2500 gtk_widget_grab_focus(compose->text);
2502 if (prefs_common.auto_exteditor) {
2503 compose_exec_ext_editor(compose);
2505 compose->modified = FALSE;
2506 compose_set_title(compose);
2508 compose->updating = FALSE;
2509 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2510 SCROLL_TO_CURSOR(compose);
2512 if (compose->deferred_destroy) {
2513 compose_destroy(compose);
2517 compose->sig_str = account_get_signature_str(compose->account);
2519 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2524 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2531 cm_return_val_if_fail(msginfo != NULL, NULL);
2534 account = account_get_reply_account(msginfo,
2535 prefs_common.reply_account_autosel);
2536 cm_return_val_if_fail(account != NULL, NULL);
2538 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2540 compose->updating = TRUE;
2542 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2543 compose->replyinfo = NULL;
2544 compose->fwdinfo = NULL;
2546 compose_show_first_last_header(compose, TRUE);
2548 gtk_widget_grab_focus(compose->header_last->entry);
2550 filename = procmsg_get_message_file(msginfo);
2552 if (filename == NULL) {
2553 compose->updating = FALSE;
2554 compose_destroy(compose);
2559 compose->redirect_filename = filename;
2561 /* Set save folder */
2562 item = msginfo->folder;
2563 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2564 gchar *folderidentifier;
2566 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2567 folderidentifier = folder_item_get_identifier(item);
2568 compose_set_save_to(compose, folderidentifier);
2569 g_free(folderidentifier);
2572 compose_attach_parts(compose, msginfo);
2574 if (msginfo->subject)
2575 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2577 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2579 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2580 _("The body of the \"Redirect\" template has an error at line %d."));
2581 quote_fmt_reset_vartable();
2582 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2584 compose_colorize_signature(compose);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2598 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2599 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2601 if (compose->toolbar->draft_btn)
2602 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2603 if (compose->toolbar->insert_btn)
2604 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2605 if (compose->toolbar->attach_btn)
2606 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2607 if (compose->toolbar->sig_btn)
2608 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2609 if (compose->toolbar->exteditor_btn)
2610 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2611 if (compose->toolbar->linewrap_current_btn)
2612 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2613 if (compose->toolbar->linewrap_all_btn)
2614 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2616 compose->modified = FALSE;
2617 compose_set_title(compose);
2618 compose->updating = FALSE;
2619 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2620 SCROLL_TO_CURSOR(compose);
2622 if (compose->deferred_destroy) {
2623 compose_destroy(compose);
2627 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2632 const GList *compose_get_compose_list(void)
2634 return compose_list;
2637 void compose_entry_append(Compose *compose, const gchar *address,
2638 ComposeEntryType type, ComposePrefType pref_type)
2640 const gchar *header;
2642 gboolean in_quote = FALSE;
2643 if (!address || *address == '\0') return;
2650 header = N_("Bcc:");
2652 case COMPOSE_REPLYTO:
2653 header = N_("Reply-To:");
2655 case COMPOSE_NEWSGROUPS:
2656 header = N_("Newsgroups:");
2658 case COMPOSE_FOLLOWUPTO:
2659 header = N_( "Followup-To:");
2661 case COMPOSE_INREPLYTO:
2662 header = N_( "In-Reply-To:");
2669 header = prefs_common_translated_header_name(header);
2671 cur = begin = (gchar *)address;
2673 /* we separate the line by commas, but not if we're inside a quoted
2675 while (*cur != '\0') {
2677 in_quote = !in_quote;
2678 if (*cur == ',' && !in_quote) {
2679 gchar *tmp = g_strdup(begin);
2681 tmp[cur-begin]='\0';
2684 while (*tmp == ' ' || *tmp == '\t')
2686 compose_add_header_entry(compose, header, tmp, pref_type);
2687 compose_entry_indicate(compose, tmp);
2694 gchar *tmp = g_strdup(begin);
2696 tmp[cur-begin]='\0';
2697 while (*tmp == ' ' || *tmp == '\t')
2699 compose_add_header_entry(compose, header, tmp, pref_type);
2700 compose_entry_indicate(compose, tmp);
2705 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2710 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2711 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2712 if (gtk_entry_get_text(entry) &&
2713 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2714 gtk_widget_modify_base(
2715 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2716 GTK_STATE_NORMAL, &default_header_bgcolor);
2717 gtk_widget_modify_text(
2718 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2719 GTK_STATE_NORMAL, &default_header_color);
2724 void compose_toolbar_cb(gint action, gpointer data)
2726 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2727 Compose *compose = (Compose*)toolbar_item->parent;
2729 cm_return_if_fail(compose != NULL);
2733 compose_send_cb(NULL, compose);
2736 compose_send_later_cb(NULL, compose);
2739 compose_draft(compose, COMPOSE_QUIT_EDITING);
2742 compose_insert_file_cb(NULL, compose);
2745 compose_attach_cb(NULL, compose);
2748 compose_insert_sig(compose, FALSE);
2751 compose_insert_sig(compose, TRUE);
2754 compose_ext_editor_cb(NULL, compose);
2756 case A_LINEWRAP_CURRENT:
2757 compose_beautify_paragraph(compose, NULL, TRUE);
2759 case A_LINEWRAP_ALL:
2760 compose_wrap_all_full(compose, TRUE);
2763 compose_address_cb(NULL, compose);
2766 case A_CHECK_SPELLING:
2767 compose_check_all(NULL, compose);
2770 case A_PRIVACY_SIGN:
2772 case A_PRIVACY_ENCRYPT:
2779 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2784 gchar *subject = NULL;
2788 gchar **attach = NULL;
2789 gchar *inreplyto = NULL;
2790 MailField mfield = NO_FIELD_PRESENT;
2792 /* get mailto parts but skip from */
2793 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2796 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2797 mfield = TO_FIELD_PRESENT;
2800 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2802 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2804 if (!g_utf8_validate (subject, -1, NULL)) {
2805 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2806 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2809 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2811 mfield = SUBJECT_FIELD_PRESENT;
2814 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2815 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2818 gboolean prev_autowrap = compose->autowrap;
2820 compose->autowrap = FALSE;
2822 mark = gtk_text_buffer_get_insert(buffer);
2823 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2825 if (!g_utf8_validate (body, -1, NULL)) {
2826 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2827 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2830 gtk_text_buffer_insert(buffer, &iter, body, -1);
2832 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2834 compose->autowrap = prev_autowrap;
2835 if (compose->autowrap)
2836 compose_wrap_all(compose);
2837 mfield = BODY_FIELD_PRESENT;
2841 gint i = 0, att = 0;
2842 gchar *warn_files = NULL;
2843 while (attach[i] != NULL) {
2844 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2845 if (utf8_filename) {
2846 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2847 gchar *tmp = g_strdup_printf("%s%s\n",
2848 warn_files?warn_files:"",
2854 g_free(utf8_filename);
2856 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2861 alertpanel_notice(ngettext(
2862 "The following file has been attached: \n%s",
2863 "The following files have been attached: \n%s", att), warn_files);
2868 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2881 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2883 static HeaderEntry hentry[] = {
2884 {"Reply-To:", NULL, TRUE },
2885 {"Cc:", NULL, TRUE },
2886 {"References:", NULL, FALSE },
2887 {"Bcc:", NULL, TRUE },
2888 {"Newsgroups:", NULL, TRUE },
2889 {"Followup-To:", NULL, TRUE },
2890 {"List-Post:", NULL, FALSE },
2891 {"X-Priority:", NULL, FALSE },
2892 {NULL, NULL, FALSE }
2909 cm_return_val_if_fail(msginfo != NULL, -1);
2911 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2912 procheader_get_header_fields(fp, hentry);
2915 if (hentry[H_REPLY_TO].body != NULL) {
2916 if (hentry[H_REPLY_TO].body[0] != '\0') {
2918 conv_unmime_header(hentry[H_REPLY_TO].body,
2921 g_free(hentry[H_REPLY_TO].body);
2922 hentry[H_REPLY_TO].body = NULL;
2924 if (hentry[H_CC].body != NULL) {
2925 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2926 g_free(hentry[H_CC].body);
2927 hentry[H_CC].body = NULL;
2929 if (hentry[H_REFERENCES].body != NULL) {
2930 if (compose->mode == COMPOSE_REEDIT)
2931 compose->references = hentry[H_REFERENCES].body;
2933 compose->references = compose_parse_references
2934 (hentry[H_REFERENCES].body, msginfo->msgid);
2935 g_free(hentry[H_REFERENCES].body);
2937 hentry[H_REFERENCES].body = NULL;
2939 if (hentry[H_BCC].body != NULL) {
2940 if (compose->mode == COMPOSE_REEDIT)
2942 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2943 g_free(hentry[H_BCC].body);
2944 hentry[H_BCC].body = NULL;
2946 if (hentry[H_NEWSGROUPS].body != NULL) {
2947 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2948 hentry[H_NEWSGROUPS].body = NULL;
2950 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2951 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2952 compose->followup_to =
2953 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2956 g_free(hentry[H_FOLLOWUP_TO].body);
2957 hentry[H_FOLLOWUP_TO].body = NULL;
2959 if (hentry[H_LIST_POST].body != NULL) {
2960 gchar *to = NULL, *start = NULL;
2962 extract_address(hentry[H_LIST_POST].body);
2963 if (hentry[H_LIST_POST].body[0] != '\0') {
2964 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2966 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2967 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2970 g_free(compose->ml_post);
2971 compose->ml_post = to;
2974 g_free(hentry[H_LIST_POST].body);
2975 hentry[H_LIST_POST].body = NULL;
2978 /* CLAWS - X-Priority */
2979 if (compose->mode == COMPOSE_REEDIT)
2980 if (hentry[H_X_PRIORITY].body != NULL) {
2983 priority = atoi(hentry[H_X_PRIORITY].body);
2984 g_free(hentry[H_X_PRIORITY].body);
2986 hentry[H_X_PRIORITY].body = NULL;
2988 if (priority < PRIORITY_HIGHEST ||
2989 priority > PRIORITY_LOWEST)
2990 priority = PRIORITY_NORMAL;
2992 compose->priority = priority;
2995 if (compose->mode == COMPOSE_REEDIT) {
2996 if (msginfo->inreplyto && *msginfo->inreplyto)
2997 compose->inreplyto = g_strdup(msginfo->inreplyto);
2999 if (msginfo->msgid && *msginfo->msgid &&
3000 compose->folder != NULL &&
3001 compose->folder->stype == F_DRAFT)
3002 compose->msgid = g_strdup(msginfo->msgid);
3004 if (msginfo->msgid && *msginfo->msgid)
3005 compose->inreplyto = g_strdup(msginfo->msgid);
3007 if (!compose->references) {
3008 if (msginfo->msgid && *msginfo->msgid) {
3009 if (msginfo->inreplyto && *msginfo->inreplyto)
3010 compose->references =
3011 g_strdup_printf("<%s>\n\t<%s>",
3015 compose->references =
3016 g_strconcat("<", msginfo->msgid, ">",
3018 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3019 compose->references =
3020 g_strconcat("<", msginfo->inreplyto, ">",
3029 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3034 cm_return_val_if_fail(msginfo != NULL, -1);
3036 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3037 procheader_get_header_fields(fp, entries);
3041 while (he != NULL && he->name != NULL) {
3043 GtkListStore *model = NULL;
3045 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3046 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3047 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3048 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3049 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3056 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3058 GSList *ref_id_list, *cur;
3062 ref_id_list = references_list_append(NULL, ref);
3063 if (!ref_id_list) return NULL;
3064 if (msgid && *msgid)
3065 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3070 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3071 /* "<" + Message-ID + ">" + CR+LF+TAB */
3072 len += strlen((gchar *)cur->data) + 5;
3074 if (len > MAX_REFERENCES_LEN) {
3075 /* remove second message-ID */
3076 if (ref_id_list && ref_id_list->next &&
3077 ref_id_list->next->next) {
3078 g_free(ref_id_list->next->data);
3079 ref_id_list = g_slist_remove
3080 (ref_id_list, ref_id_list->next->data);
3082 slist_free_strings_full(ref_id_list);
3089 new_ref = g_string_new("");
3090 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3091 if (new_ref->len > 0)
3092 g_string_append(new_ref, "\n\t");
3093 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3096 slist_free_strings_full(ref_id_list);
3098 new_ref_str = new_ref->str;
3099 g_string_free(new_ref, FALSE);
3104 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3105 const gchar *fmt, const gchar *qmark,
3106 const gchar *body, gboolean rewrap,
3107 gboolean need_unescape,
3108 const gchar *err_msg)
3110 MsgInfo* dummyinfo = NULL;
3111 gchar *quote_str = NULL;
3113 gboolean prev_autowrap;
3114 const gchar *trimmed_body = body;
3115 gint cursor_pos = -1;
3116 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3117 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3122 SIGNAL_BLOCK(buffer);
3125 dummyinfo = compose_msginfo_new_from_compose(compose);
3126 msginfo = dummyinfo;
3129 if (qmark != NULL) {
3131 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3132 compose->gtkaspell);
3134 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3136 quote_fmt_scan_string(qmark);
3139 buf = quote_fmt_get_buffer();
3142 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3144 Xstrdup_a(quote_str, buf, goto error)
3147 if (fmt && *fmt != '\0') {
3150 while (*trimmed_body == '\n')
3154 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3155 compose->gtkaspell);
3157 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3159 if (need_unescape) {
3162 /* decode \-escape sequences in the internal representation of the quote format */
3163 tmp = g_malloc(strlen(fmt)+1);
3164 pref_get_unescaped_pref(tmp, fmt);
3165 quote_fmt_scan_string(tmp);
3169 quote_fmt_scan_string(fmt);
3173 buf = quote_fmt_get_buffer();
3176 gint line = quote_fmt_get_line();
3177 alertpanel_error(err_msg, line);
3185 prev_autowrap = compose->autowrap;
3186 compose->autowrap = FALSE;
3188 mark = gtk_text_buffer_get_insert(buffer);
3189 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3190 if (g_utf8_validate(buf, -1, NULL)) {
3191 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3193 gchar *tmpout = NULL;
3194 tmpout = conv_codeset_strdup
3195 (buf, conv_get_locale_charset_str_no_utf8(),
3197 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3199 tmpout = g_malloc(strlen(buf)*2+1);
3200 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3202 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3206 cursor_pos = quote_fmt_get_cursor_pos();
3207 if (cursor_pos == -1)
3208 cursor_pos = gtk_text_iter_get_offset(&iter);
3209 compose->set_cursor_pos = cursor_pos;
3211 gtk_text_buffer_get_start_iter(buffer, &iter);
3212 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3213 gtk_text_buffer_place_cursor(buffer, &iter);
3215 compose->autowrap = prev_autowrap;
3216 if (compose->autowrap && rewrap)
3217 compose_wrap_all(compose);
3224 SIGNAL_UNBLOCK(buffer);
3226 procmsg_msginfo_free( &dummyinfo );
3231 /* if ml_post is of type addr@host and from is of type
3232 * addr-anything@host, return TRUE
3234 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3236 gchar *left_ml = NULL;
3237 gchar *right_ml = NULL;
3238 gchar *left_from = NULL;
3239 gchar *right_from = NULL;
3240 gboolean result = FALSE;
3242 if (!ml_post || !from)
3245 left_ml = g_strdup(ml_post);
3246 if (strstr(left_ml, "@")) {
3247 right_ml = strstr(left_ml, "@")+1;
3248 *(strstr(left_ml, "@")) = '\0';
3251 left_from = g_strdup(from);
3252 if (strstr(left_from, "@")) {
3253 right_from = strstr(left_from, "@")+1;
3254 *(strstr(left_from, "@")) = '\0';
3257 if (right_ml && right_from
3258 && !strncmp(left_from, left_ml, strlen(left_ml))
3259 && !strcmp(right_from, right_ml)) {
3268 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3269 gboolean respect_default_to)
3273 if (!folder || !folder->prefs)
3276 if (respect_default_to && folder->prefs->enable_default_to) {
3277 compose_entry_append(compose, folder->prefs->default_to,
3278 COMPOSE_TO, PREF_FOLDER);
3279 compose_entry_indicate(compose, folder->prefs->default_to);
3281 if (folder->prefs->enable_default_cc) {
3282 compose_entry_append(compose, folder->prefs->default_cc,
3283 COMPOSE_CC, PREF_FOLDER);
3284 compose_entry_indicate(compose, folder->prefs->default_cc);
3286 if (folder->prefs->enable_default_bcc) {
3287 compose_entry_append(compose, folder->prefs->default_bcc,
3288 COMPOSE_BCC, PREF_FOLDER);
3289 compose_entry_indicate(compose, folder->prefs->default_bcc);
3291 if (folder->prefs->enable_default_replyto) {
3292 compose_entry_append(compose, folder->prefs->default_replyto,
3293 COMPOSE_REPLYTO, PREF_FOLDER);
3294 compose_entry_indicate(compose, folder->prefs->default_replyto);
3298 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3303 if (!compose || !msginfo)
3306 if (msginfo->subject && *msginfo->subject) {
3307 buf = p = g_strdup(msginfo->subject);
3308 p += subject_get_prefix_length(p);
3309 memmove(buf, p, strlen(p) + 1);
3311 buf2 = g_strdup_printf("Re: %s", buf);
3312 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3317 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3320 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3321 gboolean to_all, gboolean to_ml,
3323 gboolean followup_and_reply_to)
3325 GSList *cc_list = NULL;
3328 gchar *replyto = NULL;
3329 gchar *ac_email = NULL;
3331 gboolean reply_to_ml = FALSE;
3332 gboolean default_reply_to = FALSE;
3334 cm_return_if_fail(compose->account != NULL);
3335 cm_return_if_fail(msginfo != NULL);
3337 reply_to_ml = to_ml && compose->ml_post;
3339 default_reply_to = msginfo->folder &&
3340 msginfo->folder->prefs->enable_default_reply_to;
3342 if (compose->account->protocol != A_NNTP) {
3343 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3345 if (reply_to_ml && !default_reply_to) {
3347 gboolean is_subscr = is_subscription(compose->ml_post,
3350 /* normal answer to ml post with a reply-to */
3351 compose_entry_append(compose,
3353 COMPOSE_TO, PREF_ML);
3354 if (compose->replyto)
3355 compose_entry_append(compose,
3357 COMPOSE_CC, PREF_ML);
3359 /* answer to subscription confirmation */
3360 if (compose->replyto)
3361 compose_entry_append(compose,
3363 COMPOSE_TO, PREF_ML);
3364 else if (msginfo->from)
3365 compose_entry_append(compose,
3367 COMPOSE_TO, PREF_ML);
3370 else if (!(to_all || to_sender) && default_reply_to) {
3371 compose_entry_append(compose,
3372 msginfo->folder->prefs->default_reply_to,
3373 COMPOSE_TO, PREF_FOLDER);
3374 compose_entry_indicate(compose,
3375 msginfo->folder->prefs->default_reply_to);
3381 compose_entry_append(compose, msginfo->from,
3382 COMPOSE_TO, PREF_NONE);
3384 Xstrdup_a(tmp1, msginfo->from, return);
3385 extract_address(tmp1);
3386 compose_entry_append(compose,
3387 (!account_find_from_address(tmp1, FALSE))
3390 COMPOSE_TO, PREF_NONE);
3391 if (compose->replyto)
3392 compose_entry_append(compose,
3394 COMPOSE_CC, PREF_NONE);
3396 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3397 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3398 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3399 if (compose->replyto) {
3400 compose_entry_append(compose,
3402 COMPOSE_TO, PREF_NONE);
3404 compose_entry_append(compose,
3405 msginfo->from ? msginfo->from : "",
3406 COMPOSE_TO, PREF_NONE);
3409 /* replying to own mail, use original recp */
3410 compose_entry_append(compose,
3411 msginfo->to ? msginfo->to : "",
3412 COMPOSE_TO, PREF_NONE);
3413 compose_entry_append(compose,
3414 msginfo->cc ? msginfo->cc : "",
3415 COMPOSE_CC, PREF_NONE);
3420 if (to_sender || (compose->followup_to &&
3421 !strncmp(compose->followup_to, "poster", 6)))
3422 compose_entry_append
3424 (compose->replyto ? compose->replyto :
3425 msginfo->from ? msginfo->from : ""),
3426 COMPOSE_TO, PREF_NONE);
3428 else if (followup_and_reply_to || to_all) {
3429 compose_entry_append
3431 (compose->replyto ? compose->replyto :
3432 msginfo->from ? msginfo->from : ""),
3433 COMPOSE_TO, PREF_NONE);
3435 compose_entry_append
3437 compose->followup_to ? compose->followup_to :
3438 compose->newsgroups ? compose->newsgroups : "",
3439 COMPOSE_NEWSGROUPS, PREF_NONE);
3441 compose_entry_append
3443 msginfo->cc ? msginfo->cc : "",
3444 COMPOSE_CC, PREF_NONE);
3447 compose_entry_append
3449 compose->followup_to ? compose->followup_to :
3450 compose->newsgroups ? compose->newsgroups : "",
3451 COMPOSE_NEWSGROUPS, PREF_NONE);
3453 compose_reply_set_subject(compose, msginfo);
3455 if (to_ml && compose->ml_post) return;
3456 if (!to_all || compose->account->protocol == A_NNTP) return;
3458 if (compose->replyto) {
3459 Xstrdup_a(replyto, compose->replyto, return);
3460 extract_address(replyto);
3462 if (msginfo->from) {
3463 Xstrdup_a(from, msginfo->from, return);
3464 extract_address(from);
3467 if (replyto && from)
3468 cc_list = address_list_append_with_comments(cc_list, from);
3469 if (to_all && msginfo->folder &&
3470 msginfo->folder->prefs->enable_default_reply_to)
3471 cc_list = address_list_append_with_comments(cc_list,
3472 msginfo->folder->prefs->default_reply_to);
3473 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3474 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3476 ac_email = g_utf8_strdown(compose->account->address, -1);
3479 for (cur = cc_list; cur != NULL; cur = cur->next) {
3480 gchar *addr = g_utf8_strdown(cur->data, -1);
3481 extract_address(addr);
3483 if (strcmp(ac_email, addr))
3484 compose_entry_append(compose, (gchar *)cur->data,
3485 COMPOSE_CC, PREF_NONE);
3487 debug_print("Cc address same as compose account's, ignoring\n");
3492 slist_free_strings_full(cc_list);
3498 #define SET_ENTRY(entry, str) \
3501 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3504 #define SET_ADDRESS(type, str) \
3507 compose_entry_append(compose, str, type, PREF_NONE); \
3510 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3512 cm_return_if_fail(msginfo != NULL);
3514 SET_ENTRY(subject_entry, msginfo->subject);
3515 SET_ENTRY(from_name, msginfo->from);
3516 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3517 SET_ADDRESS(COMPOSE_CC, compose->cc);
3518 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3519 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3520 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3521 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3523 compose_update_priority_menu_item(compose);
3524 compose_update_privacy_system_menu_item(compose, FALSE);
3525 compose_show_first_last_header(compose, TRUE);
3531 static void compose_insert_sig(Compose *compose, gboolean replace)
3533 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3534 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3536 GtkTextIter iter, iter_end;
3537 gint cur_pos, ins_pos;
3538 gboolean prev_autowrap;
3539 gboolean found = FALSE;
3540 gboolean exists = FALSE;
3542 cm_return_if_fail(compose->account != NULL);
3546 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3547 G_CALLBACK(compose_changed_cb),
3550 mark = gtk_text_buffer_get_insert(buffer);
3551 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3552 cur_pos = gtk_text_iter_get_offset (&iter);
3555 gtk_text_buffer_get_end_iter(buffer, &iter);
3557 exists = (compose->sig_str != NULL);
3560 GtkTextIter first_iter, start_iter, end_iter;
3562 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3564 if (!exists || compose->sig_str[0] == '\0')
3567 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3568 compose->signature_tag);
3571 /* include previous \n\n */
3572 gtk_text_iter_backward_chars(&first_iter, 1);
3573 start_iter = first_iter;
3574 end_iter = first_iter;
3576 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3577 compose->signature_tag);
3578 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3579 compose->signature_tag);
3581 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3587 g_free(compose->sig_str);
3588 compose->sig_str = account_get_signature_str(compose->account);
3590 cur_pos = gtk_text_iter_get_offset(&iter);
3592 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3593 g_free(compose->sig_str);
3594 compose->sig_str = NULL;
3596 if (compose->sig_inserted == FALSE)
3597 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3598 compose->sig_inserted = TRUE;
3600 cur_pos = gtk_text_iter_get_offset(&iter);
3601 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3603 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3604 gtk_text_iter_forward_chars(&iter, 1);
3605 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3606 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3608 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3609 cur_pos = gtk_text_buffer_get_char_count (buffer);
3612 /* put the cursor where it should be
3613 * either where the quote_fmt says, either where it was */
3614 if (compose->set_cursor_pos < 0)
3615 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3617 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3618 compose->set_cursor_pos);
3620 compose->set_cursor_pos = -1;
3621 gtk_text_buffer_place_cursor(buffer, &iter);
3622 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3623 G_CALLBACK(compose_changed_cb),
3629 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3632 GtkTextBuffer *buffer;
3635 const gchar *cur_encoding;
3636 gchar buf[BUFFSIZE];
3639 gboolean prev_autowrap;
3643 GError *error = NULL;
3649 GString *file_contents = NULL;
3650 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3652 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3654 /* get the size of the file we are about to insert */
3656 f = g_file_new_for_path(file);
3657 fi = g_file_query_info(f, "standard::size",
3658 G_FILE_QUERY_INFO_NONE, NULL, &error);
3660 if (error != NULL) {
3661 g_warning(error->message);
3663 g_error_free(error);
3667 ret = g_stat(file, &file_stat);
3670 gchar *shortfile = g_path_get_basename(file);
3671 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3673 return COMPOSE_INSERT_NO_FILE;
3674 } else if (prefs_common.warn_large_insert == TRUE) {
3676 size = g_file_info_get_size(fi);
3680 size = file_stat.st_size;
3683 /* ask user for confirmation if the file is large */
3684 if (prefs_common.warn_large_insert_size < 0 ||
3685 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3689 msg = g_strdup_printf(_("You are about to insert a file of %s "
3690 "in the message body. Are you sure you want to do that?"),
3691 to_human_readable(size));
3692 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3693 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3696 /* do we ask for confirmation next time? */
3697 if (aval & G_ALERTDISABLE) {
3698 /* no confirmation next time, disable feature in preferences */
3699 aval &= ~G_ALERTDISABLE;
3700 prefs_common.warn_large_insert = FALSE;
3703 /* abort file insertion if user canceled action */
3704 if (aval != G_ALERTALTERNATE) {
3705 return COMPOSE_INSERT_NO_FILE;
3711 if ((fp = g_fopen(file, "rb")) == NULL) {
3712 FILE_OP_ERROR(file, "fopen");
3713 return COMPOSE_INSERT_READ_ERROR;
3716 prev_autowrap = compose->autowrap;
3717 compose->autowrap = FALSE;
3719 text = GTK_TEXT_VIEW(compose->text);
3720 buffer = gtk_text_view_get_buffer(text);
3721 mark = gtk_text_buffer_get_insert(buffer);
3722 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3724 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3725 G_CALLBACK(text_inserted),
3728 cur_encoding = conv_get_locale_charset_str_no_utf8();
3730 file_contents = g_string_new("");
3731 while (fgets(buf, sizeof(buf), fp) != NULL) {
3734 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3735 str = g_strdup(buf);
3737 codeconv_set_strict(TRUE);
3738 str = conv_codeset_strdup
3739 (buf, cur_encoding, CS_INTERNAL);
3740 codeconv_set_strict(FALSE);
3743 result = COMPOSE_INSERT_INVALID_CHARACTER;
3749 /* strip <CR> if DOS/Windows file,
3750 replace <CR> with <LF> if Macintosh file. */
3753 if (len > 0 && str[len - 1] != '\n') {
3755 if (str[len] == '\r') str[len] = '\n';
3758 file_contents = g_string_append(file_contents, str);
3762 if (result == COMPOSE_INSERT_SUCCESS) {
3763 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3765 compose_changed_cb(NULL, compose);
3766 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3767 G_CALLBACK(text_inserted),
3769 compose->autowrap = prev_autowrap;
3770 if (compose->autowrap)
3771 compose_wrap_all(compose);
3774 g_string_free(file_contents, TRUE);
3780 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3781 const gchar *filename,
3782 const gchar *content_type,
3783 const gchar *charset)
3791 GtkListStore *store;
3793 gboolean has_binary = FALSE;
3795 if (!is_file_exist(file)) {
3796 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3797 gboolean result = FALSE;
3798 if (file_from_uri && is_file_exist(file_from_uri)) {
3799 result = compose_attach_append(
3800 compose, file_from_uri,
3801 filename, content_type,
3804 g_free(file_from_uri);
3807 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3810 if ((size = get_file_size(file)) < 0) {
3811 alertpanel_error("Can't get file size of %s\n", filename);
3815 /* In batch mode, we allow 0-length files to be attached no questions asked */
3816 if (size == 0 && !compose->batch) {
3817 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3818 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3819 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3820 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3823 if (aval != G_ALERTALTERNATE) {
3827 if ((fp = g_fopen(file, "rb")) == NULL) {
3828 alertpanel_error(_("Can't read %s."), filename);
3833 ainfo = g_new0(AttachInfo, 1);
3834 auto_ainfo = g_auto_pointer_new_with_free
3835 (ainfo, (GFreeFunc) compose_attach_info_free);
3836 ainfo->file = g_strdup(file);
3839 ainfo->content_type = g_strdup(content_type);
3840 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3842 MsgFlags flags = {0, 0};
3844 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3845 ainfo->encoding = ENC_7BIT;
3847 ainfo->encoding = ENC_8BIT;
3849 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3850 if (msginfo && msginfo->subject)
3851 name = g_strdup(msginfo->subject);
3853 name = g_path_get_basename(filename ? filename : file);
3855 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3857 procmsg_msginfo_free(&msginfo);
3859 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3860 ainfo->charset = g_strdup(charset);
3861 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3863 ainfo->encoding = ENC_BASE64;
3865 name = g_path_get_basename(filename ? filename : file);
3866 ainfo->name = g_strdup(name);
3870 ainfo->content_type = procmime_get_mime_type(file);
3871 if (!ainfo->content_type) {
3872 ainfo->content_type =
3873 g_strdup("application/octet-stream");
3874 ainfo->encoding = ENC_BASE64;
3875 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3877 procmime_get_encoding_for_text_file(file, &has_binary);
3879 ainfo->encoding = ENC_BASE64;
3880 name = g_path_get_basename(filename ? filename : file);
3881 ainfo->name = g_strdup(name);
3885 if (ainfo->name != NULL
3886 && !strcmp(ainfo->name, ".")) {
3887 g_free(ainfo->name);
3891 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3892 g_free(ainfo->content_type);
3893 ainfo->content_type = g_strdup("application/octet-stream");
3894 g_free(ainfo->charset);
3895 ainfo->charset = NULL;
3898 ainfo->size = (goffset)size;
3899 size_text = to_human_readable((goffset)size);
3901 store = GTK_LIST_STORE(gtk_tree_view_get_model
3902 (GTK_TREE_VIEW(compose->attach_clist)));
3904 gtk_list_store_append(store, &iter);
3905 gtk_list_store_set(store, &iter,
3906 COL_MIMETYPE, ainfo->content_type,
3907 COL_SIZE, size_text,
3908 COL_NAME, ainfo->name,
3909 COL_CHARSET, ainfo->charset,
3911 COL_AUTODATA, auto_ainfo,
3914 g_auto_pointer_free(auto_ainfo);
3915 compose_attach_update_label(compose);
3919 void compose_use_signing(Compose *compose, gboolean use_signing)
3921 compose->use_signing = use_signing;
3922 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3925 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3927 compose->use_encryption = use_encryption;
3928 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3931 #define NEXT_PART_NOT_CHILD(info) \
3933 node = info->node; \
3934 while (node->children) \
3935 node = g_node_last_child(node); \
3936 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3939 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3943 MimeInfo *firsttext = NULL;
3944 MimeInfo *encrypted = NULL;
3947 const gchar *partname = NULL;
3949 mimeinfo = procmime_scan_message(msginfo);
3950 if (!mimeinfo) return;
3952 if (mimeinfo->node->children == NULL) {
3953 procmime_mimeinfo_free_all(&mimeinfo);
3957 /* find first content part */
3958 child = (MimeInfo *) mimeinfo->node->children->data;
3959 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3960 child = (MimeInfo *)child->node->children->data;
3963 if (child->type == MIMETYPE_TEXT) {
3965 debug_print("First text part found\n");
3966 } else if (compose->mode == COMPOSE_REEDIT &&
3967 child->type == MIMETYPE_APPLICATION &&
3968 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3969 encrypted = (MimeInfo *)child->node->parent->data;
3972 child = (MimeInfo *) mimeinfo->node->children->data;
3973 while (child != NULL) {
3976 if (child == encrypted) {
3977 /* skip this part of tree */
3978 NEXT_PART_NOT_CHILD(child);
3982 if (child->type == MIMETYPE_MULTIPART) {
3983 /* get the actual content */
3984 child = procmime_mimeinfo_next(child);
3988 if (child == firsttext) {
3989 child = procmime_mimeinfo_next(child);
3993 outfile = procmime_get_tmp_file_name(child);
3994 if ((err = procmime_get_part(outfile, child)) < 0)
3995 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3997 gchar *content_type;
3999 content_type = procmime_get_content_type_str(child->type, child->subtype);
4001 /* if we meet a pgp signature, we don't attach it, but
4002 * we force signing. */
4003 if ((strcmp(content_type, "application/pgp-signature") &&
4004 strcmp(content_type, "application/pkcs7-signature") &&
4005 strcmp(content_type, "application/x-pkcs7-signature"))
4006 || compose->mode == COMPOSE_REDIRECT) {
4007 partname = procmime_mimeinfo_get_parameter(child, "filename");
4008 if (partname == NULL)
4009 partname = procmime_mimeinfo_get_parameter(child, "name");
4010 if (partname == NULL)
4012 compose_attach_append(compose, outfile,
4013 partname, content_type,
4014 procmime_mimeinfo_get_parameter(child, "charset"));
4016 compose_force_signing(compose, compose->account, NULL);
4018 g_free(content_type);
4021 NEXT_PART_NOT_CHILD(child);
4023 procmime_mimeinfo_free_all(&mimeinfo);
4026 #undef NEXT_PART_NOT_CHILD
4031 WAIT_FOR_INDENT_CHAR,
4032 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4035 /* return indent length, we allow:
4036 indent characters followed by indent characters or spaces/tabs,
4037 alphabets and numbers immediately followed by indent characters,
4038 and the repeating sequences of the above
4039 If quote ends with multiple spaces, only the first one is included. */
4040 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4041 const GtkTextIter *start, gint *len)
4043 GtkTextIter iter = *start;
4047 IndentState state = WAIT_FOR_INDENT_CHAR;
4050 gint alnum_count = 0;
4051 gint space_count = 0;
4054 if (prefs_common.quote_chars == NULL) {
4058 while (!gtk_text_iter_ends_line(&iter)) {
4059 wc = gtk_text_iter_get_char(&iter);
4060 if (g_unichar_iswide(wc))
4062 clen = g_unichar_to_utf8(wc, ch);
4066 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4067 is_space = g_unichar_isspace(wc);
4069 if (state == WAIT_FOR_INDENT_CHAR) {
4070 if (!is_indent && !g_unichar_isalnum(wc))
4073 quote_len += alnum_count + space_count + 1;
4074 alnum_count = space_count = 0;
4075 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4078 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4079 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4083 else if (is_indent) {
4084 quote_len += alnum_count + space_count + 1;
4085 alnum_count = space_count = 0;
4088 state = WAIT_FOR_INDENT_CHAR;
4092 gtk_text_iter_forward_char(&iter);
4095 if (quote_len > 0 && space_count > 0)
4101 if (quote_len > 0) {
4103 gtk_text_iter_forward_chars(&iter, quote_len);
4104 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4110 /* return >0 if the line is itemized */
4111 static int compose_itemized_length(GtkTextBuffer *buffer,
4112 const GtkTextIter *start)
4114 GtkTextIter iter = *start;
4119 if (gtk_text_iter_ends_line(&iter))
4124 wc = gtk_text_iter_get_char(&iter);
4125 if (!g_unichar_isspace(wc))
4127 gtk_text_iter_forward_char(&iter);
4128 if (gtk_text_iter_ends_line(&iter))
4132 clen = g_unichar_to_utf8(wc, ch);
4133 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4135 wc == 0x2022 || /* BULLET */
4136 wc == 0x2023 || /* TRIANGULAR BULLET */
4137 wc == 0x2043 || /* HYPHEN BULLET */
4138 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4139 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4140 wc == 0x2219 || /* BULLET OPERATOR */
4141 wc == 0x25d8 || /* INVERSE BULLET */
4142 wc == 0x25e6 || /* WHITE BULLET */
4143 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4144 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4145 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4146 wc == 0x29be || /* CIRCLED WHITE BULLET */
4147 wc == 0x29bf /* CIRCLED BULLET */
4151 gtk_text_iter_forward_char(&iter);
4152 if (gtk_text_iter_ends_line(&iter))
4154 wc = gtk_text_iter_get_char(&iter);
4155 if (g_unichar_isspace(wc)) {
4161 /* return the string at the start of the itemization */
4162 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4163 const GtkTextIter *start)
4165 GtkTextIter iter = *start;
4168 GString *item_chars = g_string_new("");
4171 if (gtk_text_iter_ends_line(&iter))
4176 wc = gtk_text_iter_get_char(&iter);
4177 if (!g_unichar_isspace(wc))
4179 gtk_text_iter_forward_char(&iter);
4180 if (gtk_text_iter_ends_line(&iter))
4182 g_string_append_unichar(item_chars, wc);
4185 str = item_chars->str;
4186 g_string_free(item_chars, FALSE);
4190 /* return the number of spaces at a line's start */
4191 static int compose_left_offset_length(GtkTextBuffer *buffer,
4192 const GtkTextIter *start)
4194 GtkTextIter iter = *start;
4197 if (gtk_text_iter_ends_line(&iter))
4201 wc = gtk_text_iter_get_char(&iter);
4202 if (!g_unichar_isspace(wc))
4205 gtk_text_iter_forward_char(&iter);
4206 if (gtk_text_iter_ends_line(&iter))
4210 gtk_text_iter_forward_char(&iter);
4211 if (gtk_text_iter_ends_line(&iter))
4216 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4217 const GtkTextIter *start,
4218 GtkTextIter *break_pos,
4222 GtkTextIter iter = *start, line_end = *start;
4223 PangoLogAttr *attrs;
4230 gboolean can_break = FALSE;
4231 gboolean do_break = FALSE;
4232 gboolean was_white = FALSE;
4233 gboolean prev_dont_break = FALSE;
4235 gtk_text_iter_forward_to_line_end(&line_end);
4236 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4237 len = g_utf8_strlen(str, -1);
4241 g_warning("compose_get_line_break_pos: len = 0!");
4245 /* g_print("breaking line: %d: %s (len = %d)\n",
4246 gtk_text_iter_get_line(&iter), str, len); */
4248 attrs = g_new(PangoLogAttr, len + 1);
4250 pango_default_break(str, -1, NULL, attrs, len + 1);
4254 /* skip quote and leading spaces */
4255 for (i = 0; *p != '\0' && i < len; i++) {
4258 wc = g_utf8_get_char(p);
4259 if (i >= quote_len && !g_unichar_isspace(wc))
4261 if (g_unichar_iswide(wc))
4263 else if (*p == '\t')
4267 p = g_utf8_next_char(p);
4270 for (; *p != '\0' && i < len; i++) {
4271 PangoLogAttr *attr = attrs + i;
4275 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4278 was_white = attr->is_white;
4280 /* don't wrap URI */
4281 if ((uri_len = get_uri_len(p)) > 0) {
4283 if (pos > 0 && col > max_col) {
4293 wc = g_utf8_get_char(p);
4294 if (g_unichar_iswide(wc)) {
4296 if (prev_dont_break && can_break && attr->is_line_break)
4298 } else if (*p == '\t')
4302 if (pos > 0 && col > max_col) {
4307 if (*p == '-' || *p == '/')
4308 prev_dont_break = TRUE;
4310 prev_dont_break = FALSE;
4312 p = g_utf8_next_char(p);
4316 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4321 *break_pos = *start;
4322 gtk_text_iter_set_line_offset(break_pos, pos);
4327 static gboolean compose_join_next_line(Compose *compose,
4328 GtkTextBuffer *buffer,
4330 const gchar *quote_str)
4332 GtkTextIter iter_ = *iter, cur, prev, next, end;
4333 PangoLogAttr attrs[3];
4335 gchar *next_quote_str;
4338 gboolean keep_cursor = FALSE;
4340 if (!gtk_text_iter_forward_line(&iter_) ||
4341 gtk_text_iter_ends_line(&iter_)) {
4344 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4346 if ((quote_str || next_quote_str) &&
4347 strcmp2(quote_str, next_quote_str) != 0) {
4348 g_free(next_quote_str);
4351 g_free(next_quote_str);
4354 if (quote_len > 0) {
4355 gtk_text_iter_forward_chars(&end, quote_len);
4356 if (gtk_text_iter_ends_line(&end)) {
4361 /* don't join itemized lines */
4362 if (compose_itemized_length(buffer, &end) > 0) {
4366 /* don't join signature separator */
4367 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4370 /* delete quote str */
4372 gtk_text_buffer_delete(buffer, &iter_, &end);
4374 /* don't join line breaks put by the user */
4376 gtk_text_iter_backward_char(&cur);
4377 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4378 gtk_text_iter_forward_char(&cur);
4382 gtk_text_iter_forward_char(&cur);
4383 /* delete linebreak and extra spaces */
4384 while (gtk_text_iter_backward_char(&cur)) {
4385 wc1 = gtk_text_iter_get_char(&cur);
4386 if (!g_unichar_isspace(wc1))
4391 while (!gtk_text_iter_ends_line(&cur)) {
4392 wc1 = gtk_text_iter_get_char(&cur);
4393 if (!g_unichar_isspace(wc1))
4395 gtk_text_iter_forward_char(&cur);
4398 if (!gtk_text_iter_equal(&prev, &next)) {
4401 mark = gtk_text_buffer_get_insert(buffer);
4402 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4403 if (gtk_text_iter_equal(&prev, &cur))
4405 gtk_text_buffer_delete(buffer, &prev, &next);
4409 /* insert space if required */
4410 gtk_text_iter_backward_char(&prev);
4411 wc1 = gtk_text_iter_get_char(&prev);
4412 wc2 = gtk_text_iter_get_char(&next);
4413 gtk_text_iter_forward_char(&next);
4414 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4415 pango_default_break(str, -1, NULL, attrs, 3);
4416 if (!attrs[1].is_line_break ||
4417 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4418 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4420 gtk_text_iter_backward_char(&iter_);
4421 gtk_text_buffer_place_cursor(buffer, &iter_);
4430 #define ADD_TXT_POS(bp_, ep_, pti_) \
4431 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4432 last = last->next; \
4433 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4434 last->next = NULL; \
4436 g_warning("alloc error scanning URIs"); \
4439 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4441 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4442 GtkTextBuffer *buffer;
4443 GtkTextIter iter, break_pos, end_of_line;
4444 gchar *quote_str = NULL;
4446 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4447 gboolean prev_autowrap = compose->autowrap;
4448 gint startq_offset = -1, noq_offset = -1;
4449 gint uri_start = -1, uri_stop = -1;
4450 gint nouri_start = -1, nouri_stop = -1;
4451 gint num_blocks = 0;
4452 gint quotelevel = -1;
4453 gboolean modified = force;
4454 gboolean removed = FALSE;
4455 gboolean modified_before_remove = FALSE;
4457 gboolean start = TRUE;
4458 gint itemized_len = 0, rem_item_len = 0;
4459 gchar *itemized_chars = NULL;
4460 gboolean item_continuation = FALSE;
4465 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4469 compose->autowrap = FALSE;
4471 buffer = gtk_text_view_get_buffer(text);
4472 undo_wrapping(compose->undostruct, TRUE);
4477 mark = gtk_text_buffer_get_insert(buffer);
4478 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4482 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4483 if (gtk_text_iter_ends_line(&iter)) {
4484 while (gtk_text_iter_ends_line(&iter) &&
4485 gtk_text_iter_forward_line(&iter))
4488 while (gtk_text_iter_backward_line(&iter)) {
4489 if (gtk_text_iter_ends_line(&iter)) {
4490 gtk_text_iter_forward_line(&iter);
4496 /* move to line start */
4497 gtk_text_iter_set_line_offset(&iter, 0);
4500 itemized_len = compose_itemized_length(buffer, &iter);
4502 if (!itemized_len) {
4503 itemized_len = compose_left_offset_length(buffer, &iter);
4504 item_continuation = TRUE;
4508 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4510 /* go until paragraph end (empty line) */
4511 while (start || !gtk_text_iter_ends_line(&iter)) {
4512 gchar *scanpos = NULL;
4513 /* parse table - in order of priority */
4515 const gchar *needle; /* token */
4517 /* token search function */
4518 gchar *(*search) (const gchar *haystack,
4519 const gchar *needle);
4520 /* part parsing function */
4521 gboolean (*parse) (const gchar *start,
4522 const gchar *scanpos,
4526 /* part to URI function */
4527 gchar *(*build_uri) (const gchar *bp,
4531 static struct table parser[] = {
4532 {"http://", strcasestr, get_uri_part, make_uri_string},
4533 {"https://", strcasestr, get_uri_part, make_uri_string},
4534 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4535 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4536 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4537 {"www.", strcasestr, get_uri_part, make_http_string},
4538 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4539 {"@", strcasestr, get_email_part, make_email_string}
4541 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4542 gint last_index = PARSE_ELEMS;
4544 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4548 if (!prev_autowrap && num_blocks == 0) {
4550 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4551 G_CALLBACK(text_inserted),
4554 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4557 uri_start = uri_stop = -1;
4559 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4562 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4563 if (startq_offset == -1)
4564 startq_offset = gtk_text_iter_get_offset(&iter);
4565 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4566 if (quotelevel > 2) {
4567 /* recycle colors */
4568 if (prefs_common.recycle_quote_colors)
4577 if (startq_offset == -1)
4578 noq_offset = gtk_text_iter_get_offset(&iter);
4582 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4585 if (gtk_text_iter_ends_line(&iter)) {
4587 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4588 prefs_common.linewrap_len,
4590 GtkTextIter prev, next, cur;
4591 if (prev_autowrap != FALSE || force) {
4592 compose->automatic_break = TRUE;
4594 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4595 compose->automatic_break = FALSE;
4596 if (itemized_len && compose->autoindent) {
4597 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4598 if (!item_continuation)
4599 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4601 } else if (quote_str && wrap_quote) {
4602 compose->automatic_break = TRUE;
4604 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4605 compose->automatic_break = FALSE;
4606 if (itemized_len && compose->autoindent) {
4607 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4608 if (!item_continuation)
4609 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4613 /* remove trailing spaces */
4615 rem_item_len = itemized_len;
4616 while (compose->autoindent && rem_item_len-- > 0)
4617 gtk_text_iter_backward_char(&cur);
4618 gtk_text_iter_backward_char(&cur);
4621 while (!gtk_text_iter_starts_line(&cur)) {
4624 gtk_text_iter_backward_char(&cur);
4625 wc = gtk_text_iter_get_char(&cur);
4626 if (!g_unichar_isspace(wc))
4630 if (!gtk_text_iter_equal(&prev, &next)) {
4631 gtk_text_buffer_delete(buffer, &prev, &next);
4633 gtk_text_iter_forward_char(&break_pos);
4637 gtk_text_buffer_insert(buffer, &break_pos,
4641 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4643 /* move iter to current line start */
4644 gtk_text_iter_set_line_offset(&iter, 0);
4651 /* move iter to next line start */
4657 if (!prev_autowrap && num_blocks > 0) {
4659 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4660 G_CALLBACK(text_inserted),
4664 while (!gtk_text_iter_ends_line(&end_of_line)) {
4665 gtk_text_iter_forward_char(&end_of_line);
4667 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4669 nouri_start = gtk_text_iter_get_offset(&iter);
4670 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4672 walk_pos = gtk_text_iter_get_offset(&iter);
4673 /* FIXME: this looks phony. scanning for anything in the parse table */
4674 for (n = 0; n < PARSE_ELEMS; n++) {
4677 tmp = parser[n].search(walk, parser[n].needle);
4679 if (scanpos == NULL || tmp < scanpos) {
4688 /* check if URI can be parsed */
4689 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4690 (const gchar **)&ep, FALSE)
4691 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4695 strlen(parser[last_index].needle);
4698 uri_start = walk_pos + (bp - o_walk);
4699 uri_stop = walk_pos + (ep - o_walk);
4703 gtk_text_iter_forward_line(&iter);
4706 if (startq_offset != -1) {
4707 GtkTextIter startquote, endquote;
4708 gtk_text_buffer_get_iter_at_offset(
4709 buffer, &startquote, startq_offset);
4712 switch (quotelevel) {
4714 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4715 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4716 gtk_text_buffer_apply_tag_by_name(
4717 buffer, "quote0", &startquote, &endquote);
4718 gtk_text_buffer_remove_tag_by_name(
4719 buffer, "quote1", &startquote, &endquote);
4720 gtk_text_buffer_remove_tag_by_name(
4721 buffer, "quote2", &startquote, &endquote);
4726 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4727 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4728 gtk_text_buffer_apply_tag_by_name(
4729 buffer, "quote1", &startquote, &endquote);
4730 gtk_text_buffer_remove_tag_by_name(
4731 buffer, "quote0", &startquote, &endquote);
4732 gtk_text_buffer_remove_tag_by_name(
4733 buffer, "quote2", &startquote, &endquote);
4738 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4739 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4740 gtk_text_buffer_apply_tag_by_name(
4741 buffer, "quote2", &startquote, &endquote);
4742 gtk_text_buffer_remove_tag_by_name(
4743 buffer, "quote0", &startquote, &endquote);
4744 gtk_text_buffer_remove_tag_by_name(
4745 buffer, "quote1", &startquote, &endquote);
4751 } else if (noq_offset != -1) {
4752 GtkTextIter startnoquote, endnoquote;
4753 gtk_text_buffer_get_iter_at_offset(
4754 buffer, &startnoquote, noq_offset);
4757 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4758 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4759 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4760 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4761 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4762 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4763 gtk_text_buffer_remove_tag_by_name(
4764 buffer, "quote0", &startnoquote, &endnoquote);
4765 gtk_text_buffer_remove_tag_by_name(
4766 buffer, "quote1", &startnoquote, &endnoquote);
4767 gtk_text_buffer_remove_tag_by_name(
4768 buffer, "quote2", &startnoquote, &endnoquote);
4774 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4775 GtkTextIter nouri_start_iter, nouri_end_iter;
4776 gtk_text_buffer_get_iter_at_offset(
4777 buffer, &nouri_start_iter, nouri_start);
4778 gtk_text_buffer_get_iter_at_offset(
4779 buffer, &nouri_end_iter, nouri_stop);
4780 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4781 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4782 gtk_text_buffer_remove_tag_by_name(
4783 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4784 modified_before_remove = modified;
4789 if (uri_start >= 0 && uri_stop > 0) {
4790 GtkTextIter uri_start_iter, uri_end_iter, back;
4791 gtk_text_buffer_get_iter_at_offset(
4792 buffer, &uri_start_iter, uri_start);
4793 gtk_text_buffer_get_iter_at_offset(
4794 buffer, &uri_end_iter, uri_stop);
4795 back = uri_end_iter;
4796 gtk_text_iter_backward_char(&back);
4797 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4798 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4799 gtk_text_buffer_apply_tag_by_name(
4800 buffer, "link", &uri_start_iter, &uri_end_iter);
4802 if (removed && !modified_before_remove) {
4808 /* debug_print("not modified, out after %d lines\n", lines); */
4812 /* debug_print("modified, out after %d lines\n", lines); */
4814 g_free(itemized_chars);
4817 undo_wrapping(compose->undostruct, FALSE);
4818 compose->autowrap = prev_autowrap;
4823 void compose_action_cb(void *data)
4825 Compose *compose = (Compose *)data;
4826 compose_wrap_all(compose);
4829 static void compose_wrap_all(Compose *compose)
4831 compose_wrap_all_full(compose, FALSE);
4834 static void compose_wrap_all_full(Compose *compose, gboolean force)
4836 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4837 GtkTextBuffer *buffer;
4839 gboolean modified = TRUE;
4841 buffer = gtk_text_view_get_buffer(text);
4843 gtk_text_buffer_get_start_iter(buffer, &iter);
4845 undo_wrapping(compose->undostruct, TRUE);
4847 while (!gtk_text_iter_is_end(&iter) && modified)
4848 modified = compose_beautify_paragraph(compose, &iter, force);
4850 undo_wrapping(compose->undostruct, FALSE);
4854 static void compose_set_title(Compose *compose)
4860 edited = compose->modified ? _(" [Edited]") : "";
4862 subject = gtk_editable_get_chars(
4863 GTK_EDITABLE(compose->subject_entry), 0, -1);
4865 #ifndef GENERIC_UMPC
4866 if (subject && strlen(subject))
4867 str = g_strdup_printf(_("%s - Compose message%s"),
4870 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4872 str = g_strdup(_("Compose message"));
4875 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4881 * compose_current_mail_account:
4883 * Find a current mail account (the currently selected account, or the
4884 * default account, if a news account is currently selected). If a
4885 * mail account cannot be found, display an error message.
4887 * Return value: Mail account, or NULL if not found.
4889 static PrefsAccount *
4890 compose_current_mail_account(void)
4894 if (cur_account && cur_account->protocol != A_NNTP)
4897 ac = account_get_default();
4898 if (!ac || ac->protocol == A_NNTP) {
4899 alertpanel_error(_("Account for sending mail is not specified.\n"
4900 "Please select a mail account before sending."));
4907 #define QUOTE_IF_REQUIRED(out, str) \
4909 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4913 len = strlen(str) + 3; \
4914 if ((__tmp = alloca(len)) == NULL) { \
4915 g_warning("can't allocate memory"); \
4916 g_string_free(header, TRUE); \
4919 g_snprintf(__tmp, len, "\"%s\"", str); \
4924 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4925 g_warning("can't allocate memory"); \
4926 g_string_free(header, TRUE); \
4929 strcpy(__tmp, str); \
4935 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4937 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4941 len = strlen(str) + 3; \
4942 if ((__tmp = alloca(len)) == NULL) { \
4943 g_warning("can't allocate memory"); \
4946 g_snprintf(__tmp, len, "\"%s\"", str); \
4951 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4952 g_warning("can't allocate memory"); \
4955 strcpy(__tmp, str); \
4961 static void compose_select_account(Compose *compose, PrefsAccount *account,
4964 gchar *from = NULL, *header = NULL;
4965 ComposeHeaderEntry *header_entry;
4966 #if GTK_CHECK_VERSION(2, 24, 0)
4970 cm_return_if_fail(account != NULL);
4972 compose->account = account;
4973 if (account->name && *account->name) {
4975 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4976 qbuf = escape_internal_quotes(buf, '"');
4977 from = g_strdup_printf("%s <%s>",
4978 qbuf, account->address);
4981 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4983 from = g_strdup_printf("<%s>",
4985 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4990 compose_set_title(compose);
4992 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4993 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4995 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4996 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4997 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4999 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5001 activate_privacy_system(compose, account, FALSE);
5003 if (!init && compose->mode != COMPOSE_REDIRECT) {
5004 undo_block(compose->undostruct);
5005 compose_insert_sig(compose, TRUE);
5006 undo_unblock(compose->undostruct);
5009 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5010 #if !GTK_CHECK_VERSION(2, 24, 0)
5011 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
5013 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5014 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5015 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5018 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5019 if (account->protocol == A_NNTP) {
5020 if (!strcmp(header, _("To:")))
5021 combobox_select_by_text(
5022 GTK_COMBO_BOX(header_entry->combo),
5025 if (!strcmp(header, _("Newsgroups:")))
5026 combobox_select_by_text(
5027 GTK_COMBO_BOX(header_entry->combo),
5035 /* use account's dict info if set */
5036 if (compose->gtkaspell) {
5037 if (account->enable_default_dictionary)
5038 gtkaspell_change_dict(compose->gtkaspell,
5039 account->default_dictionary, FALSE);
5040 if (account->enable_default_alt_dictionary)
5041 gtkaspell_change_alt_dict(compose->gtkaspell,
5042 account->default_alt_dictionary);
5043 if (account->enable_default_dictionary
5044 || account->enable_default_alt_dictionary)
5045 compose_spell_menu_changed(compose);
5050 gboolean compose_check_for_valid_recipient(Compose *compose) {
5051 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5052 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5053 gboolean recipient_found = FALSE;
5057 /* free to and newsgroup list */
5058 slist_free_strings_full(compose->to_list);
5059 compose->to_list = NULL;
5061 slist_free_strings_full(compose->newsgroup_list);
5062 compose->newsgroup_list = NULL;
5064 /* search header entries for to and newsgroup entries */
5065 for (list = compose->header_list; list; list = list->next) {
5068 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5069 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5072 if (entry[0] != '\0') {
5073 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5074 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5075 compose->to_list = address_list_append(compose->to_list, entry);
5076 recipient_found = TRUE;
5079 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5080 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5081 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5082 recipient_found = TRUE;
5089 return recipient_found;
5092 static gboolean compose_check_for_set_recipients(Compose *compose)
5094 if (compose->account->set_autocc && compose->account->auto_cc) {
5095 gboolean found_other = FALSE;
5097 /* search header entries for to and newsgroup entries */
5098 for (list = compose->header_list; list; list = list->next) {
5101 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5102 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5105 if (strcmp(entry, compose->account->auto_cc)
5106 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5117 if (compose->batch) {
5118 gtk_widget_show_all(compose->window);
5120 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5121 prefs_common_translated_header_name("Cc"));
5122 aval = alertpanel(_("Send"),
5124 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5126 if (aval != G_ALERTALTERNATE)
5130 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5131 gboolean found_other = FALSE;
5133 /* search header entries for to and newsgroup entries */
5134 for (list = compose->header_list; list; list = list->next) {
5137 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5138 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5141 if (strcmp(entry, compose->account->auto_bcc)
5142 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5154 if (compose->batch) {
5155 gtk_widget_show_all(compose->window);
5157 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5158 prefs_common_translated_header_name("Bcc"));
5159 aval = alertpanel(_("Send"),
5161 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5163 if (aval != G_ALERTALTERNATE)
5170 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5174 if (compose_check_for_valid_recipient(compose) == FALSE) {
5175 if (compose->batch) {
5176 gtk_widget_show_all(compose->window);
5178 alertpanel_error(_("Recipient is not specified."));
5182 if (compose_check_for_set_recipients(compose) == FALSE) {
5186 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5187 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5188 if (*str == '\0' && check_everything == TRUE &&
5189 compose->mode != COMPOSE_REDIRECT) {
5191 gchar *button_label;
5194 if (compose->sending)
5195 button_label = g_strconcat("+", _("_Send"), NULL);
5197 button_label = g_strconcat("+", _("_Queue"), NULL);
5198 message = g_strdup_printf(_("Subject is empty. %s"),
5199 compose->sending?_("Send it anyway?"):
5200 _("Queue it anyway?"));
5202 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5203 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5204 ALERT_QUESTION, G_ALERTDEFAULT);
5206 g_free(button_label);
5207 if (aval & G_ALERTDISABLE) {
5208 aval &= ~G_ALERTDISABLE;
5209 prefs_common.warn_empty_subj = FALSE;
5211 if (aval != G_ALERTALTERNATE)
5216 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5217 && check_everything == TRUE) {
5221 /* count To and Cc recipients */
5222 for (list = compose->header_list; list; list = list->next) {
5226 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5227 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5230 if ((entry[0] != '\0') &&
5231 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5232 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5238 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5240 gchar *button_label;
5243 if (compose->sending)
5244 button_label = g_strconcat("+", _("_Send"), NULL);
5246 button_label = g_strconcat("+", _("_Queue"), NULL);
5247 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5248 compose->sending?_("Send it anyway?"):
5249 _("Queue it anyway?"));
5251 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5252 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5253 ALERT_QUESTION, G_ALERTDEFAULT);
5255 if (aval & G_ALERTDISABLE) {
5256 aval &= ~G_ALERTDISABLE;
5257 prefs_common.warn_sending_many_recipients_num = 0;
5259 if (aval != G_ALERTALTERNATE)
5264 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5270 gint compose_send(Compose *compose)
5273 FolderItem *folder = NULL;
5275 gchar *msgpath = NULL;
5276 gboolean discard_window = FALSE;
5277 gchar *errstr = NULL;
5278 gchar *tmsgid = NULL;
5279 MainWindow *mainwin = mainwindow_get_mainwindow();
5280 gboolean queued_removed = FALSE;
5282 if (prefs_common.send_dialog_invisible
5283 || compose->batch == TRUE)
5284 discard_window = TRUE;
5286 compose_allow_user_actions (compose, FALSE);
5287 compose->sending = TRUE;
5289 if (compose_check_entries(compose, TRUE) == FALSE) {
5290 if (compose->batch) {
5291 gtk_widget_show_all(compose->window);
5297 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5300 if (compose->batch) {
5301 gtk_widget_show_all(compose->window);
5304 alertpanel_error(_("Could not queue message for sending:\n\n"
5305 "Charset conversion failed."));
5306 } else if (val == -5) {
5307 alertpanel_error(_("Could not queue message for sending:\n\n"
5308 "Couldn't get recipient encryption key."));
5309 } else if (val == -6) {
5311 } else if (val == -3) {
5312 if (privacy_peek_error())
5313 alertpanel_error(_("Could not queue message for sending:\n\n"
5314 "Signature failed: %s"), privacy_get_error());
5315 } else if (val == -2 && errno != 0) {
5316 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5318 alertpanel_error(_("Could not queue message for sending."));
5323 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5324 if (discard_window) {
5325 compose->sending = FALSE;
5326 compose_close(compose);
5327 /* No more compose access in the normal codepath
5328 * after this point! */
5333 alertpanel_error(_("The message was queued but could not be "
5334 "sent.\nUse \"Send queued messages\" from "
5335 "the main window to retry."));
5336 if (!discard_window) {
5343 if (msgpath == NULL) {
5344 msgpath = folder_item_fetch_msg(folder, msgnum);
5345 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5348 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5349 claws_unlink(msgpath);
5352 if (!discard_window) {
5354 if (!queued_removed)
5355 folder_item_remove_msg(folder, msgnum);
5356 folder_item_scan(folder);
5358 /* make sure we delete that */
5359 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5361 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5362 folder_item_remove_msg(folder, tmp->msgnum);
5363 procmsg_msginfo_free(&tmp);
5370 if (!queued_removed)
5371 folder_item_remove_msg(folder, msgnum);
5372 folder_item_scan(folder);
5374 /* make sure we delete that */
5375 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5377 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5378 folder_item_remove_msg(folder, tmp->msgnum);
5379 procmsg_msginfo_free(&tmp);
5382 if (!discard_window) {
5383 compose->sending = FALSE;
5384 compose_allow_user_actions (compose, TRUE);
5385 compose_close(compose);
5389 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5390 "the main window to retry."), errstr);
5393 alertpanel_error_log(_("The message was queued but could not be "
5394 "sent.\nUse \"Send queued messages\" from "
5395 "the main window to retry."));
5397 if (!discard_window) {
5406 toolbar_main_set_sensitive(mainwin);
5407 main_window_set_menu_sensitive(mainwin);
5413 compose_allow_user_actions (compose, TRUE);
5414 compose->sending = FALSE;
5415 compose->modified = TRUE;
5416 toolbar_main_set_sensitive(mainwin);
5417 main_window_set_menu_sensitive(mainwin);
5422 static gboolean compose_use_attach(Compose *compose)
5424 GtkTreeModel *model = gtk_tree_view_get_model
5425 (GTK_TREE_VIEW(compose->attach_clist));
5426 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5429 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5432 gchar buf[BUFFSIZE];
5434 gboolean first_to_address;
5435 gboolean first_cc_address;
5437 ComposeHeaderEntry *headerentry;
5438 const gchar *headerentryname;
5439 const gchar *cc_hdr;
5440 const gchar *to_hdr;
5441 gboolean err = FALSE;
5443 debug_print("Writing redirect header\n");
5445 cc_hdr = prefs_common_translated_header_name("Cc:");
5446 to_hdr = prefs_common_translated_header_name("To:");
5448 first_to_address = TRUE;
5449 for (list = compose->header_list; list; list = list->next) {
5450 headerentry = ((ComposeHeaderEntry *)list->data);
5451 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5453 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5454 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5455 Xstrdup_a(str, entstr, return -1);
5457 if (str[0] != '\0') {
5458 compose_convert_header
5459 (compose, buf, sizeof(buf), str,
5460 strlen("Resent-To") + 2, TRUE);
5462 if (first_to_address) {
5463 err |= (fprintf(fp, "Resent-To: ") < 0);
5464 first_to_address = FALSE;
5466 err |= (fprintf(fp, ",") < 0);
5468 err |= (fprintf(fp, "%s", buf) < 0);
5472 if (!first_to_address) {
5473 err |= (fprintf(fp, "\n") < 0);
5476 first_cc_address = TRUE;
5477 for (list = compose->header_list; list; list = list->next) {
5478 headerentry = ((ComposeHeaderEntry *)list->data);
5479 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5481 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5482 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5483 Xstrdup_a(str, strg, return -1);
5485 if (str[0] != '\0') {
5486 compose_convert_header
5487 (compose, buf, sizeof(buf), str,
5488 strlen("Resent-Cc") + 2, TRUE);
5490 if (first_cc_address) {
5491 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5492 first_cc_address = FALSE;
5494 err |= (fprintf(fp, ",") < 0);
5496 err |= (fprintf(fp, "%s", buf) < 0);
5500 if (!first_cc_address) {
5501 err |= (fprintf(fp, "\n") < 0);
5504 return (err ? -1:0);
5507 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5509 gchar date[RFC822_DATE_BUFFSIZE];
5510 gchar buf[BUFFSIZE];
5512 const gchar *entstr;
5513 /* struct utsname utsbuf; */
5514 gboolean err = FALSE;
5516 cm_return_val_if_fail(fp != NULL, -1);
5517 cm_return_val_if_fail(compose->account != NULL, -1);
5518 cm_return_val_if_fail(compose->account->address != NULL, -1);
5521 if (prefs_common.hide_timezone)
5522 get_rfc822_date_hide_tz(date, sizeof(date));
5524 get_rfc822_date(date, sizeof(date));
5525 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5528 if (compose->account->name && *compose->account->name) {
5529 compose_convert_header
5530 (compose, buf, sizeof(buf), compose->account->name,
5531 strlen("From: "), TRUE);
5532 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5533 buf, compose->account->address) < 0);
5535 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5538 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5539 if (*entstr != '\0') {
5540 Xstrdup_a(str, entstr, return -1);
5543 compose_convert_header(compose, buf, sizeof(buf), str,
5544 strlen("Subject: "), FALSE);
5545 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5549 /* Resent-Message-ID */
5550 if (compose->account->gen_msgid) {
5551 gchar *addr = prefs_account_generate_msgid(compose->account);
5552 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5554 g_free(compose->msgid);
5555 compose->msgid = addr;
5557 compose->msgid = NULL;
5560 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5563 /* separator between header and body */
5564 err |= (fputs("\n", fp) == EOF);
5566 return (err ? -1:0);
5569 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5574 gchar rewrite_buf[BUFFSIZE];
5576 gboolean skip = FALSE;
5577 gboolean err = FALSE;
5578 gchar *not_included[]={
5579 "Return-Path:", "Delivered-To:", "Received:",
5580 "Subject:", "X-UIDL:", "AF:",
5581 "NF:", "PS:", "SRH:",
5582 "SFN:", "DSR:", "MID:",
5583 "CFG:", "PT:", "S:",
5584 "RQ:", "SSV:", "NSV:",
5585 "SSH:", "R:", "MAID:",
5586 "NAID:", "RMID:", "FMID:",
5587 "SCF:", "RRCPT:", "NG:",
5588 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5589 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5590 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5591 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5592 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5597 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5598 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5602 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5604 for (i = 0; not_included[i] != NULL; i++) {
5605 if (g_ascii_strncasecmp(buf, not_included[i],
5606 strlen(not_included[i])) == 0) {
5616 if (fputs(buf, fdest) == -1) {
5622 if (!prefs_common.redirect_keep_from) {
5623 if (g_ascii_strncasecmp(buf, "From:",
5624 strlen("From:")) == 0) {
5625 err |= (fputs(" (by way of ", fdest) == EOF);
5626 if (compose->account->name
5627 && *compose->account->name) {
5628 gchar buffer[BUFFSIZE];
5630 compose_convert_header
5631 (compose, buffer, sizeof(buffer),
5632 compose->account->name,
5635 err |= (fprintf(fdest, "%s <%s>",
5637 compose->account->address) < 0);
5639 err |= (fprintf(fdest, "%s",
5640 compose->account->address) < 0);
5641 err |= (fputs(")", fdest) == EOF);
5647 if (fputs("\n", fdest) == -1)
5654 if (compose_redirect_write_headers(compose, fdest))
5657 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5658 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5672 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5674 GtkTextBuffer *buffer;
5675 GtkTextIter start, end, tmp;
5676 gchar *chars, *tmp_enc_file, *content;
5678 const gchar *out_codeset;
5679 EncodingType encoding = ENC_UNKNOWN;
5680 MimeInfo *mimemsg, *mimetext;
5682 const gchar *src_codeset = CS_INTERNAL;
5683 gchar *from_addr = NULL;
5684 gchar *from_name = NULL;
5687 if (action == COMPOSE_WRITE_FOR_SEND) {
5688 attach_parts = TRUE;
5690 /* We're sending the message, generate a Message-ID
5692 if (compose->msgid == NULL &&
5693 compose->account->gen_msgid) {
5694 compose->msgid = prefs_account_generate_msgid(compose->account);
5698 /* create message MimeInfo */
5699 mimemsg = procmime_mimeinfo_new();
5700 mimemsg->type = MIMETYPE_MESSAGE;
5701 mimemsg->subtype = g_strdup("rfc822");
5702 mimemsg->content = MIMECONTENT_MEM;
5703 mimemsg->tmp = TRUE; /* must free content later */
5704 mimemsg->data.mem = compose_get_header(compose);
5706 /* Create text part MimeInfo */
5707 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5708 gtk_text_buffer_get_end_iter(buffer, &end);
5711 /* We make sure that there is a newline at the end. */
5712 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5713 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5714 if (*chars != '\n') {
5715 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5720 /* get all composed text */
5721 gtk_text_buffer_get_start_iter(buffer, &start);
5722 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5724 out_codeset = conv_get_charset_str(compose->out_encoding);
5726 if (!out_codeset && is_ascii_str(chars)) {
5727 out_codeset = CS_US_ASCII;
5728 } else if (prefs_common.outgoing_fallback_to_ascii &&
5729 is_ascii_str(chars)) {
5730 out_codeset = CS_US_ASCII;
5731 encoding = ENC_7BIT;
5735 gchar *test_conv_global_out = NULL;
5736 gchar *test_conv_reply = NULL;
5738 /* automatic mode. be automatic. */
5739 codeconv_set_strict(TRUE);
5741 out_codeset = conv_get_outgoing_charset_str();
5743 debug_print("trying to convert to %s\n", out_codeset);
5744 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5747 if (!test_conv_global_out && compose->orig_charset
5748 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5749 out_codeset = compose->orig_charset;
5750 debug_print("failure; trying to convert to %s\n", out_codeset);
5751 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5754 if (!test_conv_global_out && !test_conv_reply) {
5756 out_codeset = CS_INTERNAL;
5757 debug_print("failure; finally using %s\n", out_codeset);
5759 g_free(test_conv_global_out);
5760 g_free(test_conv_reply);
5761 codeconv_set_strict(FALSE);
5764 if (encoding == ENC_UNKNOWN) {
5765 if (prefs_common.encoding_method == CTE_BASE64)
5766 encoding = ENC_BASE64;
5767 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5768 encoding = ENC_QUOTED_PRINTABLE;
5769 else if (prefs_common.encoding_method == CTE_8BIT)
5770 encoding = ENC_8BIT;
5772 encoding = procmime_get_encoding_for_charset(out_codeset);
5775 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5776 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5778 if (action == COMPOSE_WRITE_FOR_SEND) {
5779 codeconv_set_strict(TRUE);
5780 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5781 codeconv_set_strict(FALSE);
5786 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5787 "to the specified %s charset.\n"
5788 "Send it as %s?"), out_codeset, src_codeset);
5789 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5790 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5791 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5794 if (aval != G_ALERTALTERNATE) {
5799 out_codeset = src_codeset;
5805 out_codeset = src_codeset;
5810 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5811 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5812 strstr(buf, "\nFrom ") != NULL) {
5813 encoding = ENC_QUOTED_PRINTABLE;
5817 mimetext = procmime_mimeinfo_new();
5818 mimetext->content = MIMECONTENT_MEM;
5819 mimetext->tmp = TRUE; /* must free content later */
5820 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5821 * and free the data, which we need later. */
5822 mimetext->data.mem = g_strdup(buf);
5823 mimetext->type = MIMETYPE_TEXT;
5824 mimetext->subtype = g_strdup("plain");
5825 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5826 g_strdup(out_codeset));
5828 /* protect trailing spaces when signing message */
5829 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5830 privacy_system_can_sign(compose->privacy_system)) {
5831 encoding = ENC_QUOTED_PRINTABLE;
5835 debug_print("main text: %Id bytes encoded as %s in %d\n",
5837 debug_print("main text: %zd bytes encoded as %s in %d\n",
5839 strlen(buf), out_codeset, encoding);
5841 /* check for line length limit */
5842 if (action == COMPOSE_WRITE_FOR_SEND &&
5843 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5844 check_line_length(buf, 1000, &line) < 0) {
5847 msg = g_strdup_printf
5848 (_("Line %d exceeds the line length limit (998 bytes).\n"
5849 "The contents of the message might be broken on the way to the delivery.\n"
5851 "Send it anyway?"), line + 1);
5852 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5854 if (aval != G_ALERTALTERNATE) {
5860 if (encoding != ENC_UNKNOWN)
5861 procmime_encode_content(mimetext, encoding);
5863 /* append attachment parts */
5864 if (compose_use_attach(compose) && attach_parts) {
5865 MimeInfo *mimempart;
5866 gchar *boundary = NULL;
5867 mimempart = procmime_mimeinfo_new();
5868 mimempart->content = MIMECONTENT_EMPTY;
5869 mimempart->type = MIMETYPE_MULTIPART;
5870 mimempart->subtype = g_strdup("mixed");
5874 boundary = generate_mime_boundary(NULL);
5875 } while (strstr(buf, boundary) != NULL);
5877 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5880 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5882 g_node_append(mimempart->node, mimetext->node);
5883 g_node_append(mimemsg->node, mimempart->node);
5885 if (compose_add_attachments(compose, mimempart) < 0)
5888 g_node_append(mimemsg->node, mimetext->node);
5892 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5893 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5894 /* extract name and address */
5895 if (strstr(spec, " <") && strstr(spec, ">")) {
5896 from_addr = g_strdup(strrchr(spec, '<')+1);
5897 *(strrchr(from_addr, '>')) = '\0';
5898 from_name = g_strdup(spec);
5899 *(strrchr(from_name, '<')) = '\0';
5906 /* sign message if sending */
5907 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5908 privacy_system_can_sign(compose->privacy_system))
5909 if (!privacy_sign(compose->privacy_system, mimemsg,
5910 compose->account, from_addr)) {
5918 if (compose->use_encryption) {
5919 if (compose->encdata != NULL &&
5920 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5922 /* First, write an unencrypted copy and save it to outbox, if
5923 * user wants that. */
5924 if (compose->account->save_encrypted_as_clear_text) {
5925 debug_print("saving sent message unencrypted...\n");
5926 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5930 /* fp now points to a file with headers written,
5931 * let's make a copy. */
5933 content = file_read_stream_to_str(fp);
5935 str_write_to_file(content, tmp_enc_file);
5938 /* Now write the unencrypted body. */
5939 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5940 procmime_write_mimeinfo(mimemsg, tmpfp);
5943 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5945 outbox = folder_get_default_outbox();
5947 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5948 claws_unlink(tmp_enc_file);
5950 g_warning("Can't open file '%s'", tmp_enc_file);
5953 g_warning("couldn't get tempfile");
5956 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5957 debug_print("Couldn't encrypt mime structure: %s.\n",
5958 privacy_get_error());
5959 alertpanel_error(_("Couldn't encrypt the email: %s"),
5960 privacy_get_error());
5965 procmime_write_mimeinfo(mimemsg, fp);
5967 procmime_mimeinfo_free_all(&mimemsg);
5972 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5974 GtkTextBuffer *buffer;
5975 GtkTextIter start, end;
5980 if ((fp = g_fopen(file, "wb")) == NULL) {
5981 FILE_OP_ERROR(file, "fopen");
5985 /* chmod for security */
5986 if (change_file_mode_rw(fp, file) < 0) {
5987 FILE_OP_ERROR(file, "chmod");
5988 g_warning("can't change file mode");
5991 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5992 gtk_text_buffer_get_start_iter(buffer, &start);
5993 gtk_text_buffer_get_end_iter(buffer, &end);
5994 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5996 chars = conv_codeset_strdup
5997 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6006 len = strlen(chars);
6007 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
6008 FILE_OP_ERROR(file, "fwrite");
6017 if (fclose(fp) == EOF) {
6018 FILE_OP_ERROR(file, "fclose");
6025 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6028 MsgInfo *msginfo = compose->targetinfo;
6030 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6031 if (!msginfo) return -1;
6033 if (!force && MSG_IS_LOCKED(msginfo->flags))
6036 item = msginfo->folder;
6037 cm_return_val_if_fail(item != NULL, -1);
6039 if (procmsg_msg_exist(msginfo) &&
6040 (folder_has_parent_of_type(item, F_QUEUE) ||
6041 folder_has_parent_of_type(item, F_DRAFT)
6042 || msginfo == compose->autosaved_draft)) {
6043 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6044 g_warning("can't remove the old message");
6047 debug_print("removed reedit target %d\n", msginfo->msgnum);
6054 static void compose_remove_draft(Compose *compose)
6057 MsgInfo *msginfo = compose->targetinfo;
6058 drafts = account_get_special_folder(compose->account, F_DRAFT);
6060 if (procmsg_msg_exist(msginfo)) {
6061 folder_item_remove_msg(drafts, msginfo->msgnum);
6066 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6067 gboolean remove_reedit_target)
6069 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6072 static gboolean compose_warn_encryption(Compose *compose)
6074 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6075 AlertValue val = G_ALERTALTERNATE;
6077 if (warning == NULL)
6080 val = alertpanel_full(_("Encryption warning"), warning,
6081 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
6082 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
6083 if (val & G_ALERTDISABLE) {
6084 val &= ~G_ALERTDISABLE;
6085 if (val == G_ALERTALTERNATE)
6086 privacy_inhibit_encrypt_warning(compose->privacy_system,
6090 if (val == G_ALERTALTERNATE) {
6097 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6098 gchar **msgpath, gboolean perform_checks,
6099 gboolean remove_reedit_target)
6106 PrefsAccount *mailac = NULL, *newsac = NULL;
6107 gboolean err = FALSE;
6109 debug_print("queueing message...\n");
6110 cm_return_val_if_fail(compose->account != NULL, -1);
6112 if (compose_check_entries(compose, perform_checks) == FALSE) {
6113 if (compose->batch) {
6114 gtk_widget_show_all(compose->window);
6119 if (!compose->to_list && !compose->newsgroup_list) {
6120 g_warning("can't get recipient list.");
6124 if (compose->to_list) {
6125 if (compose->account->protocol != A_NNTP)
6126 mailac = compose->account;
6127 else if (cur_account && cur_account->protocol != A_NNTP)
6128 mailac = cur_account;
6129 else if (!(mailac = compose_current_mail_account())) {
6130 alertpanel_error(_("No account for sending mails available!"));
6135 if (compose->newsgroup_list) {
6136 if (compose->account->protocol == A_NNTP)
6137 newsac = compose->account;
6139 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6144 /* write queue header */
6145 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6146 G_DIR_SEPARATOR, compose, (guint) rand());
6147 debug_print("queuing to %s\n", tmp);
6148 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6149 FILE_OP_ERROR(tmp, "fopen");
6154 if (change_file_mode_rw(fp, tmp) < 0) {
6155 FILE_OP_ERROR(tmp, "chmod");
6156 g_warning("can't change file mode");
6159 /* queueing variables */
6160 err |= (fprintf(fp, "AF:\n") < 0);
6161 err |= (fprintf(fp, "NF:0\n") < 0);
6162 err |= (fprintf(fp, "PS:10\n") < 0);
6163 err |= (fprintf(fp, "SRH:1\n") < 0);
6164 err |= (fprintf(fp, "SFN:\n") < 0);
6165 err |= (fprintf(fp, "DSR:\n") < 0);
6167 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6169 err |= (fprintf(fp, "MID:\n") < 0);
6170 err |= (fprintf(fp, "CFG:\n") < 0);
6171 err |= (fprintf(fp, "PT:0\n") < 0);
6172 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6173 err |= (fprintf(fp, "RQ:\n") < 0);
6175 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6177 err |= (fprintf(fp, "SSV:\n") < 0);
6179 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6181 err |= (fprintf(fp, "NSV:\n") < 0);
6182 err |= (fprintf(fp, "SSH:\n") < 0);
6183 /* write recepient list */
6184 if (compose->to_list) {
6185 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6186 for (cur = compose->to_list->next; cur != NULL;
6188 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6189 err |= (fprintf(fp, "\n") < 0);
6191 /* write newsgroup list */
6192 if (compose->newsgroup_list) {
6193 err |= (fprintf(fp, "NG:") < 0);
6194 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6195 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6196 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6197 err |= (fprintf(fp, "\n") < 0);
6199 /* Sylpheed account IDs */
6201 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6203 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6206 if (compose->privacy_system != NULL) {
6207 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6208 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6209 if (compose->use_encryption) {
6210 if (!compose_warn_encryption(compose)) {
6216 if (mailac && mailac->encrypt_to_self) {
6217 GSList *tmp_list = g_slist_copy(compose->to_list);
6218 tmp_list = g_slist_append(tmp_list, compose->account->address);
6219 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6220 g_slist_free(tmp_list);
6222 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6224 if (compose->encdata != NULL) {
6225 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6226 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6227 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6228 compose->encdata) < 0);
6229 } /* else we finally dont want to encrypt */
6231 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6232 /* and if encdata was null, it means there's been a problem in
6235 g_warning("failed to write queue message");
6244 /* Save copy folder */
6245 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6246 gchar *savefolderid;
6248 savefolderid = compose_get_save_to(compose);
6249 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6250 g_free(savefolderid);
6252 /* Save copy folder */
6253 if (compose->return_receipt) {
6254 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6256 /* Message-ID of message replying to */
6257 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6258 gchar *folderid = NULL;
6260 if (compose->replyinfo->folder)
6261 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6262 if (folderid == NULL)
6263 folderid = g_strdup("NULL");
6265 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6268 /* Message-ID of message forwarding to */
6269 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6270 gchar *folderid = NULL;
6272 if (compose->fwdinfo->folder)
6273 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6274 if (folderid == NULL)
6275 folderid = g_strdup("NULL");
6277 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6281 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6282 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6284 /* end of headers */
6285 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6287 if (compose->redirect_filename != NULL) {
6288 if (compose_redirect_write_to_file(compose, fp) < 0) {
6296 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6300 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6304 g_warning("failed to write queue message");
6310 if (fclose(fp) == EOF) {
6311 FILE_OP_ERROR(tmp, "fclose");
6317 if (item && *item) {
6320 queue = account_get_special_folder(compose->account, F_QUEUE);
6323 g_warning("can't find queue folder");
6328 folder_item_scan(queue);
6329 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6330 g_warning("can't queue the message");
6336 if (msgpath == NULL) {
6342 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6343 compose_remove_reedit_target(compose, FALSE);
6346 if ((msgnum != NULL) && (item != NULL)) {
6354 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6357 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6362 GError *error = NULL;
6367 gchar *type, *subtype;
6368 GtkTreeModel *model;
6371 model = gtk_tree_view_get_model(tree_view);
6373 if (!gtk_tree_model_get_iter_first(model, &iter))
6376 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6378 if (!is_file_exist(ainfo->file)) {
6379 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6380 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6381 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6383 if (val == G_ALERTDEFAULT) {
6389 f = g_file_new_for_path(ainfo->file);
6390 fi = g_file_query_info(f, "standard::size",
6391 G_FILE_QUERY_INFO_NONE, NULL, &error);
6392 if (error != NULL) {
6393 g_warning(error->message);
6394 g_error_free(error);
6398 size = g_file_info_get_size(fi);
6402 if (g_stat(ainfo->file, &statbuf) < 0)
6404 size = statbuf.st_size;
6407 mimepart = procmime_mimeinfo_new();
6408 mimepart->content = MIMECONTENT_FILE;
6409 mimepart->data.filename = g_strdup(ainfo->file);
6410 mimepart->tmp = FALSE; /* or we destroy our attachment */
6411 mimepart->offset = 0;
6412 mimepart->length = size;
6414 type = g_strdup(ainfo->content_type);
6416 if (!strchr(type, '/')) {
6418 type = g_strdup("application/octet-stream");
6421 subtype = strchr(type, '/') + 1;
6422 *(subtype - 1) = '\0';
6423 mimepart->type = procmime_get_media_type(type);
6424 mimepart->subtype = g_strdup(subtype);
6427 if (mimepart->type == MIMETYPE_MESSAGE &&
6428 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6429 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6430 } else if (mimepart->type == MIMETYPE_TEXT) {
6431 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6432 /* Text parts with no name come from multipart/alternative
6433 * forwards. Make sure the recipient won't look at the
6434 * original HTML part by mistake. */
6435 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6436 ainfo->name = g_strdup_printf(_("Original %s part"),
6440 g_hash_table_insert(mimepart->typeparameters,
6441 g_strdup("charset"), g_strdup(ainfo->charset));
6443 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6444 if (mimepart->type == MIMETYPE_APPLICATION &&
6445 !strcmp2(mimepart->subtype, "octet-stream"))
6446 g_hash_table_insert(mimepart->typeparameters,
6447 g_strdup("name"), g_strdup(ainfo->name));
6448 g_hash_table_insert(mimepart->dispositionparameters,
6449 g_strdup("filename"), g_strdup(ainfo->name));
6450 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6453 if (mimepart->type == MIMETYPE_MESSAGE
6454 || mimepart->type == MIMETYPE_MULTIPART)
6455 ainfo->encoding = ENC_BINARY;
6456 else if (compose->use_signing) {
6457 if (ainfo->encoding == ENC_7BIT)
6458 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6459 else if (ainfo->encoding == ENC_8BIT)
6460 ainfo->encoding = ENC_BASE64;
6463 procmime_encode_content(mimepart, ainfo->encoding);
6465 g_node_append(parent->node, mimepart->node);
6466 } while (gtk_tree_model_iter_next(model, &iter));
6471 static gchar *compose_quote_list_of_addresses(gchar *str)
6473 GSList *list = NULL, *item = NULL;
6474 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6476 list = address_list_append_with_comments(list, str);
6477 for (item = list; item != NULL; item = item->next) {
6478 gchar *spec = item->data;
6479 gchar *endofname = strstr(spec, " <");
6480 if (endofname != NULL) {
6483 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6484 qqname = escape_internal_quotes(qname, '"');
6486 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6487 gchar *addr = g_strdup(endofname);
6488 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6489 faddr = g_strconcat(name, addr, NULL);
6492 debug_print("new auto-quoted address: '%s'\n", faddr);
6496 result = g_strdup((faddr != NULL)? faddr: spec);
6498 result = g_strconcat(result,
6500 (faddr != NULL)? faddr: spec,
6503 if (faddr != NULL) {
6508 slist_free_strings_full(list);
6513 #define IS_IN_CUSTOM_HEADER(header) \
6514 (compose->account->add_customhdr && \
6515 custom_header_find(compose->account->customhdr_list, header) != NULL)
6517 static const gchar *compose_untranslated_header_name(gchar *header_name)
6519 /* return the untranslated header name, if header_name is a known
6520 header name, in either its translated or untranslated form, with
6521 or without trailing colon. otherwise, returns header_name. */
6522 gchar *translated_header_name;
6523 gchar *translated_header_name_wcolon;
6524 const gchar *untranslated_header_name;
6525 const gchar *untranslated_header_name_wcolon;
6528 cm_return_val_if_fail(header_name != NULL, NULL);
6530 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6531 untranslated_header_name = HEADERS[i].header_name;
6532 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6534 translated_header_name = gettext(untranslated_header_name);
6535 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6537 if (!strcmp(header_name, untranslated_header_name) ||
6538 !strcmp(header_name, translated_header_name)) {
6539 return untranslated_header_name;
6541 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6542 !strcmp(header_name, translated_header_name_wcolon)) {
6543 return untranslated_header_name_wcolon;
6547 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6551 static void compose_add_headerfield_from_headerlist(Compose *compose,
6553 const gchar *fieldname,
6554 const gchar *seperator)
6556 gchar *str, *fieldname_w_colon;
6557 gboolean add_field = FALSE;
6559 ComposeHeaderEntry *headerentry;
6560 const gchar *headerentryname;
6561 const gchar *trans_fieldname;
6564 if (IS_IN_CUSTOM_HEADER(fieldname))
6567 debug_print("Adding %s-fields\n", fieldname);
6569 fieldstr = g_string_sized_new(64);
6571 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6572 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6574 for (list = compose->header_list; list; list = list->next) {
6575 headerentry = ((ComposeHeaderEntry *)list->data);
6576 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6578 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6579 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6581 str = compose_quote_list_of_addresses(ustr);
6583 if (str != NULL && str[0] != '\0') {
6585 g_string_append(fieldstr, seperator);
6586 g_string_append(fieldstr, str);
6595 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6596 compose_convert_header
6597 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6598 strlen(fieldname) + 2, TRUE);
6599 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6603 g_free(fieldname_w_colon);
6604 g_string_free(fieldstr, TRUE);
6609 static gchar *compose_get_manual_headers_info(Compose *compose)
6611 GString *sh_header = g_string_new(" ");
6613 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6615 for (list = compose->header_list; list; list = list->next) {
6616 ComposeHeaderEntry *headerentry;
6619 gchar *headername_wcolon;
6620 const gchar *headername_trans;
6622 gboolean standard_header = FALSE;
6624 headerentry = ((ComposeHeaderEntry *)list->data);
6626 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6628 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6633 if (!strstr(tmp, ":")) {
6634 headername_wcolon = g_strconcat(tmp, ":", NULL);
6635 headername = g_strdup(tmp);
6637 headername_wcolon = g_strdup(tmp);
6638 headername = g_strdup(strtok(tmp, ":"));
6642 string = std_headers;
6643 while (*string != NULL) {
6644 headername_trans = prefs_common_translated_header_name(*string);
6645 if (!strcmp(headername_trans, headername_wcolon))
6646 standard_header = TRUE;
6649 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6650 g_string_append_printf(sh_header, "%s ", headername);
6652 g_free(headername_wcolon);
6654 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6655 return g_string_free(sh_header, FALSE);
6658 static gchar *compose_get_header(Compose *compose)
6660 gchar date[RFC822_DATE_BUFFSIZE];
6661 gchar buf[BUFFSIZE];
6662 const gchar *entry_str;
6666 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6668 gchar *from_name = NULL, *from_address = NULL;
6671 cm_return_val_if_fail(compose->account != NULL, NULL);
6672 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6674 header = g_string_sized_new(64);
6677 if (prefs_common.hide_timezone)
6678 get_rfc822_date_hide_tz(date, sizeof(date));
6680 get_rfc822_date(date, sizeof(date));
6681 g_string_append_printf(header, "Date: %s\n", date);
6685 if (compose->account->name && *compose->account->name) {
6687 QUOTE_IF_REQUIRED(buf, compose->account->name);
6688 tmp = g_strdup_printf("%s <%s>",
6689 buf, compose->account->address);
6691 tmp = g_strdup_printf("%s",
6692 compose->account->address);
6694 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6695 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6697 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6698 from_address = g_strdup(compose->account->address);
6700 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6701 /* extract name and address */
6702 if (strstr(spec, " <") && strstr(spec, ">")) {
6703 from_address = g_strdup(strrchr(spec, '<')+1);
6704 *(strrchr(from_address, '>')) = '\0';
6705 from_name = g_strdup(spec);
6706 *(strrchr(from_name, '<')) = '\0';
6709 from_address = g_strdup(spec);
6716 if (from_name && *from_name) {
6718 compose_convert_header
6719 (compose, buf, sizeof(buf), from_name,
6720 strlen("From: "), TRUE);
6721 QUOTE_IF_REQUIRED(name, buf);
6722 qname = escape_internal_quotes(name, '"');
6724 g_string_append_printf(header, "From: %s <%s>\n",
6725 qname, from_address);
6726 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6727 compose->return_receipt) {
6728 compose_convert_header(compose, buf, sizeof(buf), from_name,
6729 strlen("Disposition-Notification-To: "),
6731 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6736 g_string_append_printf(header, "From: %s\n", from_address);
6737 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6738 compose->return_receipt)
6739 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6743 g_free(from_address);
6746 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6749 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6752 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6756 * If this account is a NNTP account remove Bcc header from
6757 * message body since it otherwise will be publicly shown
6759 if (compose->account->protocol != A_NNTP)
6760 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6763 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6765 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6768 compose_convert_header(compose, buf, sizeof(buf), str,
6769 strlen("Subject: "), FALSE);
6770 g_string_append_printf(header, "Subject: %s\n", buf);
6776 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6777 g_string_append_printf(header, "Message-ID: <%s>\n",
6781 if (compose->remove_references == FALSE) {
6783 if (compose->inreplyto && compose->to_list)
6784 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6787 if (compose->references)
6788 g_string_append_printf(header, "References: %s\n", compose->references);
6792 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6795 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6798 if (compose->account->organization &&
6799 strlen(compose->account->organization) &&
6800 !IS_IN_CUSTOM_HEADER("Organization")) {
6801 compose_convert_header(compose, buf, sizeof(buf),
6802 compose->account->organization,
6803 strlen("Organization: "), FALSE);
6804 g_string_append_printf(header, "Organization: %s\n", buf);
6807 /* Program version and system info */
6808 if (compose->account->gen_xmailer &&
6809 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6810 !compose->newsgroup_list) {
6811 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6813 gtk_major_version, gtk_minor_version, gtk_micro_version,
6816 if (compose->account->gen_xmailer &&
6817 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6818 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6820 gtk_major_version, gtk_minor_version, gtk_micro_version,
6824 /* custom headers */
6825 if (compose->account->add_customhdr) {
6828 for (cur = compose->account->customhdr_list; cur != NULL;
6830 CustomHeader *chdr = (CustomHeader *)cur->data;
6832 if (custom_header_is_allowed(chdr->name)
6833 && chdr->value != NULL
6834 && *(chdr->value) != '\0') {
6835 compose_convert_header
6836 (compose, buf, sizeof(buf),
6838 strlen(chdr->name) + 2, FALSE);
6839 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6844 /* Automatic Faces and X-Faces */
6845 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6846 g_string_append_printf(header, "X-Face: %s\n", buf);
6848 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6849 g_string_append_printf(header, "X-Face: %s\n", buf);
6851 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6852 g_string_append_printf(header, "Face: %s\n", buf);
6854 else if (get_default_face (buf, sizeof(buf)) == 0) {
6855 g_string_append_printf(header, "Face: %s\n", buf);
6859 switch (compose->priority) {
6860 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6861 "X-Priority: 1 (Highest)\n");
6863 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6864 "X-Priority: 2 (High)\n");
6866 case PRIORITY_NORMAL: break;
6867 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6868 "X-Priority: 4 (Low)\n");
6870 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6871 "X-Priority: 5 (Lowest)\n");
6873 default: debug_print("compose: priority unknown : %d\n",
6877 /* get special headers */
6878 for (list = compose->header_list; list; list = list->next) {
6879 ComposeHeaderEntry *headerentry;
6882 gchar *headername_wcolon;
6883 const gchar *headername_trans;
6886 gboolean standard_header = FALSE;
6888 headerentry = ((ComposeHeaderEntry *)list->data);
6890 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6892 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6897 if (!strstr(tmp, ":")) {
6898 headername_wcolon = g_strconcat(tmp, ":", NULL);
6899 headername = g_strdup(tmp);
6901 headername_wcolon = g_strdup(tmp);
6902 headername = g_strdup(strtok(tmp, ":"));
6906 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6907 Xstrdup_a(headervalue, entry_str, return NULL);
6908 subst_char(headervalue, '\r', ' ');
6909 subst_char(headervalue, '\n', ' ');
6910 g_strstrip(headervalue);
6911 if (*headervalue != '\0') {
6912 string = std_headers;
6913 while (*string != NULL && !standard_header) {
6914 headername_trans = prefs_common_translated_header_name(*string);
6915 /* support mixed translated and untranslated headers */
6916 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6917 standard_header = TRUE;
6920 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6921 /* store untranslated header name */
6922 g_string_append_printf(header, "%s %s\n",
6923 compose_untranslated_header_name(headername_wcolon), headervalue);
6927 g_free(headername_wcolon);
6931 g_string_free(header, FALSE);
6936 #undef IS_IN_CUSTOM_HEADER
6938 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6939 gint header_len, gboolean addr_field)
6941 gchar *tmpstr = NULL;
6942 const gchar *out_codeset = NULL;
6944 cm_return_if_fail(src != NULL);
6945 cm_return_if_fail(dest != NULL);
6947 if (len < 1) return;
6949 tmpstr = g_strdup(src);
6951 subst_char(tmpstr, '\n', ' ');
6952 subst_char(tmpstr, '\r', ' ');
6955 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6956 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6957 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6962 codeconv_set_strict(TRUE);
6963 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6964 conv_get_charset_str(compose->out_encoding));
6965 codeconv_set_strict(FALSE);
6967 if (!dest || *dest == '\0') {
6968 gchar *test_conv_global_out = NULL;
6969 gchar *test_conv_reply = NULL;
6971 /* automatic mode. be automatic. */
6972 codeconv_set_strict(TRUE);
6974 out_codeset = conv_get_outgoing_charset_str();
6976 debug_print("trying to convert to %s\n", out_codeset);
6977 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6980 if (!test_conv_global_out && compose->orig_charset
6981 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6982 out_codeset = compose->orig_charset;
6983 debug_print("failure; trying to convert to %s\n", out_codeset);
6984 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6987 if (!test_conv_global_out && !test_conv_reply) {
6989 out_codeset = CS_INTERNAL;
6990 debug_print("finally using %s\n", out_codeset);
6992 g_free(test_conv_global_out);
6993 g_free(test_conv_reply);
6994 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6996 codeconv_set_strict(FALSE);
7001 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7005 cm_return_if_fail(user_data != NULL);
7007 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7008 g_strstrip(address);
7009 if (*address != '\0') {
7010 gchar *name = procheader_get_fromname(address);
7011 extract_address(address);
7012 #ifndef USE_ALT_ADDRBOOK
7013 addressbook_add_contact(name, address, NULL, NULL);
7015 debug_print("%s: %s\n", name, address);
7016 if (addressadd_selection(name, address, NULL, NULL)) {
7017 debug_print( "addressbook_add_contact - added\n" );
7024 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7026 GtkWidget *menuitem;
7029 cm_return_if_fail(menu != NULL);
7030 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7032 menuitem = gtk_separator_menu_item_new();
7033 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7034 gtk_widget_show(menuitem);
7036 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7037 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7039 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7040 g_strstrip(address);
7041 if (*address == '\0') {
7042 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7045 g_signal_connect(G_OBJECT(menuitem), "activate",
7046 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7047 gtk_widget_show(menuitem);
7050 void compose_add_extra_header(gchar *header, GtkListStore *model)
7053 if (strcmp(header, "")) {
7054 COMBOBOX_ADD(model, header, COMPOSE_TO);
7058 void compose_add_extra_header_entries(GtkListStore *model)
7062 gchar buf[BUFFSIZE];
7065 if (extra_headers == NULL) {
7066 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7067 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7068 debug_print("extra headers file not found\n");
7069 goto extra_headers_done;
7071 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7072 lastc = strlen(buf) - 1; /* remove trailing control chars */
7073 while (lastc >= 0 && buf[lastc] != ':')
7074 buf[lastc--] = '\0';
7075 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7076 buf[lastc] = '\0'; /* remove trailing : for comparison */
7077 if (custom_header_is_allowed(buf)) {
7079 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7082 g_message("disallowed extra header line: %s\n", buf);
7086 g_message("invalid extra header line: %s\n", buf);
7092 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7093 extra_headers = g_slist_reverse(extra_headers);
7095 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7099 static void _ldap_srv_func(gpointer data, gpointer user_data)
7101 LdapServer *server = (LdapServer *)data;
7102 gboolean *enable = (gboolean *)user_data;
7104 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7105 server->searchFlag = *enable;
7109 static void compose_create_header_entry(Compose *compose)
7111 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7118 const gchar *header = NULL;
7119 ComposeHeaderEntry *headerentry;
7120 gboolean standard_header = FALSE;
7121 GtkListStore *model;
7124 headerentry = g_new0(ComposeHeaderEntry, 1);
7126 /* Combo box model */
7127 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7128 #if !GTK_CHECK_VERSION(2, 24, 0)
7129 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
7131 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7133 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7135 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7137 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7138 COMPOSE_NEWSGROUPS);
7139 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7141 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7142 COMPOSE_FOLLOWUPTO);
7143 compose_add_extra_header_entries(model);
7146 #if GTK_CHECK_VERSION(2, 24, 0)
7147 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7148 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7149 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7150 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7151 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7153 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7154 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7155 G_CALLBACK(compose_grab_focus_cb), compose);
7156 gtk_widget_show(combo);
7158 /* Putting only the combobox child into focus chain of its parent causes
7159 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7160 * This eliminates need to pres Tab twice in order to really get from the
7161 * combobox to next widget. */
7163 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7164 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7167 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7168 compose->header_nextrow, compose->header_nextrow+1,
7169 GTK_SHRINK, GTK_FILL, 0, 0);
7170 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7171 const gchar *last_header_entry = gtk_entry_get_text(
7172 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7174 while (*string != NULL) {
7175 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7176 standard_header = TRUE;
7179 if (standard_header)
7180 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7182 if (!compose->header_last || !standard_header) {
7183 switch(compose->account->protocol) {
7185 header = prefs_common_translated_header_name("Newsgroups:");
7188 header = prefs_common_translated_header_name("To:");
7193 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7195 gtk_editable_set_editable(
7196 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7197 prefs_common.type_any_header);
7199 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7200 G_CALLBACK(compose_grab_focus_cb), compose);
7202 /* Entry field with cleanup button */
7203 button = gtk_button_new();
7204 gtk_button_set_image(GTK_BUTTON(button),
7205 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7206 gtk_widget_show(button);
7207 CLAWS_SET_TIP(button,
7208 _("Delete entry contents"));
7209 entry = gtk_entry_new();
7210 gtk_widget_show(entry);
7211 CLAWS_SET_TIP(entry,
7212 _("Use <tab> to autocomplete from addressbook"));
7213 hbox = gtk_hbox_new (FALSE, 0);
7214 gtk_widget_show(hbox);
7215 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7216 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7217 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7218 compose->header_nextrow, compose->header_nextrow+1,
7219 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7221 g_signal_connect(G_OBJECT(entry), "key-press-event",
7222 G_CALLBACK(compose_headerentry_key_press_event_cb),
7224 g_signal_connect(G_OBJECT(entry), "changed",
7225 G_CALLBACK(compose_headerentry_changed_cb),
7227 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7228 G_CALLBACK(compose_grab_focus_cb), compose);
7230 g_signal_connect(G_OBJECT(button), "clicked",
7231 G_CALLBACK(compose_headerentry_button_clicked_cb),
7235 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7236 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7237 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7238 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7239 G_CALLBACK(compose_header_drag_received_cb),
7241 g_signal_connect(G_OBJECT(entry), "drag-drop",
7242 G_CALLBACK(compose_drag_drop),
7244 g_signal_connect(G_OBJECT(entry), "populate-popup",
7245 G_CALLBACK(compose_entry_popup_extend),
7249 #ifndef PASSWORD_CRYPTO_OLD
7250 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7251 if (pwd_servers != NULL && master_passphrase() == NULL) {
7252 gboolean enable = FALSE;
7253 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7254 /* Temporarily disable password-protected LDAP servers,
7255 * because user did not provide a master passphrase.
7256 * We can safely enable searchFlag on all servers in this list
7257 * later, since addrindex_get_password_protected_ldap_servers()
7258 * includes servers which have it enabled initially. */
7259 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7260 compose->passworded_ldap_servers = pwd_servers;
7262 #endif /* PASSWORD_CRYPTO_OLD */
7263 #endif /* USE_LDAP */
7265 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7267 headerentry->compose = compose;
7268 headerentry->combo = combo;
7269 headerentry->entry = entry;
7270 headerentry->button = button;
7271 headerentry->hbox = hbox;
7272 headerentry->headernum = compose->header_nextrow;
7273 headerentry->type = PREF_NONE;
7275 compose->header_nextrow++;
7276 compose->header_last = headerentry;
7277 compose->header_list =
7278 g_slist_append(compose->header_list,
7282 static void compose_add_header_entry(Compose *compose, const gchar *header,
7283 gchar *text, ComposePrefType pref_type)
7285 ComposeHeaderEntry *last_header = compose->header_last;
7286 gchar *tmp = g_strdup(text), *email;
7287 gboolean replyto_hdr;
7289 replyto_hdr = (!strcasecmp(header,
7290 prefs_common_translated_header_name("Reply-To:")) ||
7292 prefs_common_translated_header_name("Followup-To:")) ||
7294 prefs_common_translated_header_name("In-Reply-To:")));
7296 extract_address(tmp);
7297 email = g_utf8_strdown(tmp, -1);
7299 if (replyto_hdr == FALSE &&
7300 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7302 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7303 header, text, (gint) pref_type);
7309 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7310 gtk_entry_set_text(GTK_ENTRY(
7311 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7313 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7314 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7315 last_header->type = pref_type;
7317 if (replyto_hdr == FALSE)
7318 g_hash_table_insert(compose->email_hashtable, email,
7319 GUINT_TO_POINTER(1));
7326 static void compose_destroy_headerentry(Compose *compose,
7327 ComposeHeaderEntry *headerentry)
7329 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7332 extract_address(text);
7333 email = g_utf8_strdown(text, -1);
7334 g_hash_table_remove(compose->email_hashtable, email);
7338 gtk_widget_destroy(headerentry->combo);
7339 gtk_widget_destroy(headerentry->entry);
7340 gtk_widget_destroy(headerentry->button);
7341 gtk_widget_destroy(headerentry->hbox);
7342 g_free(headerentry);
7345 static void compose_remove_header_entries(Compose *compose)
7348 for (list = compose->header_list; list; list = list->next)
7349 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7351 compose->header_last = NULL;
7352 g_slist_free(compose->header_list);
7353 compose->header_list = NULL;
7354 compose->header_nextrow = 1;
7355 compose_create_header_entry(compose);
7358 static GtkWidget *compose_create_header(Compose *compose)
7360 GtkWidget *from_optmenu_hbox;
7361 GtkWidget *header_table_main;
7362 GtkWidget *header_scrolledwin;
7363 GtkWidget *header_table;
7365 /* parent with account selection and from header */
7366 header_table_main = gtk_table_new(2, 2, FALSE);
7367 gtk_widget_show(header_table_main);
7368 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7370 from_optmenu_hbox = compose_account_option_menu_create(compose);
7371 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7372 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7374 /* child with header labels and entries */
7375 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7376 gtk_widget_show(header_scrolledwin);
7377 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7379 header_table = gtk_table_new(2, 2, FALSE);
7380 gtk_widget_show(header_table);
7381 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7382 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7383 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7384 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7385 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7387 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7388 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7390 compose->header_table = header_table;
7391 compose->header_list = NULL;
7392 compose->header_nextrow = 0;
7394 compose_create_header_entry(compose);
7396 compose->table = NULL;
7398 return header_table_main;
7401 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7403 Compose *compose = (Compose *)data;
7404 GdkEventButton event;
7407 event.time = gtk_get_current_event_time();
7409 return attach_button_pressed(compose->attach_clist, &event, compose);
7412 static GtkWidget *compose_create_attach(Compose *compose)
7414 GtkWidget *attach_scrwin;
7415 GtkWidget *attach_clist;
7417 GtkListStore *store;
7418 GtkCellRenderer *renderer;
7419 GtkTreeViewColumn *column;
7420 GtkTreeSelection *selection;
7422 /* attachment list */
7423 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7424 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7425 GTK_POLICY_AUTOMATIC,
7426 GTK_POLICY_AUTOMATIC);
7427 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7429 store = gtk_list_store_new(N_ATTACH_COLS,
7435 G_TYPE_AUTO_POINTER,
7437 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7438 (GTK_TREE_MODEL(store)));
7439 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7440 g_object_unref(store);
7442 renderer = gtk_cell_renderer_text_new();
7443 column = gtk_tree_view_column_new_with_attributes
7444 (_("Mime type"), renderer, "text",
7445 COL_MIMETYPE, NULL);
7446 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7448 renderer = gtk_cell_renderer_text_new();
7449 column = gtk_tree_view_column_new_with_attributes
7450 (_("Size"), renderer, "text",
7452 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7454 renderer = gtk_cell_renderer_text_new();
7455 column = gtk_tree_view_column_new_with_attributes
7456 (_("Name"), renderer, "text",
7458 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7460 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7461 prefs_common.use_stripes_everywhere);
7462 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7463 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7465 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7466 G_CALLBACK(attach_selected), compose);
7467 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7468 G_CALLBACK(attach_button_pressed), compose);
7469 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7470 G_CALLBACK(popup_attach_button_pressed), compose);
7471 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7472 G_CALLBACK(attach_key_pressed), compose);
7475 gtk_drag_dest_set(attach_clist,
7476 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7477 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7478 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7479 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7480 G_CALLBACK(compose_attach_drag_received_cb),
7482 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7483 G_CALLBACK(compose_drag_drop),
7486 compose->attach_scrwin = attach_scrwin;
7487 compose->attach_clist = attach_clist;
7489 return attach_scrwin;
7492 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7493 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7495 static GtkWidget *compose_create_others(Compose *compose)
7498 GtkWidget *savemsg_checkbtn;
7499 GtkWidget *savemsg_combo;
7500 GtkWidget *savemsg_select;
7503 gchar *folderidentifier;
7505 /* Table for settings */
7506 table = gtk_table_new(3, 1, FALSE);
7507 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7508 gtk_widget_show(table);
7509 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7512 /* Save Message to folder */
7513 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7514 gtk_widget_show(savemsg_checkbtn);
7515 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7516 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7517 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7519 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7520 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7522 #if !GTK_CHECK_VERSION(2, 24, 0)
7523 savemsg_combo = gtk_combo_box_entry_new_text();
7525 savemsg_combo = gtk_combo_box_text_new_with_entry();
7527 compose->savemsg_checkbtn = savemsg_checkbtn;
7528 compose->savemsg_combo = savemsg_combo;
7529 gtk_widget_show(savemsg_combo);
7531 if (prefs_common.compose_save_to_history)
7532 #if !GTK_CHECK_VERSION(2, 24, 0)
7533 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7534 prefs_common.compose_save_to_history);
7536 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7537 prefs_common.compose_save_to_history);
7539 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7540 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7541 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7542 G_CALLBACK(compose_grab_focus_cb), compose);
7543 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7544 folderidentifier = folder_item_get_identifier(account_get_special_folder
7545 (compose->account, F_OUTBOX));
7546 compose_set_save_to(compose, folderidentifier);
7547 g_free(folderidentifier);
7550 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7551 gtk_widget_show(savemsg_select);
7552 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7553 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7554 G_CALLBACK(compose_savemsg_select_cb),
7560 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7562 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7563 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7566 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7571 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7572 _("Select folder to save message to"));
7575 path = folder_item_get_identifier(dest);
7577 compose_set_save_to(compose, path);
7581 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7582 GdkAtom clip, GtkTextIter *insert_place);
7585 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7589 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7591 if (event->button == 3) {
7593 GtkTextIter sel_start, sel_end;
7594 gboolean stuff_selected;
7596 /* move the cursor to allow GtkAspell to check the word
7597 * under the mouse */
7598 if (event->x && event->y) {
7599 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7600 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7602 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7605 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7606 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7609 stuff_selected = gtk_text_buffer_get_selection_bounds(
7611 &sel_start, &sel_end);
7613 gtk_text_buffer_place_cursor (buffer, &iter);
7614 /* reselect stuff */
7616 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7617 gtk_text_buffer_select_range(buffer,
7618 &sel_start, &sel_end);
7620 return FALSE; /* pass the event so that the right-click goes through */
7623 if (event->button == 2) {
7628 /* get the middle-click position to paste at the correct place */
7629 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7630 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7632 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7635 entry_paste_clipboard(compose, text,
7636 prefs_common.linewrap_pastes,
7637 GDK_SELECTION_PRIMARY, &iter);
7645 static void compose_spell_menu_changed(void *data)
7647 Compose *compose = (Compose *)data;
7649 GtkWidget *menuitem;
7650 GtkWidget *parent_item;
7651 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7654 if (compose->gtkaspell == NULL)
7657 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7658 "/Menu/Spelling/Options");
7660 /* setting the submenu removes /Spelling/Options from the factory
7661 * so we need to save it */
7663 if (parent_item == NULL) {
7664 parent_item = compose->aspell_options_menu;
7665 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7667 compose->aspell_options_menu = parent_item;
7669 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7671 spell_menu = g_slist_reverse(spell_menu);
7672 for (items = spell_menu;
7673 items; items = items->next) {
7674 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7675 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7676 gtk_widget_show(GTK_WIDGET(menuitem));
7678 g_slist_free(spell_menu);
7680 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7681 gtk_widget_show(parent_item);
7684 static void compose_dict_changed(void *data)
7686 Compose *compose = (Compose *) data;
7688 if(!compose->gtkaspell)
7690 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7693 gtkaspell_highlight_all(compose->gtkaspell);
7694 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7698 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7700 Compose *compose = (Compose *)data;
7701 GdkEventButton event;
7704 event.time = gtk_get_current_event_time();
7708 return text_clicked(compose->text, &event, compose);
7711 static gboolean compose_force_window_origin = TRUE;
7712 static Compose *compose_create(PrefsAccount *account,
7721 GtkWidget *handlebox;
7723 GtkWidget *notebook;
7725 GtkWidget *attach_hbox;
7726 GtkWidget *attach_lab1;
7727 GtkWidget *attach_lab2;
7732 GtkWidget *subject_hbox;
7733 GtkWidget *subject_frame;
7734 GtkWidget *subject_entry;
7738 GtkWidget *edit_vbox;
7739 GtkWidget *ruler_hbox;
7741 GtkWidget *scrolledwin;
7743 GtkTextBuffer *buffer;
7744 GtkClipboard *clipboard;
7746 UndoMain *undostruct;
7748 GtkWidget *popupmenu;
7749 GtkWidget *tmpl_menu;
7750 GtkActionGroup *action_group = NULL;
7753 GtkAspell * gtkaspell = NULL;
7756 static GdkGeometry geometry;
7758 cm_return_val_if_fail(account != NULL, NULL);
7760 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7761 &default_header_bgcolor);
7762 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7763 &default_header_color);
7765 debug_print("Creating compose window...\n");
7766 compose = g_new0(Compose, 1);
7768 compose->batch = batch;
7769 compose->account = account;
7770 compose->folder = folder;
7772 compose->mutex = cm_mutex_new();
7773 compose->set_cursor_pos = -1;
7775 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7777 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7778 gtk_widget_set_size_request(window, prefs_common.compose_width,
7779 prefs_common.compose_height);
7781 if (!geometry.max_width) {
7782 geometry.max_width = gdk_screen_width();
7783 geometry.max_height = gdk_screen_height();
7786 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7787 &geometry, GDK_HINT_MAX_SIZE);
7788 if (!geometry.min_width) {
7789 geometry.min_width = 600;
7790 geometry.min_height = 440;
7792 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7793 &geometry, GDK_HINT_MIN_SIZE);
7795 #ifndef GENERIC_UMPC
7796 if (compose_force_window_origin)
7797 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7798 prefs_common.compose_y);
7800 g_signal_connect(G_OBJECT(window), "delete_event",
7801 G_CALLBACK(compose_delete_cb), compose);
7802 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7803 gtk_widget_realize(window);
7805 gtkut_widget_set_composer_icon(window);
7807 vbox = gtk_vbox_new(FALSE, 0);
7808 gtk_container_add(GTK_CONTAINER(window), vbox);
7810 compose->ui_manager = gtk_ui_manager_new();
7811 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7812 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7813 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7814 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7815 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7816 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7817 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7818 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7819 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7820 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7939 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)
7940 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)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7946 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)
7947 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)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7952 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)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7956 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)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7962 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)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7969 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)
7970 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)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7972 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7978 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7983 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)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7986 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7993 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7995 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7996 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7999 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8001 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8002 gtk_widget_show_all(menubar);
8004 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8005 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8007 if (prefs_common.toolbar_detachable) {
8008 handlebox = gtk_handle_box_new();
8010 handlebox = gtk_hbox_new(FALSE, 0);
8012 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8014 gtk_widget_realize(handlebox);
8015 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8018 vbox2 = gtk_vbox_new(FALSE, 2);
8019 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8020 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8023 notebook = gtk_notebook_new();
8024 gtk_widget_show(notebook);
8026 /* header labels and entries */
8027 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8028 compose_create_header(compose),
8029 gtk_label_new_with_mnemonic(_("Hea_der")));
8030 /* attachment list */
8031 attach_hbox = gtk_hbox_new(FALSE, 0);
8032 gtk_widget_show(attach_hbox);
8034 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8035 gtk_widget_show(attach_lab1);
8036 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8038 attach_lab2 = gtk_label_new("");
8039 gtk_widget_show(attach_lab2);
8040 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8042 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8043 compose_create_attach(compose),
8046 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8047 compose_create_others(compose),
8048 gtk_label_new_with_mnemonic(_("Othe_rs")));
8051 subject_hbox = gtk_hbox_new(FALSE, 0);
8052 gtk_widget_show(subject_hbox);
8054 subject_frame = gtk_frame_new(NULL);
8055 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8056 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8057 gtk_widget_show(subject_frame);
8059 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8060 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8061 gtk_widget_show(subject);
8063 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8064 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8065 gtk_widget_show(label);
8068 subject_entry = claws_spell_entry_new();
8070 subject_entry = gtk_entry_new();
8072 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8073 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8074 G_CALLBACK(compose_grab_focus_cb), compose);
8075 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8076 gtk_widget_show(subject_entry);
8077 compose->subject_entry = subject_entry;
8078 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8080 edit_vbox = gtk_vbox_new(FALSE, 0);
8082 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8085 ruler_hbox = gtk_hbox_new(FALSE, 0);
8086 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8088 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8089 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8090 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8094 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8095 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8096 GTK_POLICY_AUTOMATIC,
8097 GTK_POLICY_AUTOMATIC);
8098 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8100 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8102 text = gtk_text_view_new();
8103 if (prefs_common.show_compose_margin) {
8104 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8105 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8107 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8108 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8109 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8110 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8111 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8113 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8114 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8115 G_CALLBACK(compose_edit_size_alloc),
8117 g_signal_connect(G_OBJECT(buffer), "changed",
8118 G_CALLBACK(compose_changed_cb), compose);
8119 g_signal_connect(G_OBJECT(text), "grab_focus",
8120 G_CALLBACK(compose_grab_focus_cb), compose);
8121 g_signal_connect(G_OBJECT(buffer), "insert_text",
8122 G_CALLBACK(text_inserted), compose);
8123 g_signal_connect(G_OBJECT(text), "button_press_event",
8124 G_CALLBACK(text_clicked), compose);
8125 g_signal_connect(G_OBJECT(text), "popup-menu",
8126 G_CALLBACK(compose_popup_menu), compose);
8127 g_signal_connect(G_OBJECT(subject_entry), "changed",
8128 G_CALLBACK(compose_changed_cb), compose);
8129 g_signal_connect(G_OBJECT(subject_entry), "activate",
8130 G_CALLBACK(compose_subject_entry_activated), compose);
8133 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8134 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8135 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8136 g_signal_connect(G_OBJECT(text), "drag_data_received",
8137 G_CALLBACK(compose_insert_drag_received_cb),
8139 g_signal_connect(G_OBJECT(text), "drag-drop",
8140 G_CALLBACK(compose_drag_drop),
8142 g_signal_connect(G_OBJECT(text), "key-press-event",
8143 G_CALLBACK(completion_set_focus_to_subject),
8145 gtk_widget_show_all(vbox);
8147 /* pane between attach clist and text */
8148 paned = gtk_vpaned_new();
8149 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8150 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8151 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8152 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8153 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8154 G_CALLBACK(compose_notebook_size_alloc), paned);
8156 gtk_widget_show_all(paned);
8159 if (prefs_common.textfont) {
8160 PangoFontDescription *font_desc;
8162 font_desc = pango_font_description_from_string
8163 (prefs_common.textfont);
8165 gtk_widget_modify_font(text, font_desc);
8166 pango_font_description_free(font_desc);
8170 gtk_action_group_add_actions(action_group, compose_popup_entries,
8171 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8179 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8181 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8182 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8183 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8185 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8187 undostruct = undo_init(text);
8188 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8191 address_completion_start(window);
8193 compose->window = window;
8194 compose->vbox = vbox;
8195 compose->menubar = menubar;
8196 compose->handlebox = handlebox;
8198 compose->vbox2 = vbox2;
8200 compose->paned = paned;
8202 compose->attach_label = attach_lab2;
8204 compose->notebook = notebook;
8205 compose->edit_vbox = edit_vbox;
8206 compose->ruler_hbox = ruler_hbox;
8207 compose->ruler = ruler;
8208 compose->scrolledwin = scrolledwin;
8209 compose->text = text;
8211 compose->focused_editable = NULL;
8213 compose->popupmenu = popupmenu;
8215 compose->tmpl_menu = tmpl_menu;
8217 compose->mode = mode;
8218 compose->rmode = mode;
8220 compose->targetinfo = NULL;
8221 compose->replyinfo = NULL;
8222 compose->fwdinfo = NULL;
8224 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8225 g_str_equal, (GDestroyNotify) g_free, NULL);
8227 compose->replyto = NULL;
8229 compose->bcc = NULL;
8230 compose->followup_to = NULL;
8232 compose->ml_post = NULL;
8234 compose->inreplyto = NULL;
8235 compose->references = NULL;
8236 compose->msgid = NULL;
8237 compose->boundary = NULL;
8239 compose->autowrap = prefs_common.autowrap;
8240 compose->autoindent = prefs_common.auto_indent;
8241 compose->use_signing = FALSE;
8242 compose->use_encryption = FALSE;
8243 compose->privacy_system = NULL;
8244 compose->encdata = NULL;
8246 compose->modified = FALSE;
8248 compose->return_receipt = FALSE;
8250 compose->to_list = NULL;
8251 compose->newsgroup_list = NULL;
8253 compose->undostruct = undostruct;
8255 compose->sig_str = NULL;
8257 compose->exteditor_file = NULL;
8258 compose->exteditor_pid = -1;
8259 compose->exteditor_tag = -1;
8260 compose->exteditor_socket = NULL;
8261 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8263 compose->folder_update_callback_id =
8264 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8265 compose_update_folder_hook,
8266 (gpointer) compose);
8269 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8270 if (mode != COMPOSE_REDIRECT) {
8271 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8272 strcmp(prefs_common.dictionary, "")) {
8273 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8274 prefs_common.alt_dictionary,
8275 conv_get_locale_charset_str(),
8276 prefs_common.color[COL_MISSPELLED],
8277 prefs_common.check_while_typing,
8278 prefs_common.recheck_when_changing_dict,
8279 prefs_common.use_alternate,
8280 prefs_common.use_both_dicts,
8281 GTK_TEXT_VIEW(text),
8282 GTK_WINDOW(compose->window),
8283 compose_dict_changed,
8284 compose_spell_menu_changed,
8287 alertpanel_error(_("Spell checker could not "
8289 gtkaspell_checkers_strerror());
8290 gtkaspell_checkers_reset_error();
8292 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8296 compose->gtkaspell = gtkaspell;
8297 compose_spell_menu_changed(compose);
8298 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8301 compose_select_account(compose, account, TRUE);
8303 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8304 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8306 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8307 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8309 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8310 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8312 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8313 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8315 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8316 if (account->protocol != A_NNTP)
8317 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8318 prefs_common_translated_header_name("To:"));
8320 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8321 prefs_common_translated_header_name("Newsgroups:"));
8323 #ifndef USE_ALT_ADDRBOOK
8324 addressbook_set_target_compose(compose);
8326 if (mode != COMPOSE_REDIRECT)
8327 compose_set_template_menu(compose);
8329 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8332 compose_list = g_list_append(compose_list, compose);
8334 if (!prefs_common.show_ruler)
8335 gtk_widget_hide(ruler_hbox);
8337 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8340 compose->priority = PRIORITY_NORMAL;
8341 compose_update_priority_menu_item(compose);
8343 compose_set_out_encoding(compose);
8346 compose_update_actions_menu(compose);
8348 /* Privacy Systems menu */
8349 compose_update_privacy_systems_menu(compose);
8351 activate_privacy_system(compose, account, TRUE);
8352 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8354 gtk_widget_realize(window);
8356 gtk_widget_show(window);
8362 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8367 GtkWidget *optmenubox;
8368 GtkWidget *fromlabel;
8371 GtkWidget *from_name = NULL;
8373 gint num = 0, def_menu = 0;
8375 accounts = account_get_list();
8376 cm_return_val_if_fail(accounts != NULL, NULL);
8378 optmenubox = gtk_event_box_new();
8379 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8380 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8382 hbox = gtk_hbox_new(FALSE, 4);
8383 from_name = gtk_entry_new();
8385 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8386 G_CALLBACK(compose_grab_focus_cb), compose);
8387 g_signal_connect_after(G_OBJECT(from_name), "activate",
8388 G_CALLBACK(from_name_activate_cb), optmenu);
8390 for (; accounts != NULL; accounts = accounts->next, num++) {
8391 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8392 gchar *name, *from = NULL;
8394 if (ac == compose->account) def_menu = num;
8396 name = g_markup_printf_escaped("<i>%s</i>",
8399 if (ac == compose->account) {
8400 if (ac->name && *ac->name) {
8402 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8403 from = g_strdup_printf("%s <%s>",
8405 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8407 from = g_strdup_printf("%s",
8409 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8411 if (cur_account != compose->account) {
8412 gtk_widget_modify_base(
8413 GTK_WIDGET(from_name),
8414 GTK_STATE_NORMAL, &default_header_bgcolor);
8415 gtk_widget_modify_text(
8416 GTK_WIDGET(from_name),
8417 GTK_STATE_NORMAL, &default_header_color);
8420 COMBOBOX_ADD(menu, name, ac->account_id);
8425 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8427 g_signal_connect(G_OBJECT(optmenu), "changed",
8428 G_CALLBACK(account_activated),
8430 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8431 G_CALLBACK(compose_entry_popup_extend),
8434 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8435 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8437 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8438 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8439 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8441 /* Putting only the GtkEntry into focus chain of parent hbox causes
8442 * the account selector combobox next to it to be unreachable when
8443 * navigating widgets in GtkTable with up/down arrow keys.
8444 * Note: gtk_widget_set_can_focus() was not enough. */
8446 l = g_list_prepend(l, from_name);
8447 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8450 CLAWS_SET_TIP(optmenubox,
8451 _("Account to use for this email"));
8452 CLAWS_SET_TIP(from_name,
8453 _("Sender address to be used"));
8455 compose->account_combo = optmenu;
8456 compose->from_name = from_name;
8461 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8463 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8464 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8465 Compose *compose = (Compose *) data;
8467 compose->priority = value;
8471 static void compose_reply_change_mode(Compose *compose,
8474 gboolean was_modified = compose->modified;
8476 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8478 cm_return_if_fail(compose->replyinfo != NULL);
8480 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8482 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8484 if (action == COMPOSE_REPLY_TO_ALL)
8486 if (action == COMPOSE_REPLY_TO_SENDER)
8488 if (action == COMPOSE_REPLY_TO_LIST)
8491 compose_remove_header_entries(compose);
8492 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8493 if (compose->account->set_autocc && compose->account->auto_cc)
8494 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8496 if (compose->account->set_autobcc && compose->account->auto_bcc)
8497 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8499 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8500 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8501 compose_show_first_last_header(compose, TRUE);
8502 compose->modified = was_modified;
8503 compose_set_title(compose);
8506 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8508 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8509 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8510 Compose *compose = (Compose *) data;
8513 compose_reply_change_mode(compose, value);
8516 static void compose_update_priority_menu_item(Compose * compose)
8518 GtkWidget *menuitem = NULL;
8519 switch (compose->priority) {
8520 case PRIORITY_HIGHEST:
8521 menuitem = gtk_ui_manager_get_widget
8522 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8525 menuitem = gtk_ui_manager_get_widget
8526 (compose->ui_manager, "/Menu/Options/Priority/High");
8528 case PRIORITY_NORMAL:
8529 menuitem = gtk_ui_manager_get_widget
8530 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8533 menuitem = gtk_ui_manager_get_widget
8534 (compose->ui_manager, "/Menu/Options/Priority/Low");
8536 case PRIORITY_LOWEST:
8537 menuitem = gtk_ui_manager_get_widget
8538 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8541 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8544 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8546 Compose *compose = (Compose *) data;
8548 gboolean can_sign = FALSE, can_encrypt = FALSE;
8550 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8552 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8555 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8556 g_free(compose->privacy_system);
8557 compose->privacy_system = NULL;
8558 g_free(compose->encdata);
8559 compose->encdata = NULL;
8560 if (systemid != NULL) {
8561 compose->privacy_system = g_strdup(systemid);
8563 can_sign = privacy_system_can_sign(systemid);
8564 can_encrypt = privacy_system_can_encrypt(systemid);
8567 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8569 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8570 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8571 if (compose->toolbar->privacy_sign_btn != NULL) {
8572 gtk_widget_set_sensitive(
8573 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8575 gtk_toggle_tool_button_set_active(
8576 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8577 can_sign ? compose->use_signing : FALSE);
8579 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8580 gtk_widget_set_sensitive(
8581 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8583 gtk_toggle_tool_button_set_active(
8584 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8585 can_encrypt ? compose->use_encryption : FALSE);
8589 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8591 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8592 GtkWidget *menuitem = NULL;
8593 GList *children, *amenu;
8594 gboolean can_sign = FALSE, can_encrypt = FALSE;
8595 gboolean found = FALSE;
8597 if (compose->privacy_system != NULL) {
8599 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8600 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8601 cm_return_if_fail(menuitem != NULL);
8603 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8606 while (amenu != NULL) {
8607 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8608 if (systemid != NULL) {
8609 if (strcmp(systemid, compose->privacy_system) == 0 &&
8610 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8611 menuitem = GTK_WIDGET(amenu->data);
8613 can_sign = privacy_system_can_sign(systemid);
8614 can_encrypt = privacy_system_can_encrypt(systemid);
8618 } else if (strlen(compose->privacy_system) == 0 &&
8619 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8620 menuitem = GTK_WIDGET(amenu->data);
8623 can_encrypt = FALSE;
8628 amenu = amenu->next;
8630 g_list_free(children);
8631 if (menuitem != NULL)
8632 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8634 if (warn && !found && strlen(compose->privacy_system)) {
8635 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8636 "will not be able to sign or encrypt this message."),
8637 compose->privacy_system);
8641 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8642 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8643 if (compose->toolbar->privacy_sign_btn != NULL) {
8644 gtk_widget_set_sensitive(
8645 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8648 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8649 gtk_widget_set_sensitive(
8650 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8655 static void compose_set_out_encoding(Compose *compose)
8657 CharSet out_encoding;
8658 const gchar *branch = NULL;
8659 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8661 switch(out_encoding) {
8662 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8663 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8664 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8665 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8666 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8667 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8668 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8669 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8670 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8671 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8672 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8673 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8674 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8675 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8676 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8677 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8678 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8679 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8680 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8681 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8682 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8683 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8684 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8685 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8686 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8687 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8688 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8689 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8690 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8691 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8692 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8693 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8694 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8695 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8697 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8700 static void compose_set_template_menu(Compose *compose)
8702 GSList *tmpl_list, *cur;
8706 tmpl_list = template_get_config();
8708 menu = gtk_menu_new();
8710 gtk_menu_set_accel_group (GTK_MENU (menu),
8711 gtk_ui_manager_get_accel_group(compose->ui_manager));
8712 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8713 Template *tmpl = (Template *)cur->data;
8714 gchar *accel_path = NULL;
8715 item = gtk_menu_item_new_with_label(tmpl->name);
8716 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8717 g_signal_connect(G_OBJECT(item), "activate",
8718 G_CALLBACK(compose_template_activate_cb),
8720 g_object_set_data(G_OBJECT(item), "template", tmpl);
8721 gtk_widget_show(item);
8722 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8723 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8727 gtk_widget_show(menu);
8728 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8731 void compose_update_actions_menu(Compose *compose)
8733 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8736 static void compose_update_privacy_systems_menu(Compose *compose)
8738 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8739 GSList *systems, *cur;
8741 GtkWidget *system_none;
8743 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8744 GtkWidget *privacy_menu = gtk_menu_new();
8746 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8747 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8749 g_signal_connect(G_OBJECT(system_none), "activate",
8750 G_CALLBACK(compose_set_privacy_system_cb), compose);
8752 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8753 gtk_widget_show(system_none);
8755 systems = privacy_get_system_ids();
8756 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8757 gchar *systemid = cur->data;
8759 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8760 widget = gtk_radio_menu_item_new_with_label(group,
8761 privacy_system_get_name(systemid));
8762 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8763 g_strdup(systemid), g_free);
8764 g_signal_connect(G_OBJECT(widget), "activate",
8765 G_CALLBACK(compose_set_privacy_system_cb), compose);
8767 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8768 gtk_widget_show(widget);
8771 g_slist_free(systems);
8772 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8773 gtk_widget_show_all(privacy_menu);
8774 gtk_widget_show_all(privacy_menuitem);
8777 void compose_reflect_prefs_all(void)
8782 for (cur = compose_list; cur != NULL; cur = cur->next) {
8783 compose = (Compose *)cur->data;
8784 compose_set_template_menu(compose);
8788 void compose_reflect_prefs_pixmap_theme(void)
8793 for (cur = compose_list; cur != NULL; cur = cur->next) {
8794 compose = (Compose *)cur->data;
8795 toolbar_update(TOOLBAR_COMPOSE, compose);
8799 static const gchar *compose_quote_char_from_context(Compose *compose)
8801 const gchar *qmark = NULL;
8803 cm_return_val_if_fail(compose != NULL, NULL);
8805 switch (compose->mode) {
8806 /* use forward-specific quote char */
8807 case COMPOSE_FORWARD:
8808 case COMPOSE_FORWARD_AS_ATTACH:
8809 case COMPOSE_FORWARD_INLINE:
8810 if (compose->folder && compose->folder->prefs &&
8811 compose->folder->prefs->forward_with_format)
8812 qmark = compose->folder->prefs->forward_quotemark;
8813 else if (compose->account->forward_with_format)
8814 qmark = compose->account->forward_quotemark;
8816 qmark = prefs_common.fw_quotemark;
8819 /* use reply-specific quote char in all other modes */
8821 if (compose->folder && compose->folder->prefs &&
8822 compose->folder->prefs->reply_with_format)
8823 qmark = compose->folder->prefs->reply_quotemark;
8824 else if (compose->account->reply_with_format)
8825 qmark = compose->account->reply_quotemark;
8827 qmark = prefs_common.quotemark;
8831 if (qmark == NULL || *qmark == '\0')
8837 static void compose_template_apply(Compose *compose, Template *tmpl,
8841 GtkTextBuffer *buffer;
8845 gchar *parsed_str = NULL;
8846 gint cursor_pos = 0;
8847 const gchar *err_msg = _("The body of the template has an error at line %d.");
8850 /* process the body */
8852 text = GTK_TEXT_VIEW(compose->text);
8853 buffer = gtk_text_view_get_buffer(text);
8856 qmark = compose_quote_char_from_context(compose);
8858 if (compose->replyinfo != NULL) {
8861 gtk_text_buffer_set_text(buffer, "", -1);
8862 mark = gtk_text_buffer_get_insert(buffer);
8863 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8865 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8866 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8868 } else if (compose->fwdinfo != NULL) {
8871 gtk_text_buffer_set_text(buffer, "", -1);
8872 mark = gtk_text_buffer_get_insert(buffer);
8873 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8875 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8876 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8879 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8881 GtkTextIter start, end;
8884 gtk_text_buffer_get_start_iter(buffer, &start);
8885 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8886 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8888 /* clear the buffer now */
8890 gtk_text_buffer_set_text(buffer, "", -1);
8892 parsed_str = compose_quote_fmt(compose, dummyinfo,
8893 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8894 procmsg_msginfo_free( &dummyinfo );
8900 gtk_text_buffer_set_text(buffer, "", -1);
8901 mark = gtk_text_buffer_get_insert(buffer);
8902 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8905 if (replace && parsed_str && compose->account->auto_sig)
8906 compose_insert_sig(compose, FALSE);
8908 if (replace && parsed_str) {
8909 gtk_text_buffer_get_start_iter(buffer, &iter);
8910 gtk_text_buffer_place_cursor(buffer, &iter);
8914 cursor_pos = quote_fmt_get_cursor_pos();
8915 compose->set_cursor_pos = cursor_pos;
8916 if (cursor_pos == -1)
8918 gtk_text_buffer_get_start_iter(buffer, &iter);
8919 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8920 gtk_text_buffer_place_cursor(buffer, &iter);
8923 /* process the other fields */
8925 compose_template_apply_fields(compose, tmpl);
8926 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8927 quote_fmt_reset_vartable();
8928 quote_fmtlex_destroy();
8930 compose_changed_cb(NULL, compose);
8933 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8934 gtkaspell_highlight_all(compose->gtkaspell);
8938 static void compose_template_apply_fields_error(const gchar *header)
8943 tr = g_strdup(C_("'%s' stands for a header name",
8944 "Template '%s' format error."));
8945 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8946 alertpanel_error("%s", text);
8952 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8954 MsgInfo* dummyinfo = NULL;
8955 MsgInfo *msginfo = NULL;
8958 if (compose->replyinfo != NULL)
8959 msginfo = compose->replyinfo;
8960 else if (compose->fwdinfo != NULL)
8961 msginfo = compose->fwdinfo;
8963 dummyinfo = compose_msginfo_new_from_compose(compose);
8964 msginfo = dummyinfo;
8967 if (tmpl->from && *tmpl->from != '\0') {
8969 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8970 compose->gtkaspell);
8972 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8974 quote_fmt_scan_string(tmpl->from);
8977 buf = quote_fmt_get_buffer();
8979 compose_template_apply_fields_error("From");
8981 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8984 quote_fmt_reset_vartable();
8985 quote_fmtlex_destroy();
8988 if (tmpl->to && *tmpl->to != '\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->to);
8998 buf = quote_fmt_get_buffer();
9000 compose_template_apply_fields_error("To");
9002 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9005 quote_fmt_reset_vartable();
9006 quote_fmtlex_destroy();
9009 if (tmpl->cc && *tmpl->cc != '\0') {
9011 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9012 compose->gtkaspell);
9014 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9016 quote_fmt_scan_string(tmpl->cc);
9019 buf = quote_fmt_get_buffer();
9021 compose_template_apply_fields_error("Cc");
9023 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9026 quote_fmt_reset_vartable();
9027 quote_fmtlex_destroy();
9030 if (tmpl->bcc && *tmpl->bcc != '\0') {
9032 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9033 compose->gtkaspell);
9035 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9037 quote_fmt_scan_string(tmpl->bcc);
9040 buf = quote_fmt_get_buffer();
9042 compose_template_apply_fields_error("Bcc");
9044 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9047 quote_fmt_reset_vartable();
9048 quote_fmtlex_destroy();
9051 if (tmpl->replyto && *tmpl->replyto != '\0') {
9053 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9054 compose->gtkaspell);
9056 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9058 quote_fmt_scan_string(tmpl->replyto);
9061 buf = quote_fmt_get_buffer();
9063 compose_template_apply_fields_error("Reply-To");
9065 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9068 quote_fmt_reset_vartable();
9069 quote_fmtlex_destroy();
9072 /* process the subject */
9073 if (tmpl->subject && *tmpl->subject != '\0') {
9075 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9076 compose->gtkaspell);
9078 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9080 quote_fmt_scan_string(tmpl->subject);
9083 buf = quote_fmt_get_buffer();
9085 compose_template_apply_fields_error("Subject");
9087 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9090 quote_fmt_reset_vartable();
9091 quote_fmtlex_destroy();
9094 procmsg_msginfo_free( &dummyinfo );
9097 static void compose_destroy(Compose *compose)
9099 GtkAllocation allocation;
9100 GtkTextBuffer *buffer;
9101 GtkClipboard *clipboard;
9103 compose_list = g_list_remove(compose_list, compose);
9106 gboolean enable = TRUE;
9107 g_slist_foreach(compose->passworded_ldap_servers,
9108 _ldap_srv_func, &enable);
9109 g_slist_free(compose->passworded_ldap_servers);
9112 if (compose->updating) {
9113 debug_print("danger, not destroying anything now\n");
9114 compose->deferred_destroy = TRUE;
9118 /* NOTE: address_completion_end() does nothing with the window
9119 * however this may change. */
9120 address_completion_end(compose->window);
9122 slist_free_strings_full(compose->to_list);
9123 slist_free_strings_full(compose->newsgroup_list);
9124 slist_free_strings_full(compose->header_list);
9126 slist_free_strings_full(extra_headers);
9127 extra_headers = NULL;
9129 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9131 g_hash_table_destroy(compose->email_hashtable);
9133 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9134 compose->folder_update_callback_id);
9136 procmsg_msginfo_free(&(compose->targetinfo));
9137 procmsg_msginfo_free(&(compose->replyinfo));
9138 procmsg_msginfo_free(&(compose->fwdinfo));
9140 g_free(compose->replyto);
9141 g_free(compose->cc);
9142 g_free(compose->bcc);
9143 g_free(compose->newsgroups);
9144 g_free(compose->followup_to);
9146 g_free(compose->ml_post);
9148 g_free(compose->inreplyto);
9149 g_free(compose->references);
9150 g_free(compose->msgid);
9151 g_free(compose->boundary);
9153 g_free(compose->redirect_filename);
9154 if (compose->undostruct)
9155 undo_destroy(compose->undostruct);
9157 g_free(compose->sig_str);
9159 g_free(compose->exteditor_file);
9161 g_free(compose->orig_charset);
9163 g_free(compose->privacy_system);
9164 g_free(compose->encdata);
9166 #ifndef USE_ALT_ADDRBOOK
9167 if (addressbook_get_target_compose() == compose)
9168 addressbook_set_target_compose(NULL);
9171 if (compose->gtkaspell) {
9172 gtkaspell_delete(compose->gtkaspell);
9173 compose->gtkaspell = NULL;
9177 if (!compose->batch) {
9178 gtk_widget_get_allocation(compose->window, &allocation);
9179 prefs_common.compose_width = allocation.width;
9180 prefs_common.compose_height = allocation.height;
9183 if (!gtk_widget_get_parent(compose->paned))
9184 gtk_widget_destroy(compose->paned);
9185 gtk_widget_destroy(compose->popupmenu);
9187 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9188 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9189 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9191 gtk_widget_destroy(compose->window);
9192 toolbar_destroy(compose->toolbar);
9193 g_free(compose->toolbar);
9194 cm_mutex_free(compose->mutex);
9198 static void compose_attach_info_free(AttachInfo *ainfo)
9200 g_free(ainfo->file);
9201 g_free(ainfo->content_type);
9202 g_free(ainfo->name);
9203 g_free(ainfo->charset);
9207 static void compose_attach_update_label(Compose *compose)
9212 GtkTreeModel *model;
9216 if (compose == NULL)
9219 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9220 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9221 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9225 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9226 total_size = ainfo->size;
9227 while(gtk_tree_model_iter_next(model, &iter)) {
9228 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9229 total_size += ainfo->size;
9232 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9233 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9237 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9239 Compose *compose = (Compose *)data;
9240 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9241 GtkTreeSelection *selection;
9243 GtkTreeModel *model;
9245 selection = gtk_tree_view_get_selection(tree_view);
9246 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9251 for (cur = sel; cur != NULL; cur = cur->next) {
9252 GtkTreePath *path = cur->data;
9253 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9256 gtk_tree_path_free(path);
9259 for (cur = sel; cur != NULL; cur = cur->next) {
9260 GtkTreeRowReference *ref = cur->data;
9261 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9264 if (gtk_tree_model_get_iter(model, &iter, path))
9265 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9267 gtk_tree_path_free(path);
9268 gtk_tree_row_reference_free(ref);
9272 compose_attach_update_label(compose);
9275 static struct _AttachProperty
9278 GtkWidget *mimetype_entry;
9279 GtkWidget *encoding_optmenu;
9280 GtkWidget *path_entry;
9281 GtkWidget *filename_entry;
9283 GtkWidget *cancel_btn;
9286 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9288 gtk_tree_path_free((GtkTreePath *)ptr);
9291 static void compose_attach_property(GtkAction *action, gpointer data)
9293 Compose *compose = (Compose *)data;
9294 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9296 GtkComboBox *optmenu;
9297 GtkTreeSelection *selection;
9299 GtkTreeModel *model;
9302 static gboolean cancelled;
9304 /* only if one selected */
9305 selection = gtk_tree_view_get_selection(tree_view);
9306 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9309 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9313 path = (GtkTreePath *) sel->data;
9314 gtk_tree_model_get_iter(model, &iter, path);
9315 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9318 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9324 if (!attach_prop.window)
9325 compose_attach_property_create(&cancelled);
9326 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9327 gtk_widget_grab_focus(attach_prop.ok_btn);
9328 gtk_widget_show(attach_prop.window);
9329 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9330 GTK_WINDOW(compose->window));
9332 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9333 if (ainfo->encoding == ENC_UNKNOWN)
9334 combobox_select_by_data(optmenu, ENC_BASE64);
9336 combobox_select_by_data(optmenu, ainfo->encoding);
9338 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9339 ainfo->content_type ? ainfo->content_type : "");
9340 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9341 ainfo->file ? ainfo->file : "");
9342 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9343 ainfo->name ? ainfo->name : "");
9346 const gchar *entry_text;
9348 gchar *cnttype = NULL;
9355 gtk_widget_hide(attach_prop.window);
9356 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9361 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9362 if (*entry_text != '\0') {
9365 text = g_strstrip(g_strdup(entry_text));
9366 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9367 cnttype = g_strdup(text);
9370 alertpanel_error(_("Invalid MIME type."));
9376 ainfo->encoding = combobox_get_active_data(optmenu);
9378 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9379 if (*entry_text != '\0') {
9380 if (is_file_exist(entry_text) &&
9381 (size = get_file_size(entry_text)) > 0)
9382 file = g_strdup(entry_text);
9385 (_("File doesn't exist or is empty."));
9391 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9392 if (*entry_text != '\0') {
9393 g_free(ainfo->name);
9394 ainfo->name = g_strdup(entry_text);
9398 g_free(ainfo->content_type);
9399 ainfo->content_type = cnttype;
9402 g_free(ainfo->file);
9406 ainfo->size = (goffset)size;
9408 /* update tree store */
9409 text = to_human_readable(ainfo->size);
9410 gtk_tree_model_get_iter(model, &iter, path);
9411 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9412 COL_MIMETYPE, ainfo->content_type,
9414 COL_NAME, ainfo->name,
9415 COL_CHARSET, ainfo->charset,
9421 gtk_tree_path_free(path);
9424 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9426 label = gtk_label_new(str); \
9427 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9428 GTK_FILL, 0, 0, 0); \
9429 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9431 entry = gtk_entry_new(); \
9432 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9433 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9436 static void compose_attach_property_create(gboolean *cancelled)
9442 GtkWidget *mimetype_entry;
9445 GtkListStore *optmenu_menu;
9446 GtkWidget *path_entry;
9447 GtkWidget *filename_entry;
9450 GtkWidget *cancel_btn;
9451 GList *mime_type_list, *strlist;
9454 debug_print("Creating attach_property window...\n");
9456 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9457 gtk_widget_set_size_request(window, 480, -1);
9458 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9459 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9460 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9461 g_signal_connect(G_OBJECT(window), "delete_event",
9462 G_CALLBACK(attach_property_delete_event),
9464 g_signal_connect(G_OBJECT(window), "key_press_event",
9465 G_CALLBACK(attach_property_key_pressed),
9468 vbox = gtk_vbox_new(FALSE, 8);
9469 gtk_container_add(GTK_CONTAINER(window), vbox);
9471 table = gtk_table_new(4, 2, FALSE);
9472 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9473 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9474 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9476 label = gtk_label_new(_("MIME type"));
9477 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9479 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9480 #if !GTK_CHECK_VERSION(2, 24, 0)
9481 mimetype_entry = gtk_combo_box_entry_new_text();
9483 mimetype_entry = gtk_combo_box_text_new_with_entry();
9485 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9486 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9488 /* stuff with list */
9489 mime_type_list = procmime_get_mime_type_list();
9491 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9492 MimeType *type = (MimeType *) mime_type_list->data;
9495 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9497 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9500 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9501 (GCompareFunc)strcmp2);
9504 for (mime_type_list = strlist; mime_type_list != NULL;
9505 mime_type_list = mime_type_list->next) {
9506 #if !GTK_CHECK_VERSION(2, 24, 0)
9507 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9509 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9511 g_free(mime_type_list->data);
9513 g_list_free(strlist);
9514 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9515 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9517 label = gtk_label_new(_("Encoding"));
9518 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9520 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9522 hbox = gtk_hbox_new(FALSE, 0);
9523 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9524 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9526 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9527 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9529 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9530 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9531 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9532 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9533 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9535 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9537 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9538 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9540 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9541 &ok_btn, GTK_STOCK_OK,
9543 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9544 gtk_widget_grab_default(ok_btn);
9546 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9547 G_CALLBACK(attach_property_ok),
9549 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9550 G_CALLBACK(attach_property_cancel),
9553 gtk_widget_show_all(vbox);
9555 attach_prop.window = window;
9556 attach_prop.mimetype_entry = mimetype_entry;
9557 attach_prop.encoding_optmenu = optmenu;
9558 attach_prop.path_entry = path_entry;
9559 attach_prop.filename_entry = filename_entry;
9560 attach_prop.ok_btn = ok_btn;
9561 attach_prop.cancel_btn = cancel_btn;
9564 #undef SET_LABEL_AND_ENTRY
9566 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9572 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9578 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9579 gboolean *cancelled)
9587 static gboolean attach_property_key_pressed(GtkWidget *widget,
9589 gboolean *cancelled)
9591 if (event && event->keyval == GDK_KEY_Escape) {
9595 if (event && event->keyval == GDK_KEY_Return) {
9603 static void compose_exec_ext_editor(Compose *compose)
9608 GdkNativeWindow socket_wid = 0;
9612 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9613 G_DIR_SEPARATOR, compose);
9615 if (compose_get_ext_editor_uses_socket()) {
9616 /* Only allow one socket */
9617 if (compose->exteditor_socket != NULL) {
9618 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9619 /* Move the focus off of the socket */
9620 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9625 /* Create the receiving GtkSocket */
9626 socket = gtk_socket_new ();
9627 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9628 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9630 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9631 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9632 /* Realize the socket so that we can use its ID */
9633 gtk_widget_realize(socket);
9634 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9635 compose->exteditor_socket = socket;
9638 if (pipe(pipe_fds) < 0) {
9644 if ((pid = fork()) < 0) {
9651 /* close the write side of the pipe */
9654 compose->exteditor_file = g_strdup(tmp);
9655 compose->exteditor_pid = pid;
9657 compose_set_ext_editor_sensitive(compose, FALSE);
9660 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9662 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9664 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9668 } else { /* process-monitoring process */
9674 /* close the read side of the pipe */
9677 if (compose_write_body_to_file(compose, tmp) < 0) {
9678 fd_write_all(pipe_fds[1], "2\n", 2);
9682 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9684 fd_write_all(pipe_fds[1], "1\n", 2);
9688 /* wait until editor is terminated */
9689 waitpid(pid_ed, NULL, 0);
9691 fd_write_all(pipe_fds[1], "0\n", 2);
9698 #endif /* G_OS_UNIX */
9701 static gboolean compose_can_autosave(Compose *compose)
9703 if (compose->privacy_system && compose->use_encryption)
9704 return prefs_common.autosave && prefs_common.autosave_encrypted;
9706 return prefs_common.autosave;
9710 static gboolean compose_get_ext_editor_cmd_valid()
9712 gboolean has_s = FALSE;
9713 gboolean has_w = FALSE;
9714 const gchar *p = prefs_common_get_ext_editor_cmd();
9717 while ((p = strchr(p, '%'))) {
9723 } else if (*p == 'w') {
9734 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9741 cm_return_val_if_fail(file != NULL, -1);
9743 if ((pid = fork()) < 0) {
9748 if (pid != 0) return pid;
9750 /* grandchild process */
9752 if (setpgid(0, getppid()))
9755 if (compose_get_ext_editor_cmd_valid()) {
9756 if (compose_get_ext_editor_uses_socket()) {
9757 p = g_strdup(prefs_common_get_ext_editor_cmd());
9758 s = strstr(p, "%w");
9760 if (strstr(p, "%s") < s)
9761 buf = g_strdup_printf(p, file, socket_wid);
9763 buf = g_strdup_printf(p, socket_wid, file);
9766 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9769 if (prefs_common_get_ext_editor_cmd())
9770 g_warning("External editor command-line is invalid: '%s'",
9771 prefs_common_get_ext_editor_cmd());
9772 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9775 cmdline = strsplit_with_quote(buf, " ", 0);
9777 execvp(cmdline[0], cmdline);
9780 g_strfreev(cmdline);
9785 static gboolean compose_ext_editor_kill(Compose *compose)
9787 pid_t pgid = compose->exteditor_pid * -1;
9790 ret = kill(pgid, 0);
9792 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9796 msg = g_strdup_printf
9797 (_("The external editor is still working.\n"
9798 "Force terminating the process?\n"
9799 "process group id: %d"), -pgid);
9800 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9801 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9805 if (val == G_ALERTALTERNATE) {
9806 g_source_remove(compose->exteditor_tag);
9807 g_io_channel_shutdown(compose->exteditor_ch,
9809 g_io_channel_unref(compose->exteditor_ch);
9811 if (kill(pgid, SIGTERM) < 0) perror("kill");
9812 waitpid(compose->exteditor_pid, NULL, 0);
9814 g_warning("Terminated process group id: %d. "
9815 "Temporary file: %s", -pgid, compose->exteditor_file);
9817 compose_set_ext_editor_sensitive(compose, TRUE);
9819 g_free(compose->exteditor_file);
9820 compose->exteditor_file = NULL;
9821 compose->exteditor_pid = -1;
9822 compose->exteditor_ch = NULL;
9823 compose->exteditor_tag = -1;
9831 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9835 Compose *compose = (Compose *)data;
9838 debug_print("Compose: input from monitoring process\n");
9840 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9845 g_io_channel_shutdown(source, FALSE, NULL);
9846 g_io_channel_unref(source);
9848 waitpid(compose->exteditor_pid, NULL, 0);
9850 if (buf[0] == '0') { /* success */
9851 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9852 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9853 GtkTextIter start, end;
9856 gtk_text_buffer_set_text(buffer, "", -1);
9857 compose_insert_file(compose, compose->exteditor_file);
9858 compose_changed_cb(NULL, compose);
9860 /* Check if we should save the draft or not */
9861 if (compose_can_autosave(compose))
9862 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9864 if (claws_unlink(compose->exteditor_file) < 0)
9865 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9867 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9868 gtk_text_buffer_get_start_iter(buffer, &start);
9869 gtk_text_buffer_get_end_iter(buffer, &end);
9870 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9871 if (chars && strlen(chars) > 0)
9872 compose->modified = TRUE;
9874 } else if (buf[0] == '1') { /* failed */
9875 g_warning("Couldn't exec external editor");
9876 if (claws_unlink(compose->exteditor_file) < 0)
9877 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9878 } else if (buf[0] == '2') {
9879 g_warning("Couldn't write to file");
9880 } else if (buf[0] == '3') {
9881 g_warning("Pipe read failed");
9884 compose_set_ext_editor_sensitive(compose, TRUE);
9886 g_free(compose->exteditor_file);
9887 compose->exteditor_file = NULL;
9888 compose->exteditor_pid = -1;
9889 compose->exteditor_ch = NULL;
9890 compose->exteditor_tag = -1;
9891 if (compose->exteditor_socket) {
9892 gtk_widget_destroy(compose->exteditor_socket);
9893 compose->exteditor_socket = NULL;
9900 static char *ext_editor_menu_entries[] = {
9901 "Menu/Message/Send",
9902 "Menu/Message/SendLater",
9903 "Menu/Message/InsertFile",
9904 "Menu/Message/InsertSig",
9905 "Menu/Message/ReplaceSig",
9906 "Menu/Message/Save",
9907 "Menu/Message/Print",
9912 "Menu/Tools/ShowRuler",
9913 "Menu/Tools/Actions",
9918 static void compose_set_ext_editor_sensitive(Compose *compose,
9923 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9924 cm_menu_set_sensitive_full(compose->ui_manager,
9925 ext_editor_menu_entries[i], sensitive);
9928 if (compose_get_ext_editor_uses_socket()) {
9930 if (compose->exteditor_socket)
9931 gtk_widget_hide(compose->exteditor_socket);
9932 gtk_widget_show(compose->scrolledwin);
9933 if (prefs_common.show_ruler)
9934 gtk_widget_show(compose->ruler_hbox);
9935 /* Fix the focus, as it doesn't go anywhere when the
9936 * socket is hidden or destroyed */
9937 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9939 g_assert (compose->exteditor_socket != NULL);
9940 /* Fix the focus, as it doesn't go anywhere when the
9941 * edit box is hidden */
9942 if (gtk_widget_is_focus(compose->text))
9943 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9944 gtk_widget_hide(compose->scrolledwin);
9945 gtk_widget_hide(compose->ruler_hbox);
9946 gtk_widget_show(compose->exteditor_socket);
9949 gtk_widget_set_sensitive(compose->text, sensitive);
9951 if (compose->toolbar->send_btn)
9952 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9953 if (compose->toolbar->sendl_btn)
9954 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9955 if (compose->toolbar->draft_btn)
9956 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9957 if (compose->toolbar->insert_btn)
9958 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9959 if (compose->toolbar->sig_btn)
9960 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9961 if (compose->toolbar->exteditor_btn)
9962 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9963 if (compose->toolbar->linewrap_current_btn)
9964 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9965 if (compose->toolbar->linewrap_all_btn)
9966 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9969 static gboolean compose_get_ext_editor_uses_socket()
9971 return (prefs_common_get_ext_editor_cmd() &&
9972 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9975 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9977 compose->exteditor_socket = NULL;
9978 /* returning FALSE allows destruction of the socket */
9981 #endif /* G_OS_UNIX */
9984 * compose_undo_state_changed:
9986 * Change the sensivity of the menuentries undo and redo
9988 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9989 gint redo_state, gpointer data)
9991 Compose *compose = (Compose *)data;
9993 switch (undo_state) {
9994 case UNDO_STATE_TRUE:
9995 if (!undostruct->undo_state) {
9996 undostruct->undo_state = TRUE;
9997 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
10000 case UNDO_STATE_FALSE:
10001 if (undostruct->undo_state) {
10002 undostruct->undo_state = FALSE;
10003 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
10006 case UNDO_STATE_UNCHANGED:
10008 case UNDO_STATE_REFRESH:
10009 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
10012 g_warning("Undo state not recognized");
10016 switch (redo_state) {
10017 case UNDO_STATE_TRUE:
10018 if (!undostruct->redo_state) {
10019 undostruct->redo_state = TRUE;
10020 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10023 case UNDO_STATE_FALSE:
10024 if (undostruct->redo_state) {
10025 undostruct->redo_state = FALSE;
10026 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10029 case UNDO_STATE_UNCHANGED:
10031 case UNDO_STATE_REFRESH:
10032 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10035 g_warning("Redo state not recognized");
10040 /* callback functions */
10042 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10043 GtkAllocation *allocation,
10046 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10049 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10050 * includes "non-client" (windows-izm) in calculation, so this calculation
10051 * may not be accurate.
10053 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10054 GtkAllocation *allocation,
10055 GtkSHRuler *shruler)
10057 if (prefs_common.show_ruler) {
10058 gint char_width = 0, char_height = 0;
10059 gint line_width_in_chars;
10061 gtkut_get_font_size(GTK_WIDGET(widget),
10062 &char_width, &char_height);
10063 line_width_in_chars =
10064 (allocation->width - allocation->x) / char_width;
10066 /* got the maximum */
10067 gtk_shruler_set_range(GTK_SHRULER(shruler),
10068 0.0, line_width_in_chars, 0);
10077 ComposePrefType type;
10078 gboolean entry_marked;
10079 } HeaderEntryState;
10081 static void account_activated(GtkComboBox *optmenu, gpointer data)
10083 Compose *compose = (Compose *)data;
10086 gchar *folderidentifier;
10087 gint account_id = 0;
10088 GtkTreeModel *menu;
10090 GSList *list, *saved_list = NULL;
10091 HeaderEntryState *state;
10093 /* Get ID of active account in the combo box */
10094 menu = gtk_combo_box_get_model(optmenu);
10095 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10096 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10098 ac = account_find_from_id(account_id);
10099 cm_return_if_fail(ac != NULL);
10101 if (ac != compose->account) {
10102 compose_select_account(compose, ac, FALSE);
10104 for (list = compose->header_list; list; list = list->next) {
10105 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10107 if (hentry->type == PREF_ACCOUNT || !list->next) {
10108 compose_destroy_headerentry(compose, hentry);
10111 state = g_malloc0(sizeof(HeaderEntryState));
10112 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10113 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10114 state->entry = gtk_editable_get_chars(
10115 GTK_EDITABLE(hentry->entry), 0, -1);
10116 state->type = hentry->type;
10118 saved_list = g_slist_append(saved_list, state);
10119 compose_destroy_headerentry(compose, hentry);
10122 compose->header_last = NULL;
10123 g_slist_free(compose->header_list);
10124 compose->header_list = NULL;
10125 compose->header_nextrow = 1;
10126 compose_create_header_entry(compose);
10128 if (ac->set_autocc && ac->auto_cc)
10129 compose_entry_append(compose, ac->auto_cc,
10130 COMPOSE_CC, PREF_ACCOUNT);
10131 if (ac->set_autobcc && ac->auto_bcc)
10132 compose_entry_append(compose, ac->auto_bcc,
10133 COMPOSE_BCC, PREF_ACCOUNT);
10134 if (ac->set_autoreplyto && ac->auto_replyto)
10135 compose_entry_append(compose, ac->auto_replyto,
10136 COMPOSE_REPLYTO, PREF_ACCOUNT);
10138 for (list = saved_list; list; list = list->next) {
10139 state = (HeaderEntryState *) list->data;
10141 compose_add_header_entry(compose, state->header,
10142 state->entry, state->type);
10144 g_free(state->header);
10145 g_free(state->entry);
10148 g_slist_free(saved_list);
10150 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10151 (ac->protocol == A_NNTP) ?
10152 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10155 /* Set message save folder */
10156 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10159 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10160 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10162 compose_set_save_to(compose, NULL);
10163 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10164 folderidentifier = folder_item_get_identifier(account_get_special_folder
10165 (compose->account, F_OUTBOX));
10166 compose_set_save_to(compose, folderidentifier);
10167 g_free(folderidentifier);
10171 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10172 GtkTreeViewColumn *column, Compose *compose)
10174 compose_attach_property(NULL, compose);
10177 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10180 Compose *compose = (Compose *)data;
10181 GtkTreeSelection *attach_selection;
10182 gint attach_nr_selected;
10185 if (!event) return FALSE;
10187 if (event->button == 3) {
10188 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10189 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10191 /* If no rows, or just one row is selected, right-click should
10192 * open menu relevant to the row being right-clicked on. We
10193 * achieve that by selecting the clicked row first. If more
10194 * than one row is selected, we shouldn't modify the selection,
10195 * as user may want to remove selected rows (attachments). */
10196 if (attach_nr_selected < 2) {
10197 gtk_tree_selection_unselect_all(attach_selection);
10198 attach_nr_selected = 0;
10199 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10200 event->x, event->y, &path, NULL, NULL, NULL);
10201 if (path != NULL) {
10202 gtk_tree_selection_select_path(attach_selection, path);
10203 gtk_tree_path_free(path);
10204 attach_nr_selected++;
10208 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10209 /* Properties menu item makes no sense with more than one row
10210 * selected, the properties dialog can only edit one attachment. */
10211 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10213 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10214 NULL, NULL, event->button, event->time);
10221 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10224 Compose *compose = (Compose *)data;
10226 if (!event) return FALSE;
10228 switch (event->keyval) {
10229 case GDK_KEY_Delete:
10230 compose_attach_remove_selected(NULL, compose);
10236 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10238 toolbar_comp_set_sensitive(compose, allow);
10239 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10242 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10245 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10246 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10248 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10252 static void compose_send_cb(GtkAction *action, gpointer data)
10254 Compose *compose = (Compose *)data;
10257 if (compose->exteditor_tag != -1) {
10258 debug_print("ignoring send: external editor still open\n");
10262 if (prefs_common.work_offline &&
10263 !inc_offline_should_override(TRUE,
10264 _("Claws Mail needs network access in order "
10265 "to send this email.")))
10268 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10269 g_source_remove(compose->draft_timeout_tag);
10270 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10273 compose_send(compose);
10276 static void compose_send_later_cb(GtkAction *action, gpointer data)
10278 Compose *compose = (Compose *)data;
10282 compose_allow_user_actions(compose, FALSE);
10283 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10284 compose_allow_user_actions(compose, TRUE);
10288 compose_close(compose);
10289 } else if (val == -1) {
10290 alertpanel_error(_("Could not queue message."));
10291 } else if (val == -2) {
10292 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
10293 } else if (val == -3) {
10294 if (privacy_peek_error())
10295 alertpanel_error(_("Could not queue message for sending:\n\n"
10296 "Signature failed: %s"), privacy_get_error());
10297 } else if (val == -4) {
10298 alertpanel_error(_("Could not queue message for sending:\n\n"
10299 "Charset conversion failed."));
10300 } else if (val == -5) {
10301 alertpanel_error(_("Could not queue message for sending:\n\n"
10302 "Couldn't get recipient encryption key."));
10303 } else if (val == -6) {
10306 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10309 #define DRAFTED_AT_EXIT "drafted_at_exit"
10310 static void compose_register_draft(MsgInfo *info)
10312 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10313 DRAFTED_AT_EXIT, NULL);
10314 FILE *fp = g_fopen(filepath, "ab");
10317 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10325 gboolean compose_draft (gpointer data, guint action)
10327 Compose *compose = (Compose *)data;
10332 MsgFlags flag = {0, 0};
10333 static gboolean lock = FALSE;
10334 MsgInfo *newmsginfo;
10336 gboolean target_locked = FALSE;
10337 gboolean err = FALSE;
10339 if (lock) return FALSE;
10341 if (compose->sending)
10344 draft = account_get_special_folder(compose->account, F_DRAFT);
10345 cm_return_val_if_fail(draft != NULL, FALSE);
10347 if (!g_mutex_trylock(compose->mutex)) {
10348 /* we don't want to lock the mutex once it's available,
10349 * because as the only other part of compose.c locking
10350 * it is compose_close - which means once unlocked,
10351 * the compose struct will be freed */
10352 debug_print("couldn't lock mutex, probably sending\n");
10358 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10359 G_DIR_SEPARATOR, compose);
10360 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10361 FILE_OP_ERROR(tmp, "fopen");
10365 /* chmod for security */
10366 if (change_file_mode_rw(fp, tmp) < 0) {
10367 FILE_OP_ERROR(tmp, "chmod");
10368 g_warning("can't change file mode");
10371 /* Save draft infos */
10372 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10373 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10375 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10376 gchar *savefolderid;
10378 savefolderid = compose_get_save_to(compose);
10379 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10380 g_free(savefolderid);
10382 if (compose->return_receipt) {
10383 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10385 if (compose->privacy_system) {
10386 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10387 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10388 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10391 /* Message-ID of message replying to */
10392 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10393 gchar *folderid = NULL;
10395 if (compose->replyinfo->folder)
10396 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10397 if (folderid == NULL)
10398 folderid = g_strdup("NULL");
10400 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10403 /* Message-ID of message forwarding to */
10404 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10405 gchar *folderid = NULL;
10407 if (compose->fwdinfo->folder)
10408 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10409 if (folderid == NULL)
10410 folderid = g_strdup("NULL");
10412 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10416 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10417 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10419 sheaders = compose_get_manual_headers_info(compose);
10420 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10423 /* end of headers */
10424 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10431 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10435 if (fclose(fp) == EOF) {
10439 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10440 if (compose->targetinfo) {
10441 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10443 flag.perm_flags |= MSG_LOCKED;
10445 flag.tmp_flags = MSG_DRAFT;
10447 folder_item_scan(draft);
10448 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10449 MsgInfo *tmpinfo = NULL;
10450 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10451 if (compose->msgid) {
10452 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10455 msgnum = tmpinfo->msgnum;
10456 procmsg_msginfo_free(&tmpinfo);
10457 debug_print("got draft msgnum %d from scanning\n", msgnum);
10459 debug_print("didn't get draft msgnum after scanning\n");
10462 debug_print("got draft msgnum %d from adding\n", msgnum);
10468 if (action != COMPOSE_AUTO_SAVE) {
10469 if (action != COMPOSE_DRAFT_FOR_EXIT)
10470 alertpanel_error(_("Could not save draft."));
10473 gtkut_window_popup(compose->window);
10474 val = alertpanel_full(_("Could not save draft"),
10475 _("Could not save draft.\n"
10476 "Do you want to cancel exit or discard this email?"),
10477 _("_Cancel exit"), _("_Discard email"), NULL,
10478 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10479 if (val == G_ALERTALTERNATE) {
10481 g_mutex_unlock(compose->mutex); /* must be done before closing */
10482 compose_close(compose);
10486 g_mutex_unlock(compose->mutex); /* must be done before closing */
10495 if (compose->mode == COMPOSE_REEDIT) {
10496 compose_remove_reedit_target(compose, TRUE);
10499 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10502 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10504 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10506 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10507 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10508 procmsg_msginfo_set_flags(newmsginfo, 0,
10509 MSG_HAS_ATTACHMENT);
10511 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10512 compose_register_draft(newmsginfo);
10514 procmsg_msginfo_free(&newmsginfo);
10517 folder_item_scan(draft);
10519 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10521 g_mutex_unlock(compose->mutex); /* must be done before closing */
10522 compose_close(compose);
10529 GError *error = NULL;
10534 goffset size, mtime;
10536 path = folder_item_fetch_msg(draft, msgnum);
10537 if (path == NULL) {
10538 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10542 f = g_file_new_for_path(path);
10543 fi = g_file_query_info(f, "standard::size,time::modified",
10544 G_FILE_QUERY_INFO_NONE, NULL, &error);
10545 if (error != NULL) {
10546 debug_print("couldn't query file info for '%s': %s\n",
10547 path, error->message);
10548 g_error_free(error);
10553 size = g_file_info_get_size(fi);
10554 g_file_info_get_modification_time(fi, &tv);
10556 g_object_unref(fi);
10560 if (g_stat(path, &s) < 0) {
10561 FILE_OP_ERROR(path, "stat");
10566 mtime = s.st_mtime;
10570 procmsg_msginfo_free(&(compose->targetinfo));
10571 compose->targetinfo = procmsg_msginfo_new();
10572 compose->targetinfo->msgnum = msgnum;
10573 compose->targetinfo->size = size;
10574 compose->targetinfo->mtime = mtime;
10575 compose->targetinfo->folder = draft;
10577 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10578 compose->mode = COMPOSE_REEDIT;
10580 if (action == COMPOSE_AUTO_SAVE) {
10581 compose->autosaved_draft = compose->targetinfo;
10583 compose->modified = FALSE;
10584 compose_set_title(compose);
10588 g_mutex_unlock(compose->mutex);
10592 void compose_clear_exit_drafts(void)
10594 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10595 DRAFTED_AT_EXIT, NULL);
10596 if (is_file_exist(filepath))
10597 claws_unlink(filepath);
10602 void compose_reopen_exit_drafts(void)
10604 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10605 DRAFTED_AT_EXIT, NULL);
10606 FILE *fp = g_fopen(filepath, "rb");
10610 while (fgets(buf, sizeof(buf), fp)) {
10611 gchar **parts = g_strsplit(buf, "\t", 2);
10612 const gchar *folder = parts[0];
10613 int msgnum = parts[1] ? atoi(parts[1]):-1;
10615 if (folder && *folder && msgnum > -1) {
10616 FolderItem *item = folder_find_item_from_identifier(folder);
10617 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10619 compose_reedit(info, FALSE);
10626 compose_clear_exit_drafts();
10629 static void compose_save_cb(GtkAction *action, gpointer data)
10631 Compose *compose = (Compose *)data;
10632 compose_draft(compose, COMPOSE_KEEP_EDITING);
10633 compose->rmode = COMPOSE_REEDIT;
10636 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10638 if (compose && file_list) {
10641 for ( tmp = file_list; tmp; tmp = tmp->next) {
10642 gchar *file = (gchar *) tmp->data;
10643 gchar *utf8_filename = conv_filename_to_utf8(file);
10644 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10645 compose_changed_cb(NULL, compose);
10650 g_free(utf8_filename);
10655 static void compose_attach_cb(GtkAction *action, gpointer data)
10657 Compose *compose = (Compose *)data;
10660 if (compose->redirect_filename != NULL)
10663 /* Set focus_window properly, in case we were called via popup menu,
10664 * which unsets it (via focus_out_event callback on compose window). */
10665 manage_window_focus_in(compose->window, NULL, NULL);
10667 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10670 compose_attach_from_list(compose, file_list, TRUE);
10671 g_list_free(file_list);
10675 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10677 Compose *compose = (Compose *)data;
10679 gint files_inserted = 0;
10681 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10686 for ( tmp = file_list; tmp; tmp = tmp->next) {
10687 gchar *file = (gchar *) tmp->data;
10688 gchar *filedup = g_strdup(file);
10689 gchar *shortfile = g_path_get_basename(filedup);
10690 ComposeInsertResult res;
10691 /* insert the file if the file is short or if the user confirmed that
10692 he/she wants to insert the large file */
10693 res = compose_insert_file(compose, file);
10694 if (res == COMPOSE_INSERT_READ_ERROR) {
10695 alertpanel_error(_("File '%s' could not be read."), shortfile);
10696 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10697 alertpanel_error(_("File '%s' contained invalid characters\n"
10698 "for the current encoding, insertion may be incorrect."),
10700 } else if (res == COMPOSE_INSERT_SUCCESS)
10707 g_list_free(file_list);
10711 if (files_inserted > 0 && compose->gtkaspell &&
10712 compose->gtkaspell->check_while_typing)
10713 gtkaspell_highlight_all(compose->gtkaspell);
10717 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10719 Compose *compose = (Compose *)data;
10721 compose_insert_sig(compose, FALSE);
10724 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10726 Compose *compose = (Compose *)data;
10728 compose_insert_sig(compose, TRUE);
10731 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10735 Compose *compose = (Compose *)data;
10737 gtkut_widget_get_uposition(widget, &x, &y);
10738 if (!compose->batch) {
10739 prefs_common.compose_x = x;
10740 prefs_common.compose_y = y;
10742 if (compose->sending || compose->updating)
10744 compose_close_cb(NULL, compose);
10748 void compose_close_toolbar(Compose *compose)
10750 compose_close_cb(NULL, compose);
10753 static void compose_close_cb(GtkAction *action, gpointer data)
10755 Compose *compose = (Compose *)data;
10759 if (compose->exteditor_tag != -1) {
10760 if (!compose_ext_editor_kill(compose))
10765 if (compose->modified) {
10766 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10767 if (!g_mutex_trylock(compose->mutex)) {
10768 /* we don't want to lock the mutex once it's available,
10769 * because as the only other part of compose.c locking
10770 * it is compose_close - which means once unlocked,
10771 * the compose struct will be freed */
10772 debug_print("couldn't lock mutex, probably sending\n");
10776 val = alertpanel(_("Discard message"),
10777 _("This message has been modified. Discard it?"),
10778 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10780 val = alertpanel(_("Save changes"),
10781 _("This message has been modified. Save the latest changes?"),
10782 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10785 g_mutex_unlock(compose->mutex);
10787 case G_ALERTDEFAULT:
10788 if (compose_can_autosave(compose) && !reedit)
10789 compose_remove_draft(compose);
10791 case G_ALERTALTERNATE:
10792 compose_draft(data, COMPOSE_QUIT_EDITING);
10799 compose_close(compose);
10802 static void compose_print_cb(GtkAction *action, gpointer data)
10804 Compose *compose = (Compose *) data;
10806 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10807 if (compose->targetinfo)
10808 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10811 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10813 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10814 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10815 Compose *compose = (Compose *) data;
10818 compose->out_encoding = (CharSet)value;
10821 static void compose_address_cb(GtkAction *action, gpointer data)
10823 Compose *compose = (Compose *)data;
10825 #ifndef USE_ALT_ADDRBOOK
10826 addressbook_open(compose);
10828 GError* error = NULL;
10829 addressbook_connect_signals(compose);
10830 addressbook_dbus_open(TRUE, &error);
10832 g_warning("%s", error->message);
10833 g_error_free(error);
10838 static void about_show_cb(GtkAction *action, gpointer data)
10843 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10845 Compose *compose = (Compose *)data;
10850 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10851 cm_return_if_fail(tmpl != NULL);
10853 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10855 val = alertpanel(_("Apply template"), msg,
10856 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10859 if (val == G_ALERTDEFAULT)
10860 compose_template_apply(compose, tmpl, TRUE);
10861 else if (val == G_ALERTALTERNATE)
10862 compose_template_apply(compose, tmpl, FALSE);
10865 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10867 Compose *compose = (Compose *)data;
10870 if (compose->exteditor_tag != -1) {
10871 debug_print("ignoring open external editor: external editor still open\n");
10875 compose_exec_ext_editor(compose);
10878 static void compose_undo_cb(GtkAction *action, gpointer data)
10880 Compose *compose = (Compose *)data;
10881 gboolean prev_autowrap = compose->autowrap;
10883 compose->autowrap = FALSE;
10884 undo_undo(compose->undostruct);
10885 compose->autowrap = prev_autowrap;
10888 static void compose_redo_cb(GtkAction *action, gpointer data)
10890 Compose *compose = (Compose *)data;
10891 gboolean prev_autowrap = compose->autowrap;
10893 compose->autowrap = FALSE;
10894 undo_redo(compose->undostruct);
10895 compose->autowrap = prev_autowrap;
10898 static void entry_cut_clipboard(GtkWidget *entry)
10900 if (GTK_IS_EDITABLE(entry))
10901 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10902 else if (GTK_IS_TEXT_VIEW(entry))
10903 gtk_text_buffer_cut_clipboard(
10904 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10905 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10909 static void entry_copy_clipboard(GtkWidget *entry)
10911 if (GTK_IS_EDITABLE(entry))
10912 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10913 else if (GTK_IS_TEXT_VIEW(entry))
10914 gtk_text_buffer_copy_clipboard(
10915 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10916 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10919 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10920 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10922 if (GTK_IS_TEXT_VIEW(entry)) {
10923 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10924 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10925 GtkTextIter start_iter, end_iter;
10927 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10929 if (contents == NULL)
10932 /* we shouldn't delete the selection when middle-click-pasting, or we
10933 * can't mid-click-paste our own selection */
10934 if (clip != GDK_SELECTION_PRIMARY) {
10935 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10936 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10939 if (insert_place == NULL) {
10940 /* if insert_place isn't specified, insert at the cursor.
10941 * used for Ctrl-V pasting */
10942 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10943 start = gtk_text_iter_get_offset(&start_iter);
10944 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10946 /* if insert_place is specified, paste here.
10947 * used for mid-click-pasting */
10948 start = gtk_text_iter_get_offset(insert_place);
10949 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10950 if (prefs_common.primary_paste_unselects)
10951 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10955 /* paste unwrapped: mark the paste so it's not wrapped later */
10956 end = start + strlen(contents);
10957 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10958 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10959 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10960 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10961 /* rewrap paragraph now (after a mid-click-paste) */
10962 mark_start = gtk_text_buffer_get_insert(buffer);
10963 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10964 gtk_text_iter_backward_char(&start_iter);
10965 compose_beautify_paragraph(compose, &start_iter, TRUE);
10967 } else if (GTK_IS_EDITABLE(entry))
10968 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10970 compose->modified = TRUE;
10973 static void entry_allsel(GtkWidget *entry)
10975 if (GTK_IS_EDITABLE(entry))
10976 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10977 else if (GTK_IS_TEXT_VIEW(entry)) {
10978 GtkTextIter startiter, enditer;
10979 GtkTextBuffer *textbuf;
10981 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10982 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10983 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10985 gtk_text_buffer_move_mark_by_name(textbuf,
10986 "selection_bound", &startiter);
10987 gtk_text_buffer_move_mark_by_name(textbuf,
10988 "insert", &enditer);
10992 static void compose_cut_cb(GtkAction *action, gpointer data)
10994 Compose *compose = (Compose *)data;
10995 if (compose->focused_editable
10996 #ifndef GENERIC_UMPC
10997 && gtk_widget_has_focus(compose->focused_editable)
11000 entry_cut_clipboard(compose->focused_editable);
11003 static void compose_copy_cb(GtkAction *action, gpointer data)
11005 Compose *compose = (Compose *)data;
11006 if (compose->focused_editable
11007 #ifndef GENERIC_UMPC
11008 && gtk_widget_has_focus(compose->focused_editable)
11011 entry_copy_clipboard(compose->focused_editable);
11014 static void compose_paste_cb(GtkAction *action, gpointer data)
11016 Compose *compose = (Compose *)data;
11017 gint prev_autowrap;
11018 GtkTextBuffer *buffer;
11020 if (compose->focused_editable &&
11021 #ifndef GENERIC_UMPC
11022 gtk_widget_has_focus(compose->focused_editable)
11025 entry_paste_clipboard(compose, compose->focused_editable,
11026 prefs_common.linewrap_pastes,
11027 GDK_SELECTION_CLIPBOARD, NULL);
11032 #ifndef GENERIC_UMPC
11033 gtk_widget_has_focus(compose->text) &&
11035 compose->gtkaspell &&
11036 compose->gtkaspell->check_while_typing)
11037 gtkaspell_highlight_all(compose->gtkaspell);
11041 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11043 Compose *compose = (Compose *)data;
11044 gint wrap_quote = prefs_common.linewrap_quote;
11045 if (compose->focused_editable
11046 #ifndef GENERIC_UMPC
11047 && gtk_widget_has_focus(compose->focused_editable)
11050 /* let text_insert() (called directly or at a later time
11051 * after the gtk_editable_paste_clipboard) know that
11052 * text is to be inserted as a quotation. implemented
11053 * by using a simple refcount... */
11054 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11055 G_OBJECT(compose->focused_editable),
11056 "paste_as_quotation"));
11057 g_object_set_data(G_OBJECT(compose->focused_editable),
11058 "paste_as_quotation",
11059 GINT_TO_POINTER(paste_as_quotation + 1));
11060 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11061 entry_paste_clipboard(compose, compose->focused_editable,
11062 prefs_common.linewrap_pastes,
11063 GDK_SELECTION_CLIPBOARD, NULL);
11064 prefs_common.linewrap_quote = wrap_quote;
11068 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11070 Compose *compose = (Compose *)data;
11071 gint prev_autowrap;
11072 GtkTextBuffer *buffer;
11074 if (compose->focused_editable
11075 #ifndef GENERIC_UMPC
11076 && gtk_widget_has_focus(compose->focused_editable)
11079 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11080 GDK_SELECTION_CLIPBOARD, NULL);
11085 #ifndef GENERIC_UMPC
11086 gtk_widget_has_focus(compose->text) &&
11088 compose->gtkaspell &&
11089 compose->gtkaspell->check_while_typing)
11090 gtkaspell_highlight_all(compose->gtkaspell);
11094 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11096 Compose *compose = (Compose *)data;
11097 gint prev_autowrap;
11098 GtkTextBuffer *buffer;
11100 if (compose->focused_editable
11101 #ifndef GENERIC_UMPC
11102 && gtk_widget_has_focus(compose->focused_editable)
11105 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11106 GDK_SELECTION_CLIPBOARD, NULL);
11111 #ifndef GENERIC_UMPC
11112 gtk_widget_has_focus(compose->text) &&
11114 compose->gtkaspell &&
11115 compose->gtkaspell->check_while_typing)
11116 gtkaspell_highlight_all(compose->gtkaspell);
11120 static void compose_allsel_cb(GtkAction *action, gpointer data)
11122 Compose *compose = (Compose *)data;
11123 if (compose->focused_editable
11124 #ifndef GENERIC_UMPC
11125 && gtk_widget_has_focus(compose->focused_editable)
11128 entry_allsel(compose->focused_editable);
11131 static void textview_move_beginning_of_line (GtkTextView *text)
11133 GtkTextBuffer *buffer;
11137 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11139 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11140 mark = gtk_text_buffer_get_insert(buffer);
11141 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11142 gtk_text_iter_set_line_offset(&ins, 0);
11143 gtk_text_buffer_place_cursor(buffer, &ins);
11146 static void textview_move_forward_character (GtkTextView *text)
11148 GtkTextBuffer *buffer;
11152 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11154 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11155 mark = gtk_text_buffer_get_insert(buffer);
11156 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11157 if (gtk_text_iter_forward_cursor_position(&ins))
11158 gtk_text_buffer_place_cursor(buffer, &ins);
11161 static void textview_move_backward_character (GtkTextView *text)
11163 GtkTextBuffer *buffer;
11167 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11169 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11170 mark = gtk_text_buffer_get_insert(buffer);
11171 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11172 if (gtk_text_iter_backward_cursor_position(&ins))
11173 gtk_text_buffer_place_cursor(buffer, &ins);
11176 static void textview_move_forward_word (GtkTextView *text)
11178 GtkTextBuffer *buffer;
11183 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11185 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11186 mark = gtk_text_buffer_get_insert(buffer);
11187 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11188 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11189 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11190 gtk_text_iter_backward_word_start(&ins);
11191 gtk_text_buffer_place_cursor(buffer, &ins);
11195 static void textview_move_backward_word (GtkTextView *text)
11197 GtkTextBuffer *buffer;
11201 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11203 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11204 mark = gtk_text_buffer_get_insert(buffer);
11205 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11206 if (gtk_text_iter_backward_word_starts(&ins, 1))
11207 gtk_text_buffer_place_cursor(buffer, &ins);
11210 static void textview_move_end_of_line (GtkTextView *text)
11212 GtkTextBuffer *buffer;
11216 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11218 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11219 mark = gtk_text_buffer_get_insert(buffer);
11220 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11221 if (gtk_text_iter_forward_to_line_end(&ins))
11222 gtk_text_buffer_place_cursor(buffer, &ins);
11225 static void textview_move_next_line (GtkTextView *text)
11227 GtkTextBuffer *buffer;
11232 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11234 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11235 mark = gtk_text_buffer_get_insert(buffer);
11236 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11237 offset = gtk_text_iter_get_line_offset(&ins);
11238 if (gtk_text_iter_forward_line(&ins)) {
11239 gtk_text_iter_set_line_offset(&ins, offset);
11240 gtk_text_buffer_place_cursor(buffer, &ins);
11244 static void textview_move_previous_line (GtkTextView *text)
11246 GtkTextBuffer *buffer;
11251 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11253 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11254 mark = gtk_text_buffer_get_insert(buffer);
11255 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11256 offset = gtk_text_iter_get_line_offset(&ins);
11257 if (gtk_text_iter_backward_line(&ins)) {
11258 gtk_text_iter_set_line_offset(&ins, offset);
11259 gtk_text_buffer_place_cursor(buffer, &ins);
11263 static void textview_delete_forward_character (GtkTextView *text)
11265 GtkTextBuffer *buffer;
11267 GtkTextIter ins, end_iter;
11269 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11271 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11272 mark = gtk_text_buffer_get_insert(buffer);
11273 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11275 if (gtk_text_iter_forward_char(&end_iter)) {
11276 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11280 static void textview_delete_backward_character (GtkTextView *text)
11282 GtkTextBuffer *buffer;
11284 GtkTextIter ins, end_iter;
11286 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11288 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11289 mark = gtk_text_buffer_get_insert(buffer);
11290 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11292 if (gtk_text_iter_backward_char(&end_iter)) {
11293 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11297 static void textview_delete_forward_word (GtkTextView *text)
11299 GtkTextBuffer *buffer;
11301 GtkTextIter ins, end_iter;
11303 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11305 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11306 mark = gtk_text_buffer_get_insert(buffer);
11307 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11309 if (gtk_text_iter_forward_word_end(&end_iter)) {
11310 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11314 static void textview_delete_backward_word (GtkTextView *text)
11316 GtkTextBuffer *buffer;
11318 GtkTextIter ins, end_iter;
11320 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11322 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11323 mark = gtk_text_buffer_get_insert(buffer);
11324 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11326 if (gtk_text_iter_backward_word_start(&end_iter)) {
11327 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11331 static void textview_delete_line (GtkTextView *text)
11333 GtkTextBuffer *buffer;
11335 GtkTextIter ins, start_iter, end_iter;
11337 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11339 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11340 mark = gtk_text_buffer_get_insert(buffer);
11341 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11344 gtk_text_iter_set_line_offset(&start_iter, 0);
11347 if (gtk_text_iter_ends_line(&end_iter)){
11348 if (!gtk_text_iter_forward_char(&end_iter))
11349 gtk_text_iter_backward_char(&start_iter);
11352 gtk_text_iter_forward_to_line_end(&end_iter);
11353 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11356 static void textview_delete_to_line_end (GtkTextView *text)
11358 GtkTextBuffer *buffer;
11360 GtkTextIter ins, end_iter;
11362 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11364 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11365 mark = gtk_text_buffer_get_insert(buffer);
11366 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11368 if (gtk_text_iter_ends_line(&end_iter))
11369 gtk_text_iter_forward_char(&end_iter);
11371 gtk_text_iter_forward_to_line_end(&end_iter);
11372 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11375 #define DO_ACTION(name, act) { \
11376 if(!strcmp(name, a_name)) { \
11380 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11382 const gchar *a_name = gtk_action_get_name(action);
11383 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11384 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11385 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11386 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11387 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11388 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11389 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11390 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11391 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11392 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11393 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11394 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11395 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11396 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11397 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11400 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11402 Compose *compose = (Compose *)data;
11403 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11404 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11406 action = compose_call_advanced_action_from_path(gaction);
11409 void (*do_action) (GtkTextView *text);
11410 } action_table[] = {
11411 {textview_move_beginning_of_line},
11412 {textview_move_forward_character},
11413 {textview_move_backward_character},
11414 {textview_move_forward_word},
11415 {textview_move_backward_word},
11416 {textview_move_end_of_line},
11417 {textview_move_next_line},
11418 {textview_move_previous_line},
11419 {textview_delete_forward_character},
11420 {textview_delete_backward_character},
11421 {textview_delete_forward_word},
11422 {textview_delete_backward_word},
11423 {textview_delete_line},
11424 {textview_delete_to_line_end}
11427 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11429 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11430 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11431 if (action_table[action].do_action)
11432 action_table[action].do_action(text);
11434 g_warning("Not implemented yet.");
11438 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11440 GtkAllocation allocation;
11444 if (GTK_IS_EDITABLE(widget)) {
11445 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11446 gtk_editable_set_position(GTK_EDITABLE(widget),
11449 if ((parent = gtk_widget_get_parent(widget))
11450 && (parent = gtk_widget_get_parent(parent))
11451 && (parent = gtk_widget_get_parent(parent))) {
11452 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11453 gtk_widget_get_allocation(widget, &allocation);
11454 gint y = allocation.y;
11455 gint height = allocation.height;
11456 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11457 (GTK_SCROLLED_WINDOW(parent));
11459 gfloat value = gtk_adjustment_get_value(shown);
11460 gfloat upper = gtk_adjustment_get_upper(shown);
11461 gfloat page_size = gtk_adjustment_get_page_size(shown);
11462 if (y < (int)value) {
11463 gtk_adjustment_set_value(shown, y - 1);
11465 if ((y + height) > ((int)value + (int)page_size)) {
11466 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11467 gtk_adjustment_set_value(shown,
11468 y + height - (int)page_size - 1);
11470 gtk_adjustment_set_value(shown,
11471 (int)upper - (int)page_size - 1);
11478 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11479 compose->focused_editable = widget;
11481 #ifdef GENERIC_UMPC
11482 if (GTK_IS_TEXT_VIEW(widget)
11483 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11484 g_object_ref(compose->notebook);
11485 g_object_ref(compose->edit_vbox);
11486 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11487 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11488 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11489 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11490 g_object_unref(compose->notebook);
11491 g_object_unref(compose->edit_vbox);
11492 g_signal_handlers_block_by_func(G_OBJECT(widget),
11493 G_CALLBACK(compose_grab_focus_cb),
11495 gtk_widget_grab_focus(widget);
11496 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11497 G_CALLBACK(compose_grab_focus_cb),
11499 } else if (!GTK_IS_TEXT_VIEW(widget)
11500 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11501 g_object_ref(compose->notebook);
11502 g_object_ref(compose->edit_vbox);
11503 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11504 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11505 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11506 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11507 g_object_unref(compose->notebook);
11508 g_object_unref(compose->edit_vbox);
11509 g_signal_handlers_block_by_func(G_OBJECT(widget),
11510 G_CALLBACK(compose_grab_focus_cb),
11512 gtk_widget_grab_focus(widget);
11513 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11514 G_CALLBACK(compose_grab_focus_cb),
11520 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11522 compose->modified = TRUE;
11523 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11524 #ifndef GENERIC_UMPC
11525 compose_set_title(compose);
11529 static void compose_wrap_cb(GtkAction *action, gpointer data)
11531 Compose *compose = (Compose *)data;
11532 compose_beautify_paragraph(compose, NULL, TRUE);
11535 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11537 Compose *compose = (Compose *)data;
11538 compose_wrap_all_full(compose, TRUE);
11541 static void compose_find_cb(GtkAction *action, gpointer data)
11543 Compose *compose = (Compose *)data;
11545 message_search_compose(compose);
11548 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11551 Compose *compose = (Compose *)data;
11552 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11553 if (compose->autowrap)
11554 compose_wrap_all_full(compose, TRUE);
11555 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11558 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11561 Compose *compose = (Compose *)data;
11562 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11565 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11567 Compose *compose = (Compose *)data;
11569 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11570 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11573 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11575 Compose *compose = (Compose *)data;
11577 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11578 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11581 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11583 g_free(compose->privacy_system);
11584 g_free(compose->encdata);
11586 compose->privacy_system = g_strdup(account->default_privacy_system);
11587 compose_update_privacy_system_menu_item(compose, warn);
11590 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11592 Compose *compose = (Compose *)data;
11594 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11595 gtk_widget_show(compose->ruler_hbox);
11596 prefs_common.show_ruler = TRUE;
11598 gtk_widget_hide(compose->ruler_hbox);
11599 gtk_widget_queue_resize(compose->edit_vbox);
11600 prefs_common.show_ruler = FALSE;
11604 static void compose_attach_drag_received_cb (GtkWidget *widget,
11605 GdkDragContext *context,
11608 GtkSelectionData *data,
11611 gpointer user_data)
11613 Compose *compose = (Compose *)user_data;
11617 type = gtk_selection_data_get_data_type(data);
11618 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11619 && gtk_drag_get_source_widget(context) !=
11620 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11621 list = uri_list_extract_filenames(
11622 (const gchar *)gtk_selection_data_get_data(data));
11623 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11624 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11625 compose_attach_append
11626 (compose, (const gchar *)tmp->data,
11627 utf8_filename, NULL, NULL);
11628 g_free(utf8_filename);
11630 if (list) compose_changed_cb(NULL, compose);
11631 list_free_strings(list);
11633 } else if (gtk_drag_get_source_widget(context)
11634 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11635 /* comes from our summaryview */
11636 SummaryView * summaryview = NULL;
11637 GSList * list = NULL, *cur = NULL;
11639 if (mainwindow_get_mainwindow())
11640 summaryview = mainwindow_get_mainwindow()->summaryview;
11643 list = summary_get_selected_msg_list(summaryview);
11645 for (cur = list; cur; cur = cur->next) {
11646 MsgInfo *msginfo = (MsgInfo *)cur->data;
11647 gchar *file = NULL;
11649 file = procmsg_get_message_file_full(msginfo,
11652 compose_attach_append(compose, (const gchar *)file,
11653 (const gchar *)file, "message/rfc822", NULL);
11657 g_slist_free(list);
11661 static gboolean compose_drag_drop(GtkWidget *widget,
11662 GdkDragContext *drag_context,
11664 guint time, gpointer user_data)
11666 /* not handling this signal makes compose_insert_drag_received_cb
11671 static gboolean completion_set_focus_to_subject
11672 (GtkWidget *widget,
11673 GdkEventKey *event,
11676 cm_return_val_if_fail(compose != NULL, FALSE);
11678 /* make backtab move to subject field */
11679 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11680 gtk_widget_grab_focus(compose->subject_entry);
11686 static void compose_insert_drag_received_cb (GtkWidget *widget,
11687 GdkDragContext *drag_context,
11690 GtkSelectionData *data,
11693 gpointer user_data)
11695 Compose *compose = (Compose *)user_data;
11701 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11703 type = gtk_selection_data_get_data_type(data);
11704 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11705 AlertValue val = G_ALERTDEFAULT;
11706 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11708 list = uri_list_extract_filenames(ddata);
11709 num_files = g_list_length(list);
11710 if (list == NULL && strstr(ddata, "://")) {
11711 /* Assume a list of no files, and data has ://, is a remote link */
11712 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11713 gchar *tmpfile = get_tmp_file();
11714 str_write_to_file(tmpdata, tmpfile);
11716 compose_insert_file(compose, tmpfile);
11717 claws_unlink(tmpfile);
11719 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11720 compose_beautify_paragraph(compose, NULL, TRUE);
11723 switch (prefs_common.compose_dnd_mode) {
11724 case COMPOSE_DND_ASK:
11725 msg = g_strdup_printf(
11727 "Do you want to insert the contents of the file "
11728 "into the message body, or attach it to the email?",
11729 "Do you want to insert the contents of the %d files "
11730 "into the message body, or attach them to the email?",
11733 val = alertpanel_full(_("Insert or attach?"), msg,
11734 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11735 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11738 case COMPOSE_DND_INSERT:
11739 val = G_ALERTALTERNATE;
11741 case COMPOSE_DND_ATTACH:
11742 val = G_ALERTOTHER;
11745 /* unexpected case */
11746 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11749 if (val & G_ALERTDISABLE) {
11750 val &= ~G_ALERTDISABLE;
11751 /* remember what action to perform by default, only if we don't click Cancel */
11752 if (val == G_ALERTALTERNATE)
11753 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11754 else if (val == G_ALERTOTHER)
11755 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11758 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11759 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11760 list_free_strings(list);
11763 } else if (val == G_ALERTOTHER) {
11764 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11765 list_free_strings(list);
11770 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11771 compose_insert_file(compose, (const gchar *)tmp->data);
11773 list_free_strings(list);
11775 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11780 static void compose_header_drag_received_cb (GtkWidget *widget,
11781 GdkDragContext *drag_context,
11784 GtkSelectionData *data,
11787 gpointer user_data)
11789 GtkEditable *entry = (GtkEditable *)user_data;
11790 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11792 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11795 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11796 gchar *decoded=g_new(gchar, strlen(email));
11799 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11800 gtk_editable_delete_text(entry, 0, -1);
11801 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11802 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11806 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11809 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11811 Compose *compose = (Compose *)data;
11813 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11814 compose->return_receipt = TRUE;
11816 compose->return_receipt = FALSE;
11819 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11821 Compose *compose = (Compose *)data;
11823 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11824 compose->remove_references = TRUE;
11826 compose->remove_references = FALSE;
11829 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11830 ComposeHeaderEntry *headerentry)
11832 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11833 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11834 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11838 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11839 GdkEventKey *event,
11840 ComposeHeaderEntry *headerentry)
11842 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11843 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11844 !(event->state & GDK_MODIFIER_MASK) &&
11845 (event->keyval == GDK_KEY_BackSpace) &&
11846 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11847 gtk_container_remove
11848 (GTK_CONTAINER(headerentry->compose->header_table),
11849 headerentry->combo);
11850 gtk_container_remove
11851 (GTK_CONTAINER(headerentry->compose->header_table),
11852 headerentry->entry);
11853 headerentry->compose->header_list =
11854 g_slist_remove(headerentry->compose->header_list,
11856 g_free(headerentry);
11857 } else if (event->keyval == GDK_KEY_Tab) {
11858 if (headerentry->compose->header_last == headerentry) {
11859 /* Override default next focus, and give it to subject_entry
11860 * instead of notebook tabs
11862 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11863 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11870 static gboolean scroll_postpone(gpointer data)
11872 Compose *compose = (Compose *)data;
11874 if (compose->batch)
11877 GTK_EVENTS_FLUSH();
11878 compose_show_first_last_header(compose, FALSE);
11882 static void compose_headerentry_changed_cb(GtkWidget *entry,
11883 ComposeHeaderEntry *headerentry)
11885 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11886 compose_create_header_entry(headerentry->compose);
11887 g_signal_handlers_disconnect_matched
11888 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11889 0, 0, NULL, NULL, headerentry);
11891 if (!headerentry->compose->batch)
11892 g_timeout_add(0, scroll_postpone, headerentry->compose);
11896 static gboolean compose_defer_auto_save_draft(Compose *compose)
11898 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11899 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11903 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11905 GtkAdjustment *vadj;
11907 cm_return_if_fail(compose);
11912 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11913 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11914 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11915 gtk_widget_get_parent(compose->header_table)));
11916 gtk_adjustment_set_value(vadj, (show_first ?
11917 gtk_adjustment_get_lower(vadj) :
11918 (gtk_adjustment_get_upper(vadj) -
11919 gtk_adjustment_get_page_size(vadj))));
11920 gtk_adjustment_changed(vadj);
11923 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11924 const gchar *text, gint len, Compose *compose)
11926 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11927 (G_OBJECT(compose->text), "paste_as_quotation"));
11930 cm_return_if_fail(text != NULL);
11932 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11933 G_CALLBACK(text_inserted),
11935 if (paste_as_quotation) {
11937 const gchar *qmark;
11939 GtkTextIter start_iter;
11942 len = strlen(text);
11944 new_text = g_strndup(text, len);
11946 qmark = compose_quote_char_from_context(compose);
11948 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11949 gtk_text_buffer_place_cursor(buffer, iter);
11951 pos = gtk_text_iter_get_offset(iter);
11953 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11954 _("Quote format error at line %d."));
11955 quote_fmt_reset_vartable();
11957 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11958 GINT_TO_POINTER(paste_as_quotation - 1));
11960 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11961 gtk_text_buffer_place_cursor(buffer, iter);
11962 gtk_text_buffer_delete_mark(buffer, mark);
11964 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11965 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11966 compose_beautify_paragraph(compose, &start_iter, FALSE);
11967 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11968 gtk_text_buffer_delete_mark(buffer, mark);
11970 if (strcmp(text, "\n") || compose->automatic_break
11971 || gtk_text_iter_starts_line(iter)) {
11972 GtkTextIter before_ins;
11973 gtk_text_buffer_insert(buffer, iter, text, len);
11974 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11975 before_ins = *iter;
11976 gtk_text_iter_backward_chars(&before_ins, len);
11977 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11980 /* check if the preceding is just whitespace or quote */
11981 GtkTextIter start_line;
11982 gchar *tmp = NULL, *quote = NULL;
11983 gint quote_len = 0, is_normal = 0;
11984 start_line = *iter;
11985 gtk_text_iter_set_line_offset(&start_line, 0);
11986 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11989 if (*tmp == '\0') {
11992 quote = compose_get_quote_str(buffer, &start_line, "e_len);
12000 gtk_text_buffer_insert(buffer, iter, text, len);
12002 gtk_text_buffer_insert_with_tags_by_name(buffer,
12003 iter, text, len, "no_join", NULL);
12008 if (!paste_as_quotation) {
12009 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12010 compose_beautify_paragraph(compose, iter, FALSE);
12011 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12012 gtk_text_buffer_delete_mark(buffer, mark);
12015 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12016 G_CALLBACK(text_inserted),
12018 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12020 if (compose_can_autosave(compose) &&
12021 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12022 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12023 compose->draft_timeout_tag = g_timeout_add
12024 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12028 static void compose_check_all(GtkAction *action, gpointer data)
12030 Compose *compose = (Compose *)data;
12031 if (!compose->gtkaspell)
12034 if (gtk_widget_has_focus(compose->subject_entry))
12035 claws_spell_entry_check_all(
12036 CLAWS_SPELL_ENTRY(compose->subject_entry));
12038 gtkaspell_check_all(compose->gtkaspell);
12041 static void compose_highlight_all(GtkAction *action, gpointer data)
12043 Compose *compose = (Compose *)data;
12044 if (compose->gtkaspell) {
12045 claws_spell_entry_recheck_all(
12046 CLAWS_SPELL_ENTRY(compose->subject_entry));
12047 gtkaspell_highlight_all(compose->gtkaspell);
12051 static void compose_check_backwards(GtkAction *action, gpointer data)
12053 Compose *compose = (Compose *)data;
12054 if (!compose->gtkaspell) {
12055 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12059 if (gtk_widget_has_focus(compose->subject_entry))
12060 claws_spell_entry_check_backwards(
12061 CLAWS_SPELL_ENTRY(compose->subject_entry));
12063 gtkaspell_check_backwards(compose->gtkaspell);
12066 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12068 Compose *compose = (Compose *)data;
12069 if (!compose->gtkaspell) {
12070 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12074 if (gtk_widget_has_focus(compose->subject_entry))
12075 claws_spell_entry_check_forwards_go(
12076 CLAWS_SPELL_ENTRY(compose->subject_entry));
12078 gtkaspell_check_forwards_go(compose->gtkaspell);
12083 *\brief Guess originating forward account from MsgInfo and several
12084 * "common preference" settings. Return NULL if no guess.
12086 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12088 PrefsAccount *account = NULL;
12090 cm_return_val_if_fail(msginfo, NULL);
12091 cm_return_val_if_fail(msginfo->folder, NULL);
12092 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12094 if (msginfo->folder->prefs->enable_default_account)
12095 account = account_find_from_id(msginfo->folder->prefs->default_account);
12097 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12099 Xstrdup_a(to, msginfo->to, return NULL);
12100 extract_address(to);
12101 account = account_find_from_address(to, FALSE);
12104 if (!account && prefs_common.forward_account_autosel) {
12106 if (!procheader_get_header_from_msginfo
12107 (msginfo, &cc, "Cc:")) {
12108 gchar *buf = cc + strlen("Cc:");
12109 extract_address(buf);
12110 account = account_find_from_address(buf, FALSE);
12115 if (!account && prefs_common.forward_account_autosel) {
12116 gchar *deliveredto = NULL;
12117 if (!procheader_get_header_from_msginfo
12118 (msginfo, &deliveredto, "Delivered-To:")) {
12119 gchar *buf = deliveredto + strlen("Delivered-To:");
12120 extract_address(buf);
12121 account = account_find_from_address(buf, FALSE);
12122 g_free(deliveredto);
12127 account = msginfo->folder->folder->account;
12132 gboolean compose_close(Compose *compose)
12136 cm_return_val_if_fail(compose, FALSE);
12138 if (!g_mutex_trylock(compose->mutex)) {
12139 /* we have to wait for the (possibly deferred by auto-save)
12140 * drafting to be done, before destroying the compose under
12142 debug_print("waiting for drafting to finish...\n");
12143 compose_allow_user_actions(compose, FALSE);
12144 if (compose->close_timeout_tag == 0) {
12145 compose->close_timeout_tag =
12146 g_timeout_add (500, (GSourceFunc) compose_close,
12152 if (compose->draft_timeout_tag >= 0) {
12153 g_source_remove(compose->draft_timeout_tag);
12154 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12157 gtkut_widget_get_uposition(compose->window, &x, &y);
12158 if (!compose->batch) {
12159 prefs_common.compose_x = x;
12160 prefs_common.compose_y = y;
12162 g_mutex_unlock(compose->mutex);
12163 compose_destroy(compose);
12168 * Add entry field for each address in list.
12169 * \param compose E-Mail composition object.
12170 * \param listAddress List of (formatted) E-Mail addresses.
12172 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12175 node = listAddress;
12177 addr = ( gchar * ) node->data;
12178 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12179 node = g_list_next( node );
12183 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12184 guint action, gboolean opening_multiple)
12186 gchar *body = NULL;
12187 GSList *new_msglist = NULL;
12188 MsgInfo *tmp_msginfo = NULL;
12189 gboolean originally_enc = FALSE;
12190 gboolean originally_sig = FALSE;
12191 Compose *compose = NULL;
12192 gchar *s_system = NULL;
12194 cm_return_if_fail(msgview != NULL);
12196 cm_return_if_fail(msginfo_list != NULL);
12198 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12199 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12200 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12202 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12203 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12204 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12205 orig_msginfo, mimeinfo);
12206 if (tmp_msginfo != NULL) {
12207 new_msglist = g_slist_append(NULL, tmp_msginfo);
12209 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12210 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12211 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12213 tmp_msginfo->folder = orig_msginfo->folder;
12214 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12215 if (orig_msginfo->tags) {
12216 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12217 tmp_msginfo->folder->tags_dirty = TRUE;
12223 if (!opening_multiple)
12224 body = messageview_get_selection(msgview);
12227 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12228 procmsg_msginfo_free(&tmp_msginfo);
12229 g_slist_free(new_msglist);
12231 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12233 if (compose && originally_enc) {
12234 compose_force_encryption(compose, compose->account, FALSE, s_system);
12237 if (compose && originally_sig && compose->account->default_sign_reply) {
12238 compose_force_signing(compose, compose->account, s_system);
12242 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12245 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12248 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12249 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12250 GSList *cur = msginfo_list;
12251 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12252 "messages. Opening the windows "
12253 "could take some time. Do you "
12254 "want to continue?"),
12255 g_slist_length(msginfo_list));
12256 if (g_slist_length(msginfo_list) > 9
12257 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
12258 != G_ALERTALTERNATE) {
12263 /* We'll open multiple compose windows */
12264 /* let the WM place the next windows */
12265 compose_force_window_origin = FALSE;
12266 for (; cur; cur = cur->next) {
12268 tmplist.data = cur->data;
12269 tmplist.next = NULL;
12270 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12272 compose_force_window_origin = TRUE;
12274 /* forwarding multiple mails as attachments is done via a
12275 * single compose window */
12276 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12280 void compose_check_for_email_account(Compose *compose)
12282 PrefsAccount *ac = NULL, *curr = NULL;
12288 if (compose->account && compose->account->protocol == A_NNTP) {
12289 ac = account_get_cur_account();
12290 if (ac->protocol == A_NNTP) {
12291 list = account_get_list();
12293 for( ; list != NULL ; list = g_list_next(list)) {
12294 curr = (PrefsAccount *) list->data;
12295 if (curr->protocol != A_NNTP) {
12301 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12306 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12307 const gchar *address)
12309 GSList *msginfo_list = NULL;
12310 gchar *body = messageview_get_selection(msgview);
12313 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12315 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12316 compose_check_for_email_account(compose);
12317 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12318 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12319 compose_reply_set_subject(compose, msginfo);
12322 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12325 void compose_set_position(Compose *compose, gint pos)
12327 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12329 gtkut_text_view_set_position(text, pos);
12332 gboolean compose_search_string(Compose *compose,
12333 const gchar *str, gboolean case_sens)
12335 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12337 return gtkut_text_view_search_string(text, str, case_sens);
12340 gboolean compose_search_string_backward(Compose *compose,
12341 const gchar *str, gboolean case_sens)
12343 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12345 return gtkut_text_view_search_string_backward(text, str, case_sens);
12348 /* allocate a msginfo structure and populate its data from a compose data structure */
12349 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12351 MsgInfo *newmsginfo;
12353 gchar date[RFC822_DATE_BUFFSIZE];
12355 cm_return_val_if_fail( compose != NULL, NULL );
12357 newmsginfo = procmsg_msginfo_new();
12360 get_rfc822_date(date, sizeof(date));
12361 newmsginfo->date = g_strdup(date);
12364 if (compose->from_name) {
12365 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12366 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12370 if (compose->subject_entry)
12371 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12373 /* to, cc, reply-to, newsgroups */
12374 for (list = compose->header_list; list; list = list->next) {
12375 gchar *header = gtk_editable_get_chars(
12377 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12378 gchar *entry = gtk_editable_get_chars(
12379 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12381 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12382 if ( newmsginfo->to == NULL ) {
12383 newmsginfo->to = g_strdup(entry);
12384 } else if (entry && *entry) {
12385 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12386 g_free(newmsginfo->to);
12387 newmsginfo->to = tmp;
12390 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12391 if ( newmsginfo->cc == NULL ) {
12392 newmsginfo->cc = g_strdup(entry);
12393 } else if (entry && *entry) {
12394 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12395 g_free(newmsginfo->cc);
12396 newmsginfo->cc = tmp;
12399 if ( strcasecmp(header,
12400 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12401 if ( newmsginfo->newsgroups == NULL ) {
12402 newmsginfo->newsgroups = g_strdup(entry);
12403 } else if (entry && *entry) {
12404 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12405 g_free(newmsginfo->newsgroups);
12406 newmsginfo->newsgroups = tmp;
12414 /* other data is unset */
12420 /* update compose's dictionaries from folder dict settings */
12421 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12422 FolderItem *folder_item)
12424 cm_return_if_fail(compose != NULL);
12426 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12427 FolderItemPrefs *prefs = folder_item->prefs;
12429 if (prefs->enable_default_dictionary)
12430 gtkaspell_change_dict(compose->gtkaspell,
12431 prefs->default_dictionary, FALSE);
12432 if (folder_item->prefs->enable_default_alt_dictionary)
12433 gtkaspell_change_alt_dict(compose->gtkaspell,
12434 prefs->default_alt_dictionary);
12435 if (prefs->enable_default_dictionary
12436 || prefs->enable_default_alt_dictionary)
12437 compose_spell_menu_changed(compose);
12442 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12444 Compose *compose = (Compose *)data;
12446 cm_return_if_fail(compose != NULL);
12448 gtk_widget_grab_focus(compose->text);
12451 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12453 gtk_combo_box_popup(GTK_COMBO_BOX(data));