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 ComposeQueueResult 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};
842 buffer = gtk_text_view_get_buffer(text);
844 if (prefs_common.enable_color) {
845 /* grab the quote colors, converting from an int to a GdkColor */
846 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
848 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
850 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
852 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
854 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
856 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
858 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
860 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
863 signature_color = quote_color1 = quote_color2 = quote_color3 =
864 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
867 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
868 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
869 "foreground-gdk", "e_color1,
870 "paragraph-background-gdk", "e_bgcolor1,
872 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
873 "foreground-gdk", "e_color2,
874 "paragraph-background-gdk", "e_bgcolor2,
876 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
877 "foreground-gdk", "e_color3,
878 "paragraph-background-gdk", "e_bgcolor3,
881 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
882 "foreground-gdk", "e_color1,
884 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
885 "foreground-gdk", "e_color2,
887 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
888 "foreground-gdk", "e_color3,
892 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
893 "foreground-gdk", &signature_color,
896 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
897 "foreground-gdk", &uri_color,
899 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
900 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
903 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
906 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
909 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
911 return compose_generic_new(account, mailto, item, NULL, NULL);
914 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
916 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
919 #define SCROLL_TO_CURSOR(compose) { \
920 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
921 gtk_text_view_get_buffer( \
922 GTK_TEXT_VIEW(compose->text))); \
923 gtk_text_view_scroll_mark_onscreen( \
924 GTK_TEXT_VIEW(compose->text), \
928 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
931 if (folderidentifier) {
932 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
933 prefs_common.compose_save_to_history = add_history(
934 prefs_common.compose_save_to_history, folderidentifier);
935 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
936 prefs_common.compose_save_to_history);
939 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
940 if (folderidentifier)
941 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
943 gtk_entry_set_text(GTK_ENTRY(entry), "");
946 static gchar *compose_get_save_to(Compose *compose)
949 gchar *result = NULL;
950 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
951 result = gtk_editable_get_chars(entry, 0, -1);
954 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
955 prefs_common.compose_save_to_history = add_history(
956 prefs_common.compose_save_to_history, result);
957 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
958 prefs_common.compose_save_to_history);
963 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
964 GList *attach_files, GList *listAddress )
967 GtkTextView *textview;
968 GtkTextBuffer *textbuf;
970 const gchar *subject_format = NULL;
971 const gchar *body_format = NULL;
972 gchar *mailto_from = NULL;
973 PrefsAccount *mailto_account = NULL;
974 MsgInfo* dummyinfo = NULL;
975 gint cursor_pos = -1;
976 MailField mfield = NO_FIELD_PRESENT;
980 /* check if mailto defines a from */
981 if (mailto && *mailto != '\0') {
982 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
983 /* mailto defines a from, check if we can get account prefs from it,
984 if not, the account prefs will be guessed using other ways, but we'll keep
987 mailto_account = account_find_from_address(mailto_from, TRUE);
988 if (mailto_account == NULL) {
990 Xstrdup_a(tmp_from, mailto_from, return NULL);
991 extract_address(tmp_from);
992 mailto_account = account_find_from_address(tmp_from, TRUE);
996 account = mailto_account;
999 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1000 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1001 account = account_find_from_id(item->prefs->default_account);
1003 /* if no account prefs set, fallback to the current one */
1004 if (!account) account = cur_account;
1005 cm_return_val_if_fail(account != NULL, NULL);
1007 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1009 /* override from name if mailto asked for it */
1011 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1012 g_free(mailto_from);
1014 /* override from name according to folder properties */
1015 if (item && item->prefs &&
1016 item->prefs->compose_with_format &&
1017 item->prefs->compose_override_from_format &&
1018 *item->prefs->compose_override_from_format != '\0') {
1023 dummyinfo = compose_msginfo_new_from_compose(compose);
1025 /* decode \-escape sequences in the internal representation of the quote format */
1026 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1027 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1030 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1031 compose->gtkaspell);
1033 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1035 quote_fmt_scan_string(tmp);
1038 buf = quote_fmt_get_buffer();
1040 alertpanel_error(_("New message From format error."));
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1043 quote_fmt_reset_vartable();
1044 quote_fmtlex_destroy();
1049 compose->replyinfo = NULL;
1050 compose->fwdinfo = NULL;
1052 textview = GTK_TEXT_VIEW(compose->text);
1053 textbuf = gtk_text_view_get_buffer(textview);
1054 compose_create_tags(textview, compose);
1056 undo_block(compose->undostruct);
1058 compose_set_dictionaries_from_folder_prefs(compose, item);
1061 if (account->auto_sig)
1062 compose_insert_sig(compose, FALSE);
1063 gtk_text_buffer_get_start_iter(textbuf, &iter);
1064 gtk_text_buffer_place_cursor(textbuf, &iter);
1066 if (account->protocol != A_NNTP) {
1067 if (mailto && *mailto != '\0') {
1068 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1071 compose_set_folder_prefs(compose, item, TRUE);
1073 if (item && item->ret_rcpt) {
1074 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1077 if (mailto && *mailto != '\0') {
1078 if (!strchr(mailto, '@'))
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1083 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1084 mfield = TO_FIELD_PRESENT;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1092 compose_add_field_list( compose, listAddress );
1094 if (item && item->prefs && item->prefs->compose_with_format) {
1095 subject_format = item->prefs->compose_subject_format;
1096 body_format = item->prefs->compose_body_format;
1097 } else if (account->compose_with_format) {
1098 subject_format = account->compose_subject_format;
1099 body_format = account->compose_body_format;
1100 } else if (prefs_common.compose_with_format) {
1101 subject_format = prefs_common.compose_subject_format;
1102 body_format = prefs_common.compose_body_format;
1105 if (subject_format || body_format) {
1108 && *subject_format != '\0' )
1110 gchar *subject = NULL;
1115 dummyinfo = compose_msginfo_new_from_compose(compose);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp = g_malloc(strlen(subject_format)+1);
1119 pref_get_unescaped_pref(tmp, subject_format);
1121 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1123 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1124 compose->gtkaspell);
1126 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1128 quote_fmt_scan_string(tmp);
1131 buf = quote_fmt_get_buffer();
1133 alertpanel_error(_("New message subject format error."));
1135 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1136 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1137 quote_fmt_reset_vartable();
1138 quote_fmtlex_destroy();
1142 mfield = SUBJECT_FIELD_PRESENT;
1146 && *body_format != '\0' )
1149 GtkTextBuffer *buffer;
1150 GtkTextIter start, end;
1154 dummyinfo = compose_msginfo_new_from_compose(compose);
1156 text = GTK_TEXT_VIEW(compose->text);
1157 buffer = gtk_text_view_get_buffer(text);
1158 gtk_text_buffer_get_start_iter(buffer, &start);
1159 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1160 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1162 compose_quote_fmt(compose, dummyinfo,
1164 NULL, tmp, FALSE, TRUE,
1165 _("The body of the \"New message\" template has an error at line %d."));
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1171 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1172 gtkaspell_highlight_all(compose->gtkaspell);
1174 mfield = BODY_FIELD_PRESENT;
1178 procmsg_msginfo_free( &dummyinfo );
1184 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1185 ainfo = (AttachInfo *) curr->data;
1187 compose_insert_file(compose, ainfo->file);
1189 compose_attach_append(compose, ainfo->file, ainfo->file,
1190 ainfo->content_type, ainfo->charset);
1194 compose_show_first_last_header(compose, TRUE);
1196 /* Set save folder */
1197 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1198 gchar *folderidentifier;
1200 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1201 folderidentifier = folder_item_get_identifier(item);
1202 compose_set_save_to(compose, folderidentifier);
1203 g_free(folderidentifier);
1206 /* Place cursor according to provided input (mfield) */
1208 case NO_FIELD_PRESENT:
1209 if (compose->header_last)
1210 gtk_widget_grab_focus(compose->header_last->entry);
1212 case TO_FIELD_PRESENT:
1213 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1215 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1218 gtk_widget_grab_focus(compose->subject_entry);
1220 case SUBJECT_FIELD_PRESENT:
1221 textview = GTK_TEXT_VIEW(compose->text);
1224 textbuf = gtk_text_view_get_buffer(textview);
1227 mark = gtk_text_buffer_get_insert(textbuf);
1228 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1229 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1231 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1232 * only defers where it comes to the variable body
1233 * is not null. If no body is present compose->text
1234 * will be null in which case you cannot place the
1235 * cursor inside the component so. An empty component
1236 * is therefore created before placing the cursor
1238 case BODY_FIELD_PRESENT:
1239 cursor_pos = quote_fmt_get_cursor_pos();
1240 if (cursor_pos == -1)
1241 gtk_widget_grab_focus(compose->header_last->entry);
1243 gtk_widget_grab_focus(compose->text);
1247 undo_unblock(compose->undostruct);
1249 if (prefs_common.auto_exteditor)
1250 compose_exec_ext_editor(compose);
1252 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1254 SCROLL_TO_CURSOR(compose);
1256 compose->modified = FALSE;
1257 compose_set_title(compose);
1259 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1264 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1265 gboolean override_pref, const gchar *system)
1267 const gchar *privacy = NULL;
1269 cm_return_if_fail(compose != NULL);
1270 cm_return_if_fail(account != NULL);
1272 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1275 if (account->default_privacy_system && strlen(account->default_privacy_system))
1276 privacy = account->default_privacy_system;
1280 GSList *privacy_avail = privacy_get_system_ids();
1281 if (privacy_avail && g_slist_length(privacy_avail)) {
1282 privacy = (gchar *)(privacy_avail->data);
1284 g_slist_free_full(privacy_avail, g_free);
1286 if (privacy != NULL) {
1288 g_free(compose->privacy_system);
1289 compose->privacy_system = NULL;
1290 g_free(compose->encdata);
1291 compose->encdata = NULL;
1293 if (compose->privacy_system == NULL)
1294 compose->privacy_system = g_strdup(privacy);
1295 else if (*(compose->privacy_system) == '\0') {
1296 g_free(compose->privacy_system);
1297 g_free(compose->encdata);
1298 compose->encdata = NULL;
1299 compose->privacy_system = g_strdup(privacy);
1301 compose_update_privacy_system_menu_item(compose, FALSE);
1302 compose_use_encryption(compose, TRUE);
1306 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1308 const gchar *privacy = NULL;
1310 if (account->default_privacy_system && strlen(account->default_privacy_system))
1311 privacy = account->default_privacy_system;
1315 GSList *privacy_avail = privacy_get_system_ids();
1316 if (privacy_avail && g_slist_length(privacy_avail)) {
1317 privacy = (gchar *)(privacy_avail->data);
1321 if (privacy != NULL) {
1323 g_free(compose->privacy_system);
1324 compose->privacy_system = NULL;
1325 g_free(compose->encdata);
1326 compose->encdata = NULL;
1328 if (compose->privacy_system == NULL)
1329 compose->privacy_system = g_strdup(privacy);
1330 compose_update_privacy_system_menu_item(compose, FALSE);
1331 compose_use_signing(compose, TRUE);
1335 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1339 Compose *compose = NULL;
1341 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1343 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1344 cm_return_val_if_fail(msginfo != NULL, NULL);
1346 list_len = g_slist_length(msginfo_list);
1350 case COMPOSE_REPLY_TO_ADDRESS:
1351 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1352 FALSE, prefs_common.default_reply_list, FALSE, body);
1354 case COMPOSE_REPLY_WITH_QUOTE:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1356 FALSE, prefs_common.default_reply_list, FALSE, body);
1358 case COMPOSE_REPLY_WITHOUT_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1360 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1362 case COMPOSE_REPLY_TO_SENDER:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1364 FALSE, FALSE, TRUE, body);
1366 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1367 compose = compose_followup_and_reply_to(msginfo,
1368 COMPOSE_QUOTE_CHECK,
1369 FALSE, FALSE, body);
1371 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1373 FALSE, FALSE, TRUE, body);
1375 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1377 FALSE, FALSE, TRUE, NULL);
1379 case COMPOSE_REPLY_TO_ALL:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1381 TRUE, FALSE, FALSE, body);
1383 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1385 TRUE, FALSE, FALSE, body);
1387 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1389 TRUE, FALSE, FALSE, NULL);
1391 case COMPOSE_REPLY_TO_LIST:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1393 FALSE, TRUE, FALSE, body);
1395 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1397 FALSE, TRUE, FALSE, body);
1399 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1401 FALSE, TRUE, FALSE, NULL);
1403 case COMPOSE_FORWARD:
1404 if (prefs_common.forward_as_attachment) {
1405 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1408 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1412 case COMPOSE_FORWARD_INLINE:
1413 /* check if we reply to more than one Message */
1414 if (list_len == 1) {
1415 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1418 /* more messages FALL THROUGH */
1419 case COMPOSE_FORWARD_AS_ATTACH:
1420 compose = compose_forward_multiple(NULL, msginfo_list);
1422 case COMPOSE_REDIRECT:
1423 compose = compose_redirect(NULL, msginfo, FALSE);
1426 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1429 if (compose == NULL) {
1430 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1434 compose->rmode = mode;
1435 switch (compose->rmode) {
1437 case COMPOSE_REPLY_WITH_QUOTE:
1438 case COMPOSE_REPLY_WITHOUT_QUOTE:
1439 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1440 debug_print("reply mode Normal\n");
1441 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1442 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1444 case COMPOSE_REPLY_TO_SENDER:
1445 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1446 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1447 debug_print("reply mode Sender\n");
1448 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1450 case COMPOSE_REPLY_TO_ALL:
1451 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1452 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1453 debug_print("reply mode All\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1456 case COMPOSE_REPLY_TO_LIST:
1457 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1458 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1459 debug_print("reply mode List\n");
1460 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1462 case COMPOSE_REPLY_TO_ADDRESS:
1463 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1471 static Compose *compose_reply(MsgInfo *msginfo,
1472 ComposeQuoteMode quote_mode,
1478 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1479 to_sender, FALSE, body);
1482 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1483 ComposeQuoteMode quote_mode,
1488 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1489 to_sender, TRUE, body);
1492 static void compose_extract_original_charset(Compose *compose)
1494 MsgInfo *info = NULL;
1495 if (compose->replyinfo) {
1496 info = compose->replyinfo;
1497 } else if (compose->fwdinfo) {
1498 info = compose->fwdinfo;
1499 } else if (compose->targetinfo) {
1500 info = compose->targetinfo;
1503 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1504 MimeInfo *partinfo = mimeinfo;
1505 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1506 partinfo = procmime_mimeinfo_next(partinfo);
1508 compose->orig_charset =
1509 g_strdup(procmime_mimeinfo_get_parameter(
1510 partinfo, "charset"));
1512 procmime_mimeinfo_free_all(&mimeinfo);
1516 #define SIGNAL_BLOCK(buffer) { \
1517 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1518 G_CALLBACK(compose_changed_cb), \
1520 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1521 G_CALLBACK(text_inserted), \
1525 #define SIGNAL_UNBLOCK(buffer) { \
1526 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1527 G_CALLBACK(compose_changed_cb), \
1529 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1530 G_CALLBACK(text_inserted), \
1534 static Compose *compose_generic_reply(MsgInfo *msginfo,
1535 ComposeQuoteMode quote_mode,
1536 gboolean to_all, gboolean to_ml,
1538 gboolean followup_and_reply_to,
1542 PrefsAccount *account = NULL;
1543 GtkTextView *textview;
1544 GtkTextBuffer *textbuf;
1545 gboolean quote = FALSE;
1546 const gchar *qmark = NULL;
1547 const gchar *body_fmt = NULL;
1548 gchar *s_system = NULL;
1550 cm_return_val_if_fail(msginfo != NULL, NULL);
1551 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1553 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1555 cm_return_val_if_fail(account != NULL, NULL);
1557 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1559 compose->updating = TRUE;
1561 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1564 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1565 if (!compose->replyinfo)
1566 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1568 compose_extract_original_charset(compose);
1570 if (msginfo->folder && msginfo->folder->ret_rcpt)
1571 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1573 /* Set save folder */
1574 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1575 gchar *folderidentifier;
1577 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1578 folderidentifier = folder_item_get_identifier(msginfo->folder);
1579 compose_set_save_to(compose, folderidentifier);
1580 g_free(folderidentifier);
1583 if (compose_parse_header(compose, msginfo) < 0) {
1584 compose->updating = FALSE;
1585 compose_destroy(compose);
1589 /* override from name according to folder properties */
1590 if (msginfo->folder && msginfo->folder->prefs &&
1591 msginfo->folder->prefs->reply_with_format &&
1592 msginfo->folder->prefs->reply_override_from_format &&
1593 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1598 /* decode \-escape sequences in the internal representation of the quote format */
1599 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1600 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1603 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1604 compose->gtkaspell);
1606 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1608 quote_fmt_scan_string(tmp);
1611 buf = quote_fmt_get_buffer();
1613 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1615 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1616 quote_fmt_reset_vartable();
1617 quote_fmtlex_destroy();
1622 textview = (GTK_TEXT_VIEW(compose->text));
1623 textbuf = gtk_text_view_get_buffer(textview);
1624 compose_create_tags(textview, compose);
1626 undo_block(compose->undostruct);
1628 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1629 gtkaspell_block_check(compose->gtkaspell);
1632 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1633 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1634 /* use the reply format of folder (if enabled), or the account's one
1635 (if enabled) or fallback to the global reply format, which is always
1636 enabled (even if empty), and use the relevant quotemark */
1638 if (msginfo->folder && msginfo->folder->prefs &&
1639 msginfo->folder->prefs->reply_with_format) {
1640 qmark = msginfo->folder->prefs->reply_quotemark;
1641 body_fmt = msginfo->folder->prefs->reply_body_format;
1643 } else if (account->reply_with_format) {
1644 qmark = account->reply_quotemark;
1645 body_fmt = account->reply_body_format;
1648 qmark = prefs_common.quotemark;
1649 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1650 body_fmt = gettext(prefs_common.quotefmt);
1657 /* empty quotemark is not allowed */
1658 if (qmark == NULL || *qmark == '\0')
1660 compose_quote_fmt(compose, compose->replyinfo,
1661 body_fmt, qmark, body, FALSE, TRUE,
1662 _("The body of the \"Reply\" template has an error at line %d."));
1663 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1664 quote_fmt_reset_vartable();
1667 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1668 compose_force_encryption(compose, account, FALSE, s_system);
1671 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1672 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1673 compose_force_signing(compose, account, s_system);
1677 SIGNAL_BLOCK(textbuf);
1679 if (account->auto_sig)
1680 compose_insert_sig(compose, FALSE);
1682 compose_wrap_all(compose);
1685 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1686 gtkaspell_highlight_all(compose->gtkaspell);
1687 gtkaspell_unblock_check(compose->gtkaspell);
1689 SIGNAL_UNBLOCK(textbuf);
1691 gtk_widget_grab_focus(compose->text);
1693 undo_unblock(compose->undostruct);
1695 if (prefs_common.auto_exteditor)
1696 compose_exec_ext_editor(compose);
1698 compose->modified = FALSE;
1699 compose_set_title(compose);
1701 compose->updating = FALSE;
1702 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1703 SCROLL_TO_CURSOR(compose);
1705 if (compose->deferred_destroy) {
1706 compose_destroy(compose);
1714 #define INSERT_FW_HEADER(var, hdr) \
1715 if (msginfo->var && *msginfo->var) { \
1716 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1717 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1718 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1721 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1722 gboolean as_attach, const gchar *body,
1723 gboolean no_extedit,
1727 GtkTextView *textview;
1728 GtkTextBuffer *textbuf;
1729 gint cursor_pos = -1;
1732 cm_return_val_if_fail(msginfo != NULL, NULL);
1733 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1735 if (!account && !(account = compose_find_account(msginfo)))
1736 account = cur_account;
1738 if (!prefs_common.forward_as_attachment)
1739 mode = COMPOSE_FORWARD_INLINE;
1741 mode = COMPOSE_FORWARD;
1742 compose = compose_create(account, msginfo->folder, mode, batch);
1744 compose->updating = TRUE;
1745 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1746 if (!compose->fwdinfo)
1747 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1749 compose_extract_original_charset(compose);
1751 if (msginfo->subject && *msginfo->subject) {
1752 gchar *buf, *buf2, *p;
1754 buf = p = g_strdup(msginfo->subject);
1755 p += subject_get_prefix_length(p);
1756 memmove(buf, p, strlen(p) + 1);
1758 buf2 = g_strdup_printf("Fw: %s", buf);
1759 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1765 /* override from name according to folder properties */
1766 if (msginfo->folder && msginfo->folder->prefs &&
1767 msginfo->folder->prefs->forward_with_format &&
1768 msginfo->folder->prefs->forward_override_from_format &&
1769 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1773 MsgInfo *full_msginfo = NULL;
1776 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1778 full_msginfo = procmsg_msginfo_copy(msginfo);
1780 /* decode \-escape sequences in the internal representation of the quote format */
1781 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1782 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1785 gtkaspell_block_check(compose->gtkaspell);
1786 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1787 compose->gtkaspell);
1789 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1791 quote_fmt_scan_string(tmp);
1794 buf = quote_fmt_get_buffer();
1796 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1798 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1799 quote_fmt_reset_vartable();
1800 quote_fmtlex_destroy();
1803 procmsg_msginfo_free(&full_msginfo);
1806 textview = GTK_TEXT_VIEW(compose->text);
1807 textbuf = gtk_text_view_get_buffer(textview);
1808 compose_create_tags(textview, compose);
1810 undo_block(compose->undostruct);
1814 msgfile = procmsg_get_message_file(msginfo);
1815 if (!is_file_exist(msgfile))
1816 g_warning("%s: file does not exist", msgfile);
1818 compose_attach_append(compose, msgfile, msgfile,
1819 "message/rfc822", NULL);
1823 const gchar *qmark = NULL;
1824 const gchar *body_fmt = NULL;
1825 MsgInfo *full_msginfo;
1827 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1829 full_msginfo = procmsg_msginfo_copy(msginfo);
1831 /* use the forward format of folder (if enabled), or the account's one
1832 (if enabled) or fallback to the global forward format, which is always
1833 enabled (even if empty), and use the relevant quotemark */
1834 if (msginfo->folder && msginfo->folder->prefs &&
1835 msginfo->folder->prefs->forward_with_format) {
1836 qmark = msginfo->folder->prefs->forward_quotemark;
1837 body_fmt = msginfo->folder->prefs->forward_body_format;
1839 } else if (account->forward_with_format) {
1840 qmark = account->forward_quotemark;
1841 body_fmt = account->forward_body_format;
1844 qmark = prefs_common.fw_quotemark;
1845 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1846 body_fmt = gettext(prefs_common.fw_quotefmt);
1851 /* empty quotemark is not allowed */
1852 if (qmark == NULL || *qmark == '\0')
1855 compose_quote_fmt(compose, full_msginfo,
1856 body_fmt, qmark, body, FALSE, TRUE,
1857 _("The body of the \"Forward\" template has an error at line %d."));
1858 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1859 quote_fmt_reset_vartable();
1860 compose_attach_parts(compose, msginfo);
1862 procmsg_msginfo_free(&full_msginfo);
1865 SIGNAL_BLOCK(textbuf);
1867 if (account->auto_sig)
1868 compose_insert_sig(compose, FALSE);
1870 compose_wrap_all(compose);
1873 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1874 gtkaspell_highlight_all(compose->gtkaspell);
1875 gtkaspell_unblock_check(compose->gtkaspell);
1877 SIGNAL_UNBLOCK(textbuf);
1879 cursor_pos = quote_fmt_get_cursor_pos();
1880 if (cursor_pos == -1)
1881 gtk_widget_grab_focus(compose->header_last->entry);
1883 gtk_widget_grab_focus(compose->text);
1885 if (!no_extedit && prefs_common.auto_exteditor)
1886 compose_exec_ext_editor(compose);
1889 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1890 gchar *folderidentifier;
1892 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1893 folderidentifier = folder_item_get_identifier(msginfo->folder);
1894 compose_set_save_to(compose, folderidentifier);
1895 g_free(folderidentifier);
1898 undo_unblock(compose->undostruct);
1900 compose->modified = FALSE;
1901 compose_set_title(compose);
1903 compose->updating = FALSE;
1904 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1905 SCROLL_TO_CURSOR(compose);
1907 if (compose->deferred_destroy) {
1908 compose_destroy(compose);
1912 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1917 #undef INSERT_FW_HEADER
1919 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1922 GtkTextView *textview;
1923 GtkTextBuffer *textbuf;
1927 gboolean single_mail = TRUE;
1929 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1931 if (g_slist_length(msginfo_list) > 1)
1932 single_mail = FALSE;
1934 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1935 if (((MsgInfo *)msginfo->data)->folder == NULL)
1938 /* guess account from first selected message */
1940 !(account = compose_find_account(msginfo_list->data)))
1941 account = cur_account;
1943 cm_return_val_if_fail(account != NULL, NULL);
1945 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1946 if (msginfo->data) {
1947 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1948 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1952 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1953 g_warning("no msginfo_list");
1957 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1959 compose->updating = TRUE;
1961 /* override from name according to folder properties */
1962 if (msginfo_list->data) {
1963 MsgInfo *msginfo = msginfo_list->data;
1965 if (msginfo->folder && msginfo->folder->prefs &&
1966 msginfo->folder->prefs->forward_with_format &&
1967 msginfo->folder->prefs->forward_override_from_format &&
1968 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1973 /* decode \-escape sequences in the internal representation of the quote format */
1974 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1975 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1978 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1979 compose->gtkaspell);
1981 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1983 quote_fmt_scan_string(tmp);
1986 buf = quote_fmt_get_buffer();
1988 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1990 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1991 quote_fmt_reset_vartable();
1992 quote_fmtlex_destroy();
1998 textview = GTK_TEXT_VIEW(compose->text);
1999 textbuf = gtk_text_view_get_buffer(textview);
2000 compose_create_tags(textview, compose);
2002 undo_block(compose->undostruct);
2003 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2004 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2006 if (!is_file_exist(msgfile))
2007 g_warning("%s: file does not exist", msgfile);
2009 compose_attach_append(compose, msgfile, msgfile,
2010 "message/rfc822", NULL);
2015 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2016 if (info->subject && *info->subject) {
2017 gchar *buf, *buf2, *p;
2019 buf = p = g_strdup(info->subject);
2020 p += subject_get_prefix_length(p);
2021 memmove(buf, p, strlen(p) + 1);
2023 buf2 = g_strdup_printf("Fw: %s", buf);
2024 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2030 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2031 _("Fw: multiple emails"));
2034 SIGNAL_BLOCK(textbuf);
2036 if (account->auto_sig)
2037 compose_insert_sig(compose, FALSE);
2039 compose_wrap_all(compose);
2041 SIGNAL_UNBLOCK(textbuf);
2043 gtk_text_buffer_get_start_iter(textbuf, &iter);
2044 gtk_text_buffer_place_cursor(textbuf, &iter);
2046 if (prefs_common.auto_exteditor)
2047 compose_exec_ext_editor(compose);
2049 gtk_widget_grab_focus(compose->header_last->entry);
2050 undo_unblock(compose->undostruct);
2051 compose->modified = FALSE;
2052 compose_set_title(compose);
2054 compose->updating = FALSE;
2055 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2056 SCROLL_TO_CURSOR(compose);
2058 if (compose->deferred_destroy) {
2059 compose_destroy(compose);
2063 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2068 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2070 GtkTextIter start = *iter;
2071 GtkTextIter end_iter;
2072 int start_pos = gtk_text_iter_get_offset(&start);
2074 if (!compose->account->sig_sep)
2077 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2078 start_pos+strlen(compose->account->sig_sep));
2080 /* check sig separator */
2081 str = gtk_text_iter_get_text(&start, &end_iter);
2082 if (!strcmp(str, compose->account->sig_sep)) {
2084 /* check end of line (\n) */
2085 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2086 start_pos+strlen(compose->account->sig_sep));
2087 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2088 start_pos+strlen(compose->account->sig_sep)+1);
2089 tmp = gtk_text_iter_get_text(&start, &end_iter);
2090 if (!strcmp(tmp,"\n")) {
2102 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2104 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2105 Compose *compose = (Compose *)data;
2106 FolderItem *old_item = NULL;
2107 FolderItem *new_item = NULL;
2108 gchar *old_id, *new_id;
2110 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2111 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2114 old_item = hookdata->item;
2115 new_item = hookdata->item2;
2117 old_id = folder_item_get_identifier(old_item);
2118 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2120 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2121 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2122 compose->targetinfo->folder = new_item;
2125 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2126 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2127 compose->replyinfo->folder = new_item;
2130 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2131 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->fwdinfo->folder = new_item;
2140 static void compose_colorize_signature(Compose *compose)
2142 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2144 GtkTextIter end_iter;
2145 gtk_text_buffer_get_start_iter(buffer, &iter);
2146 while (gtk_text_iter_forward_line(&iter))
2147 if (compose_is_sig_separator(compose, buffer, &iter)) {
2148 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2149 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2153 #define BLOCK_WRAP() { \
2154 prev_autowrap = compose->autowrap; \
2155 buffer = gtk_text_view_get_buffer( \
2156 GTK_TEXT_VIEW(compose->text)); \
2157 compose->autowrap = FALSE; \
2159 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2160 G_CALLBACK(compose_changed_cb), \
2162 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2163 G_CALLBACK(text_inserted), \
2166 #define UNBLOCK_WRAP() { \
2167 compose->autowrap = prev_autowrap; \
2168 if (compose->autowrap) { \
2169 gint old = compose->draft_timeout_tag; \
2170 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2171 compose_wrap_all(compose); \
2172 compose->draft_timeout_tag = old; \
2175 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2176 G_CALLBACK(compose_changed_cb), \
2178 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2179 G_CALLBACK(text_inserted), \
2183 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2185 Compose *compose = NULL;
2186 PrefsAccount *account = NULL;
2187 GtkTextView *textview;
2188 GtkTextBuffer *textbuf;
2192 gboolean use_signing = FALSE;
2193 gboolean use_encryption = FALSE;
2194 gchar *privacy_system = NULL;
2195 int priority = PRIORITY_NORMAL;
2196 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2197 gboolean autowrap = prefs_common.autowrap;
2198 gboolean autoindent = prefs_common.auto_indent;
2199 HeaderEntry *manual_headers = NULL;
2201 cm_return_val_if_fail(msginfo != NULL, NULL);
2202 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2204 if (compose_put_existing_to_front(msginfo)) {
2208 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2209 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2210 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2211 gchar *queueheader_buf = NULL;
2214 /* Select Account from queue headers */
2215 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2216 "X-Claws-Account-Id:")) {
2217 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2218 account = account_find_from_id(id);
2219 g_free(queueheader_buf);
2221 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2222 "X-Sylpheed-Account-Id:")) {
2223 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2224 account = account_find_from_id(id);
2225 g_free(queueheader_buf);
2227 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2229 id = atoi(&queueheader_buf[strlen("NAID:")]);
2230 account = account_find_from_id(id);
2231 g_free(queueheader_buf);
2233 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2235 id = atoi(&queueheader_buf[strlen("MAID:")]);
2236 account = account_find_from_id(id);
2237 g_free(queueheader_buf);
2239 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2241 account = account_find_from_address(queueheader_buf, FALSE);
2242 g_free(queueheader_buf);
2244 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2246 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2247 use_signing = param;
2248 g_free(queueheader_buf);
2250 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2251 "X-Sylpheed-Sign:")) {
2252 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2253 use_signing = param;
2254 g_free(queueheader_buf);
2256 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2257 "X-Claws-Encrypt:")) {
2258 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2259 use_encryption = param;
2260 g_free(queueheader_buf);
2262 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2263 "X-Sylpheed-Encrypt:")) {
2264 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2265 use_encryption = param;
2266 g_free(queueheader_buf);
2268 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2269 "X-Claws-Auto-Wrapping:")) {
2270 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2272 g_free(queueheader_buf);
2274 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2275 "X-Claws-Auto-Indent:")) {
2276 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2278 g_free(queueheader_buf);
2280 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2281 "X-Claws-Privacy-System:")) {
2282 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2283 g_free(queueheader_buf);
2285 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2286 "X-Sylpheed-Privacy-System:")) {
2287 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2292 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2298 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2299 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2300 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2301 if (orig_item != NULL) {
2302 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2306 g_free(queueheader_buf);
2308 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2310 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2311 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2312 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2313 if (orig_item != NULL) {
2314 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2318 g_free(queueheader_buf);
2320 /* Get manual headers */
2321 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2322 "X-Claws-Manual-Headers:")) {
2323 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2324 if (listmh && *listmh != '\0') {
2325 debug_print("Got manual headers: %s\n", listmh);
2326 manual_headers = procheader_entries_from_str(listmh);
2329 g_free(queueheader_buf);
2332 account = msginfo->folder->folder->account;
2335 if (!account && prefs_common.reedit_account_autosel) {
2337 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2338 extract_address(from);
2339 account = account_find_from_address(from, FALSE);
2344 account = cur_account;
2346 cm_return_val_if_fail(account != NULL, NULL);
2348 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2350 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2351 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2352 compose->autowrap = autowrap;
2353 compose->replyinfo = replyinfo;
2354 compose->fwdinfo = fwdinfo;
2356 compose->updating = TRUE;
2357 compose->priority = priority;
2359 if (privacy_system != NULL) {
2360 compose->privacy_system = privacy_system;
2361 compose_use_signing(compose, use_signing);
2362 compose_use_encryption(compose, use_encryption);
2363 compose_update_privacy_system_menu_item(compose, FALSE);
2365 activate_privacy_system(compose, account, FALSE);
2368 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2369 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2371 compose_extract_original_charset(compose);
2373 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2374 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2375 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2376 gchar *queueheader_buf = NULL;
2378 /* Set message save folder */
2379 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2380 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2381 compose_set_save_to(compose, &queueheader_buf[4]);
2382 g_free(queueheader_buf);
2384 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2385 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2387 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2389 g_free(queueheader_buf);
2393 if (compose_parse_header(compose, msginfo) < 0) {
2394 compose->updating = FALSE;
2395 compose_destroy(compose);
2398 compose_reedit_set_entry(compose, msginfo);
2400 textview = GTK_TEXT_VIEW(compose->text);
2401 textbuf = gtk_text_view_get_buffer(textview);
2402 compose_create_tags(textview, compose);
2404 mark = gtk_text_buffer_get_insert(textbuf);
2405 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2407 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2408 G_CALLBACK(compose_changed_cb),
2411 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2412 fp = procmime_get_first_encrypted_text_content(msginfo);
2414 compose_force_encryption(compose, account, TRUE, NULL);
2417 fp = procmime_get_first_text_content(msginfo);
2420 g_warning("Can't get text part");
2424 gchar buf[BUFFSIZE];
2425 gboolean prev_autowrap;
2426 GtkTextBuffer *buffer;
2428 while (fgets(buf, sizeof(buf), fp) != NULL) {
2430 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2436 compose_attach_parts(compose, msginfo);
2438 compose_colorize_signature(compose);
2440 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2441 G_CALLBACK(compose_changed_cb),
2444 if (manual_headers != NULL) {
2445 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2446 procheader_entries_free(manual_headers);
2447 compose->updating = FALSE;
2448 compose_destroy(compose);
2451 procheader_entries_free(manual_headers);
2454 gtk_widget_grab_focus(compose->text);
2456 if (prefs_common.auto_exteditor) {
2457 compose_exec_ext_editor(compose);
2459 compose->modified = FALSE;
2460 compose_set_title(compose);
2462 compose->updating = FALSE;
2463 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2464 SCROLL_TO_CURSOR(compose);
2466 if (compose->deferred_destroy) {
2467 compose_destroy(compose);
2471 compose->sig_str = account_get_signature_str(compose->account);
2473 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2478 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2485 cm_return_val_if_fail(msginfo != NULL, NULL);
2488 account = account_get_reply_account(msginfo,
2489 prefs_common.reply_account_autosel);
2490 cm_return_val_if_fail(account != NULL, NULL);
2492 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2494 compose->updating = TRUE;
2496 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2497 compose->replyinfo = NULL;
2498 compose->fwdinfo = NULL;
2500 compose_show_first_last_header(compose, TRUE);
2502 gtk_widget_grab_focus(compose->header_last->entry);
2504 filename = procmsg_get_message_file(msginfo);
2506 if (filename == NULL) {
2507 compose->updating = FALSE;
2508 compose_destroy(compose);
2513 compose->redirect_filename = filename;
2515 /* Set save folder */
2516 item = msginfo->folder;
2517 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2518 gchar *folderidentifier;
2520 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2521 folderidentifier = folder_item_get_identifier(item);
2522 compose_set_save_to(compose, folderidentifier);
2523 g_free(folderidentifier);
2526 compose_attach_parts(compose, msginfo);
2528 if (msginfo->subject)
2529 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2531 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2533 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2534 _("The body of the \"Redirect\" template has an error at line %d."));
2535 quote_fmt_reset_vartable();
2536 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2538 compose_colorize_signature(compose);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2543 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2545 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2546 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2547 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2548 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2549 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2550 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2551 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2555 if (compose->toolbar->draft_btn)
2556 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2557 if (compose->toolbar->insert_btn)
2558 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2559 if (compose->toolbar->attach_btn)
2560 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2561 if (compose->toolbar->sig_btn)
2562 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2563 if (compose->toolbar->exteditor_btn)
2564 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2565 if (compose->toolbar->linewrap_current_btn)
2566 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2567 if (compose->toolbar->linewrap_all_btn)
2568 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2570 compose->modified = FALSE;
2571 compose_set_title(compose);
2572 compose->updating = FALSE;
2573 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2574 SCROLL_TO_CURSOR(compose);
2576 if (compose->deferred_destroy) {
2577 compose_destroy(compose);
2581 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2586 const GList *compose_get_compose_list(void)
2588 return compose_list;
2591 void compose_entry_append(Compose *compose, const gchar *address,
2592 ComposeEntryType type, ComposePrefType pref_type)
2594 const gchar *header;
2596 gboolean in_quote = FALSE;
2597 if (!address || *address == '\0') return;
2604 header = N_("Bcc:");
2606 case COMPOSE_REPLYTO:
2607 header = N_("Reply-To:");
2609 case COMPOSE_NEWSGROUPS:
2610 header = N_("Newsgroups:");
2612 case COMPOSE_FOLLOWUPTO:
2613 header = N_( "Followup-To:");
2615 case COMPOSE_INREPLYTO:
2616 header = N_( "In-Reply-To:");
2623 header = prefs_common_translated_header_name(header);
2625 cur = begin = (gchar *)address;
2627 /* we separate the line by commas, but not if we're inside a quoted
2629 while (*cur != '\0') {
2631 in_quote = !in_quote;
2632 if (*cur == ',' && !in_quote) {
2633 gchar *tmp = g_strdup(begin);
2635 tmp[cur-begin]='\0';
2638 while (*tmp == ' ' || *tmp == '\t')
2640 compose_add_header_entry(compose, header, tmp, pref_type);
2641 compose_entry_indicate(compose, tmp);
2648 gchar *tmp = g_strdup(begin);
2650 tmp[cur-begin]='\0';
2651 while (*tmp == ' ' || *tmp == '\t')
2653 compose_add_header_entry(compose, header, tmp, pref_type);
2654 compose_entry_indicate(compose, tmp);
2659 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2664 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2665 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2666 if (gtk_entry_get_text(entry) &&
2667 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2668 gtk_widget_modify_base(
2669 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2670 GTK_STATE_NORMAL, &default_header_bgcolor);
2671 gtk_widget_modify_text(
2672 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2673 GTK_STATE_NORMAL, &default_header_color);
2678 void compose_toolbar_cb(gint action, gpointer data)
2680 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2681 Compose *compose = (Compose*)toolbar_item->parent;
2683 cm_return_if_fail(compose != NULL);
2687 compose_send_cb(NULL, compose);
2690 compose_send_later_cb(NULL, compose);
2693 compose_draft(compose, COMPOSE_QUIT_EDITING);
2696 compose_insert_file_cb(NULL, compose);
2699 compose_attach_cb(NULL, compose);
2702 compose_insert_sig(compose, FALSE);
2705 compose_insert_sig(compose, TRUE);
2708 compose_ext_editor_cb(NULL, compose);
2710 case A_LINEWRAP_CURRENT:
2711 compose_beautify_paragraph(compose, NULL, TRUE);
2713 case A_LINEWRAP_ALL:
2714 compose_wrap_all_full(compose, TRUE);
2717 compose_address_cb(NULL, compose);
2720 case A_CHECK_SPELLING:
2721 compose_check_all(NULL, compose);
2724 case A_PRIVACY_SIGN:
2726 case A_PRIVACY_ENCRYPT:
2733 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2738 gchar *subject = NULL;
2742 gchar **attach = NULL;
2743 gchar *inreplyto = NULL;
2744 MailField mfield = NO_FIELD_PRESENT;
2746 /* get mailto parts but skip from */
2747 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2750 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2751 mfield = TO_FIELD_PRESENT;
2754 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2756 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2758 if (!g_utf8_validate (subject, -1, NULL)) {
2759 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2760 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2763 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2765 mfield = SUBJECT_FIELD_PRESENT;
2768 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2769 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2772 gboolean prev_autowrap = compose->autowrap;
2774 compose->autowrap = FALSE;
2776 mark = gtk_text_buffer_get_insert(buffer);
2777 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2779 if (!g_utf8_validate (body, -1, NULL)) {
2780 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2781 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2784 gtk_text_buffer_insert(buffer, &iter, body, -1);
2786 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2788 compose->autowrap = prev_autowrap;
2789 if (compose->autowrap)
2790 compose_wrap_all(compose);
2791 mfield = BODY_FIELD_PRESENT;
2795 gint i = 0, att = 0;
2796 gchar *warn_files = NULL;
2797 while (attach[i] != NULL) {
2798 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2799 if (utf8_filename) {
2800 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2801 gchar *tmp = g_strdup_printf("%s%s\n",
2802 warn_files?warn_files:"",
2808 g_free(utf8_filename);
2810 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2815 alertpanel_notice(ngettext(
2816 "The following file has been attached: \n%s",
2817 "The following files have been attached: \n%s", att), warn_files);
2822 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2835 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2837 static HeaderEntry hentry[] = {
2838 {"Reply-To:", NULL, TRUE },
2839 {"Cc:", NULL, TRUE },
2840 {"References:", NULL, FALSE },
2841 {"Bcc:", NULL, TRUE },
2842 {"Newsgroups:", NULL, TRUE },
2843 {"Followup-To:", NULL, TRUE },
2844 {"List-Post:", NULL, FALSE },
2845 {"X-Priority:", NULL, FALSE },
2846 {NULL, NULL, FALSE }
2863 cm_return_val_if_fail(msginfo != NULL, -1);
2865 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2866 procheader_get_header_fields(fp, hentry);
2869 if (hentry[H_REPLY_TO].body != NULL) {
2870 if (hentry[H_REPLY_TO].body[0] != '\0') {
2872 conv_unmime_header(hentry[H_REPLY_TO].body,
2875 g_free(hentry[H_REPLY_TO].body);
2876 hentry[H_REPLY_TO].body = NULL;
2878 if (hentry[H_CC].body != NULL) {
2879 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2880 g_free(hentry[H_CC].body);
2881 hentry[H_CC].body = NULL;
2883 if (hentry[H_REFERENCES].body != NULL) {
2884 if (compose->mode == COMPOSE_REEDIT)
2885 compose->references = hentry[H_REFERENCES].body;
2887 compose->references = compose_parse_references
2888 (hentry[H_REFERENCES].body, msginfo->msgid);
2889 g_free(hentry[H_REFERENCES].body);
2891 hentry[H_REFERENCES].body = NULL;
2893 if (hentry[H_BCC].body != NULL) {
2894 if (compose->mode == COMPOSE_REEDIT)
2896 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2897 g_free(hentry[H_BCC].body);
2898 hentry[H_BCC].body = NULL;
2900 if (hentry[H_NEWSGROUPS].body != NULL) {
2901 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2902 hentry[H_NEWSGROUPS].body = NULL;
2904 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2905 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2906 compose->followup_to =
2907 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2910 g_free(hentry[H_FOLLOWUP_TO].body);
2911 hentry[H_FOLLOWUP_TO].body = NULL;
2913 if (hentry[H_LIST_POST].body != NULL) {
2914 gchar *to = NULL, *start = NULL;
2916 extract_address(hentry[H_LIST_POST].body);
2917 if (hentry[H_LIST_POST].body[0] != '\0') {
2918 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2920 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2921 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2924 g_free(compose->ml_post);
2925 compose->ml_post = to;
2928 g_free(hentry[H_LIST_POST].body);
2929 hentry[H_LIST_POST].body = NULL;
2932 /* CLAWS - X-Priority */
2933 if (compose->mode == COMPOSE_REEDIT)
2934 if (hentry[H_X_PRIORITY].body != NULL) {
2937 priority = atoi(hentry[H_X_PRIORITY].body);
2938 g_free(hentry[H_X_PRIORITY].body);
2940 hentry[H_X_PRIORITY].body = NULL;
2942 if (priority < PRIORITY_HIGHEST ||
2943 priority > PRIORITY_LOWEST)
2944 priority = PRIORITY_NORMAL;
2946 compose->priority = priority;
2949 if (compose->mode == COMPOSE_REEDIT) {
2950 if (msginfo->inreplyto && *msginfo->inreplyto)
2951 compose->inreplyto = g_strdup(msginfo->inreplyto);
2953 if (msginfo->msgid && *msginfo->msgid &&
2954 compose->folder != NULL &&
2955 compose->folder->stype == F_DRAFT)
2956 compose->msgid = g_strdup(msginfo->msgid);
2958 if (msginfo->msgid && *msginfo->msgid)
2959 compose->inreplyto = g_strdup(msginfo->msgid);
2961 if (!compose->references) {
2962 if (msginfo->msgid && *msginfo->msgid) {
2963 if (msginfo->inreplyto && *msginfo->inreplyto)
2964 compose->references =
2965 g_strdup_printf("<%s>\n\t<%s>",
2969 compose->references =
2970 g_strconcat("<", msginfo->msgid, ">",
2972 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2973 compose->references =
2974 g_strconcat("<", msginfo->inreplyto, ">",
2983 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2988 cm_return_val_if_fail(msginfo != NULL, -1);
2990 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2991 procheader_get_header_fields(fp, entries);
2995 while (he != NULL && he->name != NULL) {
2997 GtkListStore *model = NULL;
2999 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3000 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3001 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3002 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3003 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3010 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3012 GSList *ref_id_list, *cur;
3016 ref_id_list = references_list_append(NULL, ref);
3017 if (!ref_id_list) return NULL;
3018 if (msgid && *msgid)
3019 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3024 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3025 /* "<" + Message-ID + ">" + CR+LF+TAB */
3026 len += strlen((gchar *)cur->data) + 5;
3028 if (len > MAX_REFERENCES_LEN) {
3029 /* remove second message-ID */
3030 if (ref_id_list && ref_id_list->next &&
3031 ref_id_list->next->next) {
3032 g_free(ref_id_list->next->data);
3033 ref_id_list = g_slist_remove
3034 (ref_id_list, ref_id_list->next->data);
3036 slist_free_strings_full(ref_id_list);
3043 new_ref = g_string_new("");
3044 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3045 if (new_ref->len > 0)
3046 g_string_append(new_ref, "\n\t");
3047 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3050 slist_free_strings_full(ref_id_list);
3052 new_ref_str = new_ref->str;
3053 g_string_free(new_ref, FALSE);
3058 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3059 const gchar *fmt, const gchar *qmark,
3060 const gchar *body, gboolean rewrap,
3061 gboolean need_unescape,
3062 const gchar *err_msg)
3064 MsgInfo* dummyinfo = NULL;
3065 gchar *quote_str = NULL;
3067 gboolean prev_autowrap;
3068 const gchar *trimmed_body = body;
3069 gint cursor_pos = -1;
3070 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3071 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3076 SIGNAL_BLOCK(buffer);
3079 dummyinfo = compose_msginfo_new_from_compose(compose);
3080 msginfo = dummyinfo;
3083 if (qmark != NULL) {
3085 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3086 compose->gtkaspell);
3088 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3090 quote_fmt_scan_string(qmark);
3093 buf = quote_fmt_get_buffer();
3096 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3098 Xstrdup_a(quote_str, buf, goto error)
3101 if (fmt && *fmt != '\0') {
3104 while (*trimmed_body == '\n')
3108 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3109 compose->gtkaspell);
3111 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3113 if (need_unescape) {
3116 /* decode \-escape sequences in the internal representation of the quote format */
3117 tmp = g_malloc(strlen(fmt)+1);
3118 pref_get_unescaped_pref(tmp, fmt);
3119 quote_fmt_scan_string(tmp);
3123 quote_fmt_scan_string(fmt);
3127 buf = quote_fmt_get_buffer();
3130 gint line = quote_fmt_get_line();
3131 alertpanel_error(err_msg, line);
3139 prev_autowrap = compose->autowrap;
3140 compose->autowrap = FALSE;
3142 mark = gtk_text_buffer_get_insert(buffer);
3143 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3144 if (g_utf8_validate(buf, -1, NULL)) {
3145 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3147 gchar *tmpout = NULL;
3148 tmpout = conv_codeset_strdup
3149 (buf, conv_get_locale_charset_str_no_utf8(),
3151 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3153 tmpout = g_malloc(strlen(buf)*2+1);
3154 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3156 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3160 cursor_pos = quote_fmt_get_cursor_pos();
3161 if (cursor_pos == -1)
3162 cursor_pos = gtk_text_iter_get_offset(&iter);
3163 compose->set_cursor_pos = cursor_pos;
3165 gtk_text_buffer_get_start_iter(buffer, &iter);
3166 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3167 gtk_text_buffer_place_cursor(buffer, &iter);
3169 compose->autowrap = prev_autowrap;
3170 if (compose->autowrap && rewrap)
3171 compose_wrap_all(compose);
3178 SIGNAL_UNBLOCK(buffer);
3180 procmsg_msginfo_free( &dummyinfo );
3185 /* if ml_post is of type addr@host and from is of type
3186 * addr-anything@host, return TRUE
3188 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3190 gchar *left_ml = NULL;
3191 gchar *right_ml = NULL;
3192 gchar *left_from = NULL;
3193 gchar *right_from = NULL;
3194 gboolean result = FALSE;
3196 if (!ml_post || !from)
3199 left_ml = g_strdup(ml_post);
3200 if (strstr(left_ml, "@")) {
3201 right_ml = strstr(left_ml, "@")+1;
3202 *(strstr(left_ml, "@")) = '\0';
3205 left_from = g_strdup(from);
3206 if (strstr(left_from, "@")) {
3207 right_from = strstr(left_from, "@")+1;
3208 *(strstr(left_from, "@")) = '\0';
3211 if (right_ml && right_from
3212 && !strncmp(left_from, left_ml, strlen(left_ml))
3213 && !strcmp(right_from, right_ml)) {
3222 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3223 gboolean respect_default_to)
3227 if (!folder || !folder->prefs)
3230 if (respect_default_to && folder->prefs->enable_default_to) {
3231 compose_entry_append(compose, folder->prefs->default_to,
3232 COMPOSE_TO, PREF_FOLDER);
3233 compose_entry_indicate(compose, folder->prefs->default_to);
3235 if (folder->prefs->enable_default_cc) {
3236 compose_entry_append(compose, folder->prefs->default_cc,
3237 COMPOSE_CC, PREF_FOLDER);
3238 compose_entry_indicate(compose, folder->prefs->default_cc);
3240 if (folder->prefs->enable_default_bcc) {
3241 compose_entry_append(compose, folder->prefs->default_bcc,
3242 COMPOSE_BCC, PREF_FOLDER);
3243 compose_entry_indicate(compose, folder->prefs->default_bcc);
3245 if (folder->prefs->enable_default_replyto) {
3246 compose_entry_append(compose, folder->prefs->default_replyto,
3247 COMPOSE_REPLYTO, PREF_FOLDER);
3248 compose_entry_indicate(compose, folder->prefs->default_replyto);
3252 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3257 if (!compose || !msginfo)
3260 if (msginfo->subject && *msginfo->subject) {
3261 buf = p = g_strdup(msginfo->subject);
3262 p += subject_get_prefix_length(p);
3263 memmove(buf, p, strlen(p) + 1);
3265 buf2 = g_strdup_printf("Re: %s", buf);
3266 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3271 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3274 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3275 gboolean to_all, gboolean to_ml,
3277 gboolean followup_and_reply_to)
3279 GSList *cc_list = NULL;
3282 gchar *replyto = NULL;
3283 gchar *ac_email = NULL;
3285 gboolean reply_to_ml = FALSE;
3286 gboolean default_reply_to = FALSE;
3288 cm_return_if_fail(compose->account != NULL);
3289 cm_return_if_fail(msginfo != NULL);
3291 reply_to_ml = to_ml && compose->ml_post;
3293 default_reply_to = msginfo->folder &&
3294 msginfo->folder->prefs->enable_default_reply_to;
3296 if (compose->account->protocol != A_NNTP) {
3297 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3299 if (reply_to_ml && !default_reply_to) {
3301 gboolean is_subscr = is_subscription(compose->ml_post,
3304 /* normal answer to ml post with a reply-to */
3305 compose_entry_append(compose,
3307 COMPOSE_TO, PREF_ML);
3308 if (compose->replyto)
3309 compose_entry_append(compose,
3311 COMPOSE_CC, PREF_ML);
3313 /* answer to subscription confirmation */
3314 if (compose->replyto)
3315 compose_entry_append(compose,
3317 COMPOSE_TO, PREF_ML);
3318 else if (msginfo->from)
3319 compose_entry_append(compose,
3321 COMPOSE_TO, PREF_ML);
3324 else if (!(to_all || to_sender) && default_reply_to) {
3325 compose_entry_append(compose,
3326 msginfo->folder->prefs->default_reply_to,
3327 COMPOSE_TO, PREF_FOLDER);
3328 compose_entry_indicate(compose,
3329 msginfo->folder->prefs->default_reply_to);
3335 compose_entry_append(compose, msginfo->from,
3336 COMPOSE_TO, PREF_NONE);
3338 Xstrdup_a(tmp1, msginfo->from, return);
3339 extract_address(tmp1);
3340 compose_entry_append(compose,
3341 (!account_find_from_address(tmp1, FALSE))
3344 COMPOSE_TO, PREF_NONE);
3345 if (compose->replyto)
3346 compose_entry_append(compose,
3348 COMPOSE_CC, PREF_NONE);
3350 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3351 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3352 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3353 if (compose->replyto) {
3354 compose_entry_append(compose,
3356 COMPOSE_TO, PREF_NONE);
3358 compose_entry_append(compose,
3359 msginfo->from ? msginfo->from : "",
3360 COMPOSE_TO, PREF_NONE);
3363 /* replying to own mail, use original recp */
3364 compose_entry_append(compose,
3365 msginfo->to ? msginfo->to : "",
3366 COMPOSE_TO, PREF_NONE);
3367 compose_entry_append(compose,
3368 msginfo->cc ? msginfo->cc : "",
3369 COMPOSE_CC, PREF_NONE);
3374 if (to_sender || (compose->followup_to &&
3375 !strncmp(compose->followup_to, "poster", 6)))
3376 compose_entry_append
3378 (compose->replyto ? compose->replyto :
3379 msginfo->from ? msginfo->from : ""),
3380 COMPOSE_TO, PREF_NONE);
3382 else if (followup_and_reply_to || to_all) {
3383 compose_entry_append
3385 (compose->replyto ? compose->replyto :
3386 msginfo->from ? msginfo->from : ""),
3387 COMPOSE_TO, PREF_NONE);
3389 compose_entry_append
3391 compose->followup_to ? compose->followup_to :
3392 compose->newsgroups ? compose->newsgroups : "",
3393 COMPOSE_NEWSGROUPS, PREF_NONE);
3395 compose_entry_append
3397 msginfo->cc ? msginfo->cc : "",
3398 COMPOSE_CC, PREF_NONE);
3401 compose_entry_append
3403 compose->followup_to ? compose->followup_to :
3404 compose->newsgroups ? compose->newsgroups : "",
3405 COMPOSE_NEWSGROUPS, PREF_NONE);
3407 compose_reply_set_subject(compose, msginfo);
3409 if (to_ml && compose->ml_post) return;
3410 if (!to_all || compose->account->protocol == A_NNTP) return;
3412 if (compose->replyto) {
3413 Xstrdup_a(replyto, compose->replyto, return);
3414 extract_address(replyto);
3416 if (msginfo->from) {
3417 Xstrdup_a(from, msginfo->from, return);
3418 extract_address(from);
3421 if (replyto && from)
3422 cc_list = address_list_append_with_comments(cc_list, from);
3423 if (to_all && msginfo->folder &&
3424 msginfo->folder->prefs->enable_default_reply_to)
3425 cc_list = address_list_append_with_comments(cc_list,
3426 msginfo->folder->prefs->default_reply_to);
3427 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3428 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3430 ac_email = g_utf8_strdown(compose->account->address, -1);
3433 for (cur = cc_list; cur != NULL; cur = cur->next) {
3434 gchar *addr = g_utf8_strdown(cur->data, -1);
3435 extract_address(addr);
3437 if (strcmp(ac_email, addr))
3438 compose_entry_append(compose, (gchar *)cur->data,
3439 COMPOSE_CC, PREF_NONE);
3441 debug_print("Cc address same as compose account's, ignoring\n");
3446 slist_free_strings_full(cc_list);
3452 #define SET_ENTRY(entry, str) \
3455 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3458 #define SET_ADDRESS(type, str) \
3461 compose_entry_append(compose, str, type, PREF_NONE); \
3464 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3466 cm_return_if_fail(msginfo != NULL);
3468 SET_ENTRY(subject_entry, msginfo->subject);
3469 SET_ENTRY(from_name, msginfo->from);
3470 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3471 SET_ADDRESS(COMPOSE_CC, compose->cc);
3472 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3473 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3474 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3475 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3477 compose_update_priority_menu_item(compose);
3478 compose_update_privacy_system_menu_item(compose, FALSE);
3479 compose_show_first_last_header(compose, TRUE);
3485 static void compose_insert_sig(Compose *compose, gboolean replace)
3487 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3488 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3490 GtkTextIter iter, iter_end;
3491 gint cur_pos, ins_pos;
3492 gboolean prev_autowrap;
3493 gboolean found = FALSE;
3494 gboolean exists = FALSE;
3496 cm_return_if_fail(compose->account != NULL);
3500 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3501 G_CALLBACK(compose_changed_cb),
3504 mark = gtk_text_buffer_get_insert(buffer);
3505 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3506 cur_pos = gtk_text_iter_get_offset (&iter);
3509 gtk_text_buffer_get_end_iter(buffer, &iter);
3511 exists = (compose->sig_str != NULL);
3514 GtkTextIter first_iter, start_iter, end_iter;
3516 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3518 if (!exists || compose->sig_str[0] == '\0')
3521 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3522 compose->signature_tag);
3525 /* include previous \n\n */
3526 gtk_text_iter_backward_chars(&first_iter, 1);
3527 start_iter = first_iter;
3528 end_iter = first_iter;
3530 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3531 compose->signature_tag);
3532 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3533 compose->signature_tag);
3535 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3541 g_free(compose->sig_str);
3542 compose->sig_str = account_get_signature_str(compose->account);
3544 cur_pos = gtk_text_iter_get_offset(&iter);
3546 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3547 g_free(compose->sig_str);
3548 compose->sig_str = NULL;
3550 if (compose->sig_inserted == FALSE)
3551 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3552 compose->sig_inserted = TRUE;
3554 cur_pos = gtk_text_iter_get_offset(&iter);
3555 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3557 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3558 gtk_text_iter_forward_chars(&iter, 1);
3559 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3560 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3562 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3563 cur_pos = gtk_text_buffer_get_char_count (buffer);
3566 /* put the cursor where it should be
3567 * either where the quote_fmt says, either where it was */
3568 if (compose->set_cursor_pos < 0)
3569 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3571 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3572 compose->set_cursor_pos);
3574 compose->set_cursor_pos = -1;
3575 gtk_text_buffer_place_cursor(buffer, &iter);
3576 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3577 G_CALLBACK(compose_changed_cb),
3583 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3586 GtkTextBuffer *buffer;
3589 const gchar *cur_encoding;
3590 gchar buf[BUFFSIZE];
3593 gboolean prev_autowrap;
3597 GError *error = NULL;
3603 GString *file_contents = NULL;
3604 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3606 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3608 /* get the size of the file we are about to insert */
3610 f = g_file_new_for_path(file);
3611 fi = g_file_query_info(f, "standard::size",
3612 G_FILE_QUERY_INFO_NONE, NULL, &error);
3614 if (error != NULL) {
3615 g_warning(error->message);
3617 g_error_free(error);
3621 ret = g_stat(file, &file_stat);
3624 gchar *shortfile = g_path_get_basename(file);
3625 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3627 return COMPOSE_INSERT_NO_FILE;
3628 } else if (prefs_common.warn_large_insert == TRUE) {
3630 size = g_file_info_get_size(fi);
3634 size = file_stat.st_size;
3637 /* ask user for confirmation if the file is large */
3638 if (prefs_common.warn_large_insert_size < 0 ||
3639 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3643 msg = g_strdup_printf(_("You are about to insert a file of %s "
3644 "in the message body. Are you sure you want to do that?"),
3645 to_human_readable(size));
3646 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3647 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3648 NULL, ALERT_QUESTION);
3651 /* do we ask for confirmation next time? */
3652 if (aval & G_ALERTDISABLE) {
3653 /* no confirmation next time, disable feature in preferences */
3654 aval &= ~G_ALERTDISABLE;
3655 prefs_common.warn_large_insert = FALSE;
3658 /* abort file insertion if user canceled action */
3659 if (aval != G_ALERTALTERNATE) {
3660 return COMPOSE_INSERT_NO_FILE;
3666 if ((fp = g_fopen(file, "rb")) == NULL) {
3667 FILE_OP_ERROR(file, "fopen");
3668 return COMPOSE_INSERT_READ_ERROR;
3671 prev_autowrap = compose->autowrap;
3672 compose->autowrap = FALSE;
3674 text = GTK_TEXT_VIEW(compose->text);
3675 buffer = gtk_text_view_get_buffer(text);
3676 mark = gtk_text_buffer_get_insert(buffer);
3677 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3679 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3680 G_CALLBACK(text_inserted),
3683 cur_encoding = conv_get_locale_charset_str_no_utf8();
3685 file_contents = g_string_new("");
3686 while (fgets(buf, sizeof(buf), fp) != NULL) {
3689 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3690 str = g_strdup(buf);
3692 codeconv_set_strict(TRUE);
3693 str = conv_codeset_strdup
3694 (buf, cur_encoding, CS_INTERNAL);
3695 codeconv_set_strict(FALSE);
3698 result = COMPOSE_INSERT_INVALID_CHARACTER;
3704 /* strip <CR> if DOS/Windows file,
3705 replace <CR> with <LF> if Macintosh file. */
3708 if (len > 0 && str[len - 1] != '\n') {
3710 if (str[len] == '\r') str[len] = '\n';
3713 file_contents = g_string_append(file_contents, str);
3717 if (result == COMPOSE_INSERT_SUCCESS) {
3718 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3720 compose_changed_cb(NULL, compose);
3721 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3722 G_CALLBACK(text_inserted),
3724 compose->autowrap = prev_autowrap;
3725 if (compose->autowrap)
3726 compose_wrap_all(compose);
3729 g_string_free(file_contents, TRUE);
3735 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3736 const gchar *filename,
3737 const gchar *content_type,
3738 const gchar *charset)
3746 GtkListStore *store;
3748 gboolean has_binary = FALSE;
3750 if (!is_file_exist(file)) {
3751 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3752 gboolean result = FALSE;
3753 if (file_from_uri && is_file_exist(file_from_uri)) {
3754 result = compose_attach_append(
3755 compose, file_from_uri,
3756 filename, content_type,
3759 g_free(file_from_uri);
3762 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3765 if ((size = get_file_size(file)) < 0) {
3766 alertpanel_error("Can't get file size of %s\n", filename);
3770 /* In batch mode, we allow 0-length files to be attached no questions asked */
3771 if (size == 0 && !compose->batch) {
3772 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3773 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3774 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3775 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3778 if (aval != G_ALERTALTERNATE) {
3782 if ((fp = g_fopen(file, "rb")) == NULL) {
3783 alertpanel_error(_("Can't read %s."), filename);
3788 ainfo = g_new0(AttachInfo, 1);
3789 auto_ainfo = g_auto_pointer_new_with_free
3790 (ainfo, (GFreeFunc) compose_attach_info_free);
3791 ainfo->file = g_strdup(file);
3794 ainfo->content_type = g_strdup(content_type);
3795 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3797 MsgFlags flags = {0, 0};
3799 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3800 ainfo->encoding = ENC_7BIT;
3802 ainfo->encoding = ENC_8BIT;
3804 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3805 if (msginfo && msginfo->subject)
3806 name = g_strdup(msginfo->subject);
3808 name = g_path_get_basename(filename ? filename : file);
3810 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3812 procmsg_msginfo_free(&msginfo);
3814 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3815 ainfo->charset = g_strdup(charset);
3816 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3818 ainfo->encoding = ENC_BASE64;
3820 name = g_path_get_basename(filename ? filename : file);
3821 ainfo->name = g_strdup(name);
3825 ainfo->content_type = procmime_get_mime_type(file);
3826 if (!ainfo->content_type) {
3827 ainfo->content_type =
3828 g_strdup("application/octet-stream");
3829 ainfo->encoding = ENC_BASE64;
3830 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3832 procmime_get_encoding_for_text_file(file, &has_binary);
3834 ainfo->encoding = ENC_BASE64;
3835 name = g_path_get_basename(filename ? filename : file);
3836 ainfo->name = g_strdup(name);
3840 if (ainfo->name != NULL
3841 && !strcmp(ainfo->name, ".")) {
3842 g_free(ainfo->name);
3846 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3847 g_free(ainfo->content_type);
3848 ainfo->content_type = g_strdup("application/octet-stream");
3849 g_free(ainfo->charset);
3850 ainfo->charset = NULL;
3853 ainfo->size = (goffset)size;
3854 size_text = to_human_readable((goffset)size);
3856 store = GTK_LIST_STORE(gtk_tree_view_get_model
3857 (GTK_TREE_VIEW(compose->attach_clist)));
3859 gtk_list_store_append(store, &iter);
3860 gtk_list_store_set(store, &iter,
3861 COL_MIMETYPE, ainfo->content_type,
3862 COL_SIZE, size_text,
3863 COL_NAME, ainfo->name,
3864 COL_CHARSET, ainfo->charset,
3866 COL_AUTODATA, auto_ainfo,
3869 g_auto_pointer_free(auto_ainfo);
3870 compose_attach_update_label(compose);
3874 void compose_use_signing(Compose *compose, gboolean use_signing)
3876 compose->use_signing = use_signing;
3877 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3880 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3882 compose->use_encryption = use_encryption;
3883 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3886 #define NEXT_PART_NOT_CHILD(info) \
3888 node = info->node; \
3889 while (node->children) \
3890 node = g_node_last_child(node); \
3891 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3894 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3898 MimeInfo *firsttext = NULL;
3899 MimeInfo *encrypted = NULL;
3902 const gchar *partname = NULL;
3904 mimeinfo = procmime_scan_message(msginfo);
3905 if (!mimeinfo) return;
3907 if (mimeinfo->node->children == NULL) {
3908 procmime_mimeinfo_free_all(&mimeinfo);
3912 /* find first content part */
3913 child = (MimeInfo *) mimeinfo->node->children->data;
3914 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3915 child = (MimeInfo *)child->node->children->data;
3918 if (child->type == MIMETYPE_TEXT) {
3920 debug_print("First text part found\n");
3921 } else if (compose->mode == COMPOSE_REEDIT &&
3922 child->type == MIMETYPE_APPLICATION &&
3923 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3924 encrypted = (MimeInfo *)child->node->parent->data;
3927 child = (MimeInfo *) mimeinfo->node->children->data;
3928 while (child != NULL) {
3931 if (child == encrypted) {
3932 /* skip this part of tree */
3933 NEXT_PART_NOT_CHILD(child);
3937 if (child->type == MIMETYPE_MULTIPART) {
3938 /* get the actual content */
3939 child = procmime_mimeinfo_next(child);
3943 if (child == firsttext) {
3944 child = procmime_mimeinfo_next(child);
3948 outfile = procmime_get_tmp_file_name(child);
3949 if ((err = procmime_get_part(outfile, child)) < 0)
3950 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3952 gchar *content_type;
3954 content_type = procmime_get_content_type_str(child->type, child->subtype);
3956 /* if we meet a pgp signature, we don't attach it, but
3957 * we force signing. */
3958 if ((strcmp(content_type, "application/pgp-signature") &&
3959 strcmp(content_type, "application/pkcs7-signature") &&
3960 strcmp(content_type, "application/x-pkcs7-signature"))
3961 || compose->mode == COMPOSE_REDIRECT) {
3962 partname = procmime_mimeinfo_get_parameter(child, "filename");
3963 if (partname == NULL)
3964 partname = procmime_mimeinfo_get_parameter(child, "name");
3965 if (partname == NULL)
3967 compose_attach_append(compose, outfile,
3968 partname, content_type,
3969 procmime_mimeinfo_get_parameter(child, "charset"));
3971 compose_force_signing(compose, compose->account, NULL);
3973 g_free(content_type);
3976 NEXT_PART_NOT_CHILD(child);
3978 procmime_mimeinfo_free_all(&mimeinfo);
3981 #undef NEXT_PART_NOT_CHILD
3986 WAIT_FOR_INDENT_CHAR,
3987 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3990 /* return indent length, we allow:
3991 indent characters followed by indent characters or spaces/tabs,
3992 alphabets and numbers immediately followed by indent characters,
3993 and the repeating sequences of the above
3994 If quote ends with multiple spaces, only the first one is included. */
3995 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3996 const GtkTextIter *start, gint *len)
3998 GtkTextIter iter = *start;
4002 IndentState state = WAIT_FOR_INDENT_CHAR;
4005 gint alnum_count = 0;
4006 gint space_count = 0;
4009 if (prefs_common.quote_chars == NULL) {
4013 while (!gtk_text_iter_ends_line(&iter)) {
4014 wc = gtk_text_iter_get_char(&iter);
4015 if (g_unichar_iswide(wc))
4017 clen = g_unichar_to_utf8(wc, ch);
4021 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4022 is_space = g_unichar_isspace(wc);
4024 if (state == WAIT_FOR_INDENT_CHAR) {
4025 if (!is_indent && !g_unichar_isalnum(wc))
4028 quote_len += alnum_count + space_count + 1;
4029 alnum_count = space_count = 0;
4030 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4033 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4034 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4038 else if (is_indent) {
4039 quote_len += alnum_count + space_count + 1;
4040 alnum_count = space_count = 0;
4043 state = WAIT_FOR_INDENT_CHAR;
4047 gtk_text_iter_forward_char(&iter);
4050 if (quote_len > 0 && space_count > 0)
4056 if (quote_len > 0) {
4058 gtk_text_iter_forward_chars(&iter, quote_len);
4059 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4065 /* return >0 if the line is itemized */
4066 static int compose_itemized_length(GtkTextBuffer *buffer,
4067 const GtkTextIter *start)
4069 GtkTextIter iter = *start;
4074 if (gtk_text_iter_ends_line(&iter))
4079 wc = gtk_text_iter_get_char(&iter);
4080 if (!g_unichar_isspace(wc))
4082 gtk_text_iter_forward_char(&iter);
4083 if (gtk_text_iter_ends_line(&iter))
4087 clen = g_unichar_to_utf8(wc, ch);
4088 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4090 wc == 0x2022 || /* BULLET */
4091 wc == 0x2023 || /* TRIANGULAR BULLET */
4092 wc == 0x2043 || /* HYPHEN BULLET */
4093 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4094 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4095 wc == 0x2219 || /* BULLET OPERATOR */
4096 wc == 0x25d8 || /* INVERSE BULLET */
4097 wc == 0x25e6 || /* WHITE BULLET */
4098 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4099 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4100 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4101 wc == 0x29be || /* CIRCLED WHITE BULLET */
4102 wc == 0x29bf /* CIRCLED BULLET */
4106 gtk_text_iter_forward_char(&iter);
4107 if (gtk_text_iter_ends_line(&iter))
4109 wc = gtk_text_iter_get_char(&iter);
4110 if (g_unichar_isspace(wc)) {
4116 /* return the string at the start of the itemization */
4117 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4118 const GtkTextIter *start)
4120 GtkTextIter iter = *start;
4123 GString *item_chars = g_string_new("");
4126 if (gtk_text_iter_ends_line(&iter))
4131 wc = gtk_text_iter_get_char(&iter);
4132 if (!g_unichar_isspace(wc))
4134 gtk_text_iter_forward_char(&iter);
4135 if (gtk_text_iter_ends_line(&iter))
4137 g_string_append_unichar(item_chars, wc);
4140 str = item_chars->str;
4141 g_string_free(item_chars, FALSE);
4145 /* return the number of spaces at a line's start */
4146 static int compose_left_offset_length(GtkTextBuffer *buffer,
4147 const GtkTextIter *start)
4149 GtkTextIter iter = *start;
4152 if (gtk_text_iter_ends_line(&iter))
4156 wc = gtk_text_iter_get_char(&iter);
4157 if (!g_unichar_isspace(wc))
4160 gtk_text_iter_forward_char(&iter);
4161 if (gtk_text_iter_ends_line(&iter))
4165 gtk_text_iter_forward_char(&iter);
4166 if (gtk_text_iter_ends_line(&iter))
4171 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4172 const GtkTextIter *start,
4173 GtkTextIter *break_pos,
4177 GtkTextIter iter = *start, line_end = *start;
4178 PangoLogAttr *attrs;
4185 gboolean can_break = FALSE;
4186 gboolean do_break = FALSE;
4187 gboolean was_white = FALSE;
4188 gboolean prev_dont_break = FALSE;
4190 gtk_text_iter_forward_to_line_end(&line_end);
4191 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4192 len = g_utf8_strlen(str, -1);
4196 g_warning("compose_get_line_break_pos: len = 0!");
4200 /* g_print("breaking line: %d: %s (len = %d)\n",
4201 gtk_text_iter_get_line(&iter), str, len); */
4203 attrs = g_new(PangoLogAttr, len + 1);
4205 pango_default_break(str, -1, NULL, attrs, len + 1);
4209 /* skip quote and leading spaces */
4210 for (i = 0; *p != '\0' && i < len; i++) {
4213 wc = g_utf8_get_char(p);
4214 if (i >= quote_len && !g_unichar_isspace(wc))
4216 if (g_unichar_iswide(wc))
4218 else if (*p == '\t')
4222 p = g_utf8_next_char(p);
4225 for (; *p != '\0' && i < len; i++) {
4226 PangoLogAttr *attr = attrs + i;
4230 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4233 was_white = attr->is_white;
4235 /* don't wrap URI */
4236 if ((uri_len = get_uri_len(p)) > 0) {
4238 if (pos > 0 && col > max_col) {
4248 wc = g_utf8_get_char(p);
4249 if (g_unichar_iswide(wc)) {
4251 if (prev_dont_break && can_break && attr->is_line_break)
4253 } else if (*p == '\t')
4257 if (pos > 0 && col > max_col) {
4262 if (*p == '-' || *p == '/')
4263 prev_dont_break = TRUE;
4265 prev_dont_break = FALSE;
4267 p = g_utf8_next_char(p);
4271 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4276 *break_pos = *start;
4277 gtk_text_iter_set_line_offset(break_pos, pos);
4282 static gboolean compose_join_next_line(Compose *compose,
4283 GtkTextBuffer *buffer,
4285 const gchar *quote_str)
4287 GtkTextIter iter_ = *iter, cur, prev, next, end;
4288 PangoLogAttr attrs[3];
4290 gchar *next_quote_str;
4293 gboolean keep_cursor = FALSE;
4295 if (!gtk_text_iter_forward_line(&iter_) ||
4296 gtk_text_iter_ends_line(&iter_)) {
4299 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4301 if ((quote_str || next_quote_str) &&
4302 strcmp2(quote_str, next_quote_str) != 0) {
4303 g_free(next_quote_str);
4306 g_free(next_quote_str);
4309 if (quote_len > 0) {
4310 gtk_text_iter_forward_chars(&end, quote_len);
4311 if (gtk_text_iter_ends_line(&end)) {
4316 /* don't join itemized lines */
4317 if (compose_itemized_length(buffer, &end) > 0) {
4321 /* don't join signature separator */
4322 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4325 /* delete quote str */
4327 gtk_text_buffer_delete(buffer, &iter_, &end);
4329 /* don't join line breaks put by the user */
4331 gtk_text_iter_backward_char(&cur);
4332 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4333 gtk_text_iter_forward_char(&cur);
4337 gtk_text_iter_forward_char(&cur);
4338 /* delete linebreak and extra spaces */
4339 while (gtk_text_iter_backward_char(&cur)) {
4340 wc1 = gtk_text_iter_get_char(&cur);
4341 if (!g_unichar_isspace(wc1))
4346 while (!gtk_text_iter_ends_line(&cur)) {
4347 wc1 = gtk_text_iter_get_char(&cur);
4348 if (!g_unichar_isspace(wc1))
4350 gtk_text_iter_forward_char(&cur);
4353 if (!gtk_text_iter_equal(&prev, &next)) {
4356 mark = gtk_text_buffer_get_insert(buffer);
4357 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4358 if (gtk_text_iter_equal(&prev, &cur))
4360 gtk_text_buffer_delete(buffer, &prev, &next);
4364 /* insert space if required */
4365 gtk_text_iter_backward_char(&prev);
4366 wc1 = gtk_text_iter_get_char(&prev);
4367 wc2 = gtk_text_iter_get_char(&next);
4368 gtk_text_iter_forward_char(&next);
4369 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4370 pango_default_break(str, -1, NULL, attrs, 3);
4371 if (!attrs[1].is_line_break ||
4372 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4373 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4375 gtk_text_iter_backward_char(&iter_);
4376 gtk_text_buffer_place_cursor(buffer, &iter_);
4385 #define ADD_TXT_POS(bp_, ep_, pti_) \
4386 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4387 last = last->next; \
4388 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4389 last->next = NULL; \
4391 g_warning("alloc error scanning URIs"); \
4394 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4396 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4397 GtkTextBuffer *buffer;
4398 GtkTextIter iter, break_pos, end_of_line;
4399 gchar *quote_str = NULL;
4401 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4402 gboolean prev_autowrap = compose->autowrap;
4403 gint startq_offset = -1, noq_offset = -1;
4404 gint uri_start = -1, uri_stop = -1;
4405 gint nouri_start = -1, nouri_stop = -1;
4406 gint num_blocks = 0;
4407 gint quotelevel = -1;
4408 gboolean modified = force;
4409 gboolean removed = FALSE;
4410 gboolean modified_before_remove = FALSE;
4412 gboolean start = TRUE;
4413 gint itemized_len = 0, rem_item_len = 0;
4414 gchar *itemized_chars = NULL;
4415 gboolean item_continuation = FALSE;
4420 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4424 compose->autowrap = FALSE;
4426 buffer = gtk_text_view_get_buffer(text);
4427 undo_wrapping(compose->undostruct, TRUE);
4432 mark = gtk_text_buffer_get_insert(buffer);
4433 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4437 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4438 if (gtk_text_iter_ends_line(&iter)) {
4439 while (gtk_text_iter_ends_line(&iter) &&
4440 gtk_text_iter_forward_line(&iter))
4443 while (gtk_text_iter_backward_line(&iter)) {
4444 if (gtk_text_iter_ends_line(&iter)) {
4445 gtk_text_iter_forward_line(&iter);
4451 /* move to line start */
4452 gtk_text_iter_set_line_offset(&iter, 0);
4455 itemized_len = compose_itemized_length(buffer, &iter);
4457 if (!itemized_len) {
4458 itemized_len = compose_left_offset_length(buffer, &iter);
4459 item_continuation = TRUE;
4463 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4465 /* go until paragraph end (empty line) */
4466 while (start || !gtk_text_iter_ends_line(&iter)) {
4467 gchar *scanpos = NULL;
4468 /* parse table - in order of priority */
4470 const gchar *needle; /* token */
4472 /* token search function */
4473 gchar *(*search) (const gchar *haystack,
4474 const gchar *needle);
4475 /* part parsing function */
4476 gboolean (*parse) (const gchar *start,
4477 const gchar *scanpos,
4481 /* part to URI function */
4482 gchar *(*build_uri) (const gchar *bp,
4486 static struct table parser[] = {
4487 {"http://", strcasestr, get_uri_part, make_uri_string},
4488 {"https://", strcasestr, get_uri_part, make_uri_string},
4489 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4490 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4491 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4492 {"www.", strcasestr, get_uri_part, make_http_string},
4493 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4494 {"@", strcasestr, get_email_part, make_email_string}
4496 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4497 gint last_index = PARSE_ELEMS;
4499 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4503 if (!prev_autowrap && num_blocks == 0) {
4505 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4506 G_CALLBACK(text_inserted),
4509 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4512 uri_start = uri_stop = -1;
4514 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4517 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4518 if (startq_offset == -1)
4519 startq_offset = gtk_text_iter_get_offset(&iter);
4520 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4521 if (quotelevel > 2) {
4522 /* recycle colors */
4523 if (prefs_common.recycle_quote_colors)
4532 if (startq_offset == -1)
4533 noq_offset = gtk_text_iter_get_offset(&iter);
4537 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4540 if (gtk_text_iter_ends_line(&iter)) {
4542 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4543 prefs_common.linewrap_len,
4545 GtkTextIter prev, next, cur;
4546 if (prev_autowrap != FALSE || force) {
4547 compose->automatic_break = TRUE;
4549 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4550 compose->automatic_break = FALSE;
4551 if (itemized_len && compose->autoindent) {
4552 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4553 if (!item_continuation)
4554 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4556 } else if (quote_str && wrap_quote) {
4557 compose->automatic_break = TRUE;
4559 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4560 compose->automatic_break = FALSE;
4561 if (itemized_len && compose->autoindent) {
4562 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4563 if (!item_continuation)
4564 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4568 /* remove trailing spaces */
4570 rem_item_len = itemized_len;
4571 while (compose->autoindent && rem_item_len-- > 0)
4572 gtk_text_iter_backward_char(&cur);
4573 gtk_text_iter_backward_char(&cur);
4576 while (!gtk_text_iter_starts_line(&cur)) {
4579 gtk_text_iter_backward_char(&cur);
4580 wc = gtk_text_iter_get_char(&cur);
4581 if (!g_unichar_isspace(wc))
4585 if (!gtk_text_iter_equal(&prev, &next)) {
4586 gtk_text_buffer_delete(buffer, &prev, &next);
4588 gtk_text_iter_forward_char(&break_pos);
4592 gtk_text_buffer_insert(buffer, &break_pos,
4596 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4598 /* move iter to current line start */
4599 gtk_text_iter_set_line_offset(&iter, 0);
4606 /* move iter to next line start */
4612 if (!prev_autowrap && num_blocks > 0) {
4614 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4615 G_CALLBACK(text_inserted),
4619 while (!gtk_text_iter_ends_line(&end_of_line)) {
4620 gtk_text_iter_forward_char(&end_of_line);
4622 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4624 nouri_start = gtk_text_iter_get_offset(&iter);
4625 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4627 walk_pos = gtk_text_iter_get_offset(&iter);
4628 /* FIXME: this looks phony. scanning for anything in the parse table */
4629 for (n = 0; n < PARSE_ELEMS; n++) {
4632 tmp = parser[n].search(walk, parser[n].needle);
4634 if (scanpos == NULL || tmp < scanpos) {
4643 /* check if URI can be parsed */
4644 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4645 (const gchar **)&ep, FALSE)
4646 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4650 strlen(parser[last_index].needle);
4653 uri_start = walk_pos + (bp - o_walk);
4654 uri_stop = walk_pos + (ep - o_walk);
4658 gtk_text_iter_forward_line(&iter);
4661 if (startq_offset != -1) {
4662 GtkTextIter startquote, endquote;
4663 gtk_text_buffer_get_iter_at_offset(
4664 buffer, &startquote, startq_offset);
4667 switch (quotelevel) {
4669 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4670 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4671 gtk_text_buffer_apply_tag_by_name(
4672 buffer, "quote0", &startquote, &endquote);
4673 gtk_text_buffer_remove_tag_by_name(
4674 buffer, "quote1", &startquote, &endquote);
4675 gtk_text_buffer_remove_tag_by_name(
4676 buffer, "quote2", &startquote, &endquote);
4681 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4682 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4683 gtk_text_buffer_apply_tag_by_name(
4684 buffer, "quote1", &startquote, &endquote);
4685 gtk_text_buffer_remove_tag_by_name(
4686 buffer, "quote0", &startquote, &endquote);
4687 gtk_text_buffer_remove_tag_by_name(
4688 buffer, "quote2", &startquote, &endquote);
4693 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4694 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4695 gtk_text_buffer_apply_tag_by_name(
4696 buffer, "quote2", &startquote, &endquote);
4697 gtk_text_buffer_remove_tag_by_name(
4698 buffer, "quote0", &startquote, &endquote);
4699 gtk_text_buffer_remove_tag_by_name(
4700 buffer, "quote1", &startquote, &endquote);
4706 } else if (noq_offset != -1) {
4707 GtkTextIter startnoquote, endnoquote;
4708 gtk_text_buffer_get_iter_at_offset(
4709 buffer, &startnoquote, noq_offset);
4712 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4713 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4714 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4715 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4716 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4717 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4718 gtk_text_buffer_remove_tag_by_name(
4719 buffer, "quote0", &startnoquote, &endnoquote);
4720 gtk_text_buffer_remove_tag_by_name(
4721 buffer, "quote1", &startnoquote, &endnoquote);
4722 gtk_text_buffer_remove_tag_by_name(
4723 buffer, "quote2", &startnoquote, &endnoquote);
4729 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4730 GtkTextIter nouri_start_iter, nouri_end_iter;
4731 gtk_text_buffer_get_iter_at_offset(
4732 buffer, &nouri_start_iter, nouri_start);
4733 gtk_text_buffer_get_iter_at_offset(
4734 buffer, &nouri_end_iter, nouri_stop);
4735 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4736 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4737 gtk_text_buffer_remove_tag_by_name(
4738 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4739 modified_before_remove = modified;
4744 if (uri_start >= 0 && uri_stop > 0) {
4745 GtkTextIter uri_start_iter, uri_end_iter, back;
4746 gtk_text_buffer_get_iter_at_offset(
4747 buffer, &uri_start_iter, uri_start);
4748 gtk_text_buffer_get_iter_at_offset(
4749 buffer, &uri_end_iter, uri_stop);
4750 back = uri_end_iter;
4751 gtk_text_iter_backward_char(&back);
4752 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4753 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4754 gtk_text_buffer_apply_tag_by_name(
4755 buffer, "link", &uri_start_iter, &uri_end_iter);
4757 if (removed && !modified_before_remove) {
4763 /* debug_print("not modified, out after %d lines\n", lines); */
4767 /* debug_print("modified, out after %d lines\n", lines); */
4769 g_free(itemized_chars);
4772 undo_wrapping(compose->undostruct, FALSE);
4773 compose->autowrap = prev_autowrap;
4778 void compose_action_cb(void *data)
4780 Compose *compose = (Compose *)data;
4781 compose_wrap_all(compose);
4784 static void compose_wrap_all(Compose *compose)
4786 compose_wrap_all_full(compose, FALSE);
4789 static void compose_wrap_all_full(Compose *compose, gboolean force)
4791 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4792 GtkTextBuffer *buffer;
4794 gboolean modified = TRUE;
4796 buffer = gtk_text_view_get_buffer(text);
4798 gtk_text_buffer_get_start_iter(buffer, &iter);
4800 undo_wrapping(compose->undostruct, TRUE);
4802 while (!gtk_text_iter_is_end(&iter) && modified)
4803 modified = compose_beautify_paragraph(compose, &iter, force);
4805 undo_wrapping(compose->undostruct, FALSE);
4809 static void compose_set_title(Compose *compose)
4815 edited = compose->modified ? _(" [Edited]") : "";
4817 subject = gtk_editable_get_chars(
4818 GTK_EDITABLE(compose->subject_entry), 0, -1);
4820 #ifndef GENERIC_UMPC
4821 if (subject && strlen(subject))
4822 str = g_strdup_printf(_("%s - Compose message%s"),
4825 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4827 str = g_strdup(_("Compose message"));
4830 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4836 * compose_current_mail_account:
4838 * Find a current mail account (the currently selected account, or the
4839 * default account, if a news account is currently selected). If a
4840 * mail account cannot be found, display an error message.
4842 * Return value: Mail account, or NULL if not found.
4844 static PrefsAccount *
4845 compose_current_mail_account(void)
4849 if (cur_account && cur_account->protocol != A_NNTP)
4852 ac = account_get_default();
4853 if (!ac || ac->protocol == A_NNTP) {
4854 alertpanel_error(_("Account for sending mail is not specified.\n"
4855 "Please select a mail account before sending."));
4862 #define QUOTE_IF_REQUIRED(out, str) \
4864 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4868 len = strlen(str) + 3; \
4869 if ((__tmp = alloca(len)) == NULL) { \
4870 g_warning("can't allocate memory"); \
4871 g_string_free(header, TRUE); \
4874 g_snprintf(__tmp, len, "\"%s\"", str); \
4879 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4880 g_warning("can't allocate memory"); \
4881 g_string_free(header, TRUE); \
4884 strcpy(__tmp, str); \
4890 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4892 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4896 len = strlen(str) + 3; \
4897 if ((__tmp = alloca(len)) == NULL) { \
4898 g_warning("can't allocate memory"); \
4901 g_snprintf(__tmp, len, "\"%s\"", str); \
4906 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4907 g_warning("can't allocate memory"); \
4910 strcpy(__tmp, str); \
4916 static void compose_select_account(Compose *compose, PrefsAccount *account,
4919 gchar *from = NULL, *header = NULL;
4920 ComposeHeaderEntry *header_entry;
4923 cm_return_if_fail(account != NULL);
4925 compose->account = account;
4926 if (account->name && *account->name) {
4928 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4929 qbuf = escape_internal_quotes(buf, '"');
4930 from = g_strdup_printf("%s <%s>",
4931 qbuf, account->address);
4934 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4936 from = g_strdup_printf("<%s>",
4938 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4943 compose_set_title(compose);
4945 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4946 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4948 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4949 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4950 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4952 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4954 activate_privacy_system(compose, account, FALSE);
4956 if (!init && compose->mode != COMPOSE_REDIRECT) {
4957 undo_block(compose->undostruct);
4958 compose_insert_sig(compose, TRUE);
4959 undo_unblock(compose->undostruct);
4962 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4963 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4964 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4965 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4967 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4968 if (account->protocol == A_NNTP) {
4969 if (!strcmp(header, _("To:")))
4970 combobox_select_by_text(
4971 GTK_COMBO_BOX(header_entry->combo),
4974 if (!strcmp(header, _("Newsgroups:")))
4975 combobox_select_by_text(
4976 GTK_COMBO_BOX(header_entry->combo),
4984 /* use account's dict info if set */
4985 if (compose->gtkaspell) {
4986 if (account->enable_default_dictionary)
4987 gtkaspell_change_dict(compose->gtkaspell,
4988 account->default_dictionary, FALSE);
4989 if (account->enable_default_alt_dictionary)
4990 gtkaspell_change_alt_dict(compose->gtkaspell,
4991 account->default_alt_dictionary);
4992 if (account->enable_default_dictionary
4993 || account->enable_default_alt_dictionary)
4994 compose_spell_menu_changed(compose);
4999 gboolean compose_check_for_valid_recipient(Compose *compose) {
5000 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5001 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5002 gboolean recipient_found = FALSE;
5006 /* free to and newsgroup list */
5007 slist_free_strings_full(compose->to_list);
5008 compose->to_list = NULL;
5010 slist_free_strings_full(compose->newsgroup_list);
5011 compose->newsgroup_list = NULL;
5013 /* search header entries for to and newsgroup entries */
5014 for (list = compose->header_list; list; list = list->next) {
5017 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5018 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5021 if (entry[0] != '\0') {
5022 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5023 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5024 compose->to_list = address_list_append(compose->to_list, entry);
5025 recipient_found = TRUE;
5028 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5029 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5030 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5031 recipient_found = TRUE;
5038 return recipient_found;
5041 static gboolean compose_check_for_set_recipients(Compose *compose)
5043 if (compose->account->set_autocc && compose->account->auto_cc) {
5044 gboolean found_other = FALSE;
5046 /* search header entries for to and newsgroup entries */
5047 for (list = compose->header_list; list; list = list->next) {
5050 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5051 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5054 if (strcmp(entry, compose->account->auto_cc)
5055 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5066 if (compose->batch) {
5067 gtk_widget_show_all(compose->window);
5069 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5070 prefs_common_translated_header_name("Cc"));
5071 aval = alertpanel(_("Send"),
5073 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5075 if (aval != G_ALERTALTERNATE)
5079 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5080 gboolean found_other = FALSE;
5082 /* search header entries for to and newsgroup entries */
5083 for (list = compose->header_list; list; list = list->next) {
5086 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5087 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5090 if (strcmp(entry, compose->account->auto_bcc)
5091 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5103 if (compose->batch) {
5104 gtk_widget_show_all(compose->window);
5106 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5107 prefs_common_translated_header_name("Bcc"));
5108 aval = alertpanel(_("Send"),
5110 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5112 if (aval != G_ALERTALTERNATE)
5119 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5123 if (compose_check_for_valid_recipient(compose) == FALSE) {
5124 if (compose->batch) {
5125 gtk_widget_show_all(compose->window);
5127 alertpanel_error(_("Recipient is not specified."));
5131 if (compose_check_for_set_recipients(compose) == FALSE) {
5135 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5136 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5137 if (*str == '\0' && check_everything == TRUE &&
5138 compose->mode != COMPOSE_REDIRECT) {
5142 message = g_strdup_printf(_("Subject is empty. %s"),
5143 compose->sending?_("Send it anyway?"):
5144 _("Queue it anyway?"));
5146 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5147 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5148 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5150 if (aval & G_ALERTDISABLE) {
5151 aval &= ~G_ALERTDISABLE;
5152 prefs_common.warn_empty_subj = FALSE;
5154 if (aval != G_ALERTALTERNATE)
5159 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5160 && check_everything == TRUE) {
5164 /* count To and Cc recipients */
5165 for (list = compose->header_list; list; list = list->next) {
5169 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5170 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5173 if ((entry[0] != '\0') &&
5174 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5175 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5181 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5185 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5186 compose->sending?_("Send it anyway?"):
5187 _("Queue it anyway?"));
5189 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5190 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5191 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5193 if (aval & G_ALERTDISABLE) {
5194 aval &= ~G_ALERTDISABLE;
5195 prefs_common.warn_sending_many_recipients_num = 0;
5197 if (aval != G_ALERTALTERNATE)
5202 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5208 static void _display_queue_error(ComposeQueueResult val)
5211 case COMPOSE_QUEUE_SUCCESS:
5213 case COMPOSE_QUEUE_ERROR_NO_MSG:
5214 alertpanel_error(_("Could not queue message."));
5216 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5217 alertpanel_error(_("Could not queue message:\n\n%s."),
5220 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5221 alertpanel_error(_("Could not queue message for sending:\n\n"
5222 "Signature failed: %s"),
5223 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5225 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5226 alertpanel_error(_("Could not queue message for sending:\n\n"
5227 "Encryption failed: %s"),
5228 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5230 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5231 alertpanel_error(_("Could not queue message for sending:\n\n"
5232 "Charset conversion failed."));
5234 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5235 alertpanel_error(_("Could not queue message for sending:\n\n"
5236 "Couldn't get recipient encryption key."));
5239 /* unhandled error */
5240 debug_print("oops, unhandled compose_queue() return value %d\n",
5246 gint compose_send(Compose *compose)
5249 FolderItem *folder = NULL;
5250 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5251 gchar *msgpath = NULL;
5252 gboolean discard_window = FALSE;
5253 gchar *errstr = NULL;
5254 gchar *tmsgid = NULL;
5255 MainWindow *mainwin = mainwindow_get_mainwindow();
5256 gboolean queued_removed = FALSE;
5258 if (prefs_common.send_dialog_invisible
5259 || compose->batch == TRUE)
5260 discard_window = TRUE;
5262 compose_allow_user_actions (compose, FALSE);
5263 compose->sending = TRUE;
5265 if (compose_check_entries(compose, TRUE) == FALSE) {
5266 if (compose->batch) {
5267 gtk_widget_show_all(compose->window);
5273 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5275 if (val != COMPOSE_QUEUE_SUCCESS) {
5276 if (compose->batch) {
5277 gtk_widget_show_all(compose->window);
5280 _display_queue_error(val);
5285 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5286 if (discard_window) {
5287 compose->sending = FALSE;
5288 compose_close(compose);
5289 /* No more compose access in the normal codepath
5290 * after this point! */
5295 alertpanel_error(_("The message was queued but could not be "
5296 "sent.\nUse \"Send queued messages\" from "
5297 "the main window to retry."));
5298 if (!discard_window) {
5305 if (msgpath == NULL) {
5306 msgpath = folder_item_fetch_msg(folder, msgnum);
5307 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5310 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5311 claws_unlink(msgpath);
5314 if (!discard_window) {
5316 if (!queued_removed)
5317 folder_item_remove_msg(folder, msgnum);
5318 folder_item_scan(folder);
5320 /* make sure we delete that */
5321 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5323 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5324 folder_item_remove_msg(folder, tmp->msgnum);
5325 procmsg_msginfo_free(&tmp);
5332 if (!queued_removed)
5333 folder_item_remove_msg(folder, msgnum);
5334 folder_item_scan(folder);
5336 /* make sure we delete that */
5337 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5339 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5340 folder_item_remove_msg(folder, tmp->msgnum);
5341 procmsg_msginfo_free(&tmp);
5344 if (!discard_window) {
5345 compose->sending = FALSE;
5346 compose_allow_user_actions (compose, TRUE);
5347 compose_close(compose);
5351 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5352 "the main window to retry."), errstr);
5355 alertpanel_error_log(_("The message was queued but could not be "
5356 "sent.\nUse \"Send queued messages\" from "
5357 "the main window to retry."));
5359 if (!discard_window) {
5368 toolbar_main_set_sensitive(mainwin);
5369 main_window_set_menu_sensitive(mainwin);
5375 compose_allow_user_actions (compose, TRUE);
5376 compose->sending = FALSE;
5377 compose->modified = TRUE;
5378 toolbar_main_set_sensitive(mainwin);
5379 main_window_set_menu_sensitive(mainwin);
5384 static gboolean compose_use_attach(Compose *compose)
5386 GtkTreeModel *model = gtk_tree_view_get_model
5387 (GTK_TREE_VIEW(compose->attach_clist));
5388 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5391 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5394 gchar buf[BUFFSIZE];
5396 gboolean first_to_address;
5397 gboolean first_cc_address;
5399 ComposeHeaderEntry *headerentry;
5400 const gchar *headerentryname;
5401 const gchar *cc_hdr;
5402 const gchar *to_hdr;
5403 gboolean err = FALSE;
5405 debug_print("Writing redirect header\n");
5407 cc_hdr = prefs_common_translated_header_name("Cc:");
5408 to_hdr = prefs_common_translated_header_name("To:");
5410 first_to_address = TRUE;
5411 for (list = compose->header_list; list; list = list->next) {
5412 headerentry = ((ComposeHeaderEntry *)list->data);
5413 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5415 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5416 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5417 Xstrdup_a(str, entstr, return -1);
5419 if (str[0] != '\0') {
5420 compose_convert_header
5421 (compose, buf, sizeof(buf), str,
5422 strlen("Resent-To") + 2, TRUE);
5424 if (first_to_address) {
5425 err |= (fprintf(fp, "Resent-To: ") < 0);
5426 first_to_address = FALSE;
5428 err |= (fprintf(fp, ",") < 0);
5430 err |= (fprintf(fp, "%s", buf) < 0);
5434 if (!first_to_address) {
5435 err |= (fprintf(fp, "\n") < 0);
5438 first_cc_address = TRUE;
5439 for (list = compose->header_list; list; list = list->next) {
5440 headerentry = ((ComposeHeaderEntry *)list->data);
5441 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5443 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5444 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5445 Xstrdup_a(str, strg, return -1);
5447 if (str[0] != '\0') {
5448 compose_convert_header
5449 (compose, buf, sizeof(buf), str,
5450 strlen("Resent-Cc") + 2, TRUE);
5452 if (first_cc_address) {
5453 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5454 first_cc_address = FALSE;
5456 err |= (fprintf(fp, ",") < 0);
5458 err |= (fprintf(fp, "%s", buf) < 0);
5462 if (!first_cc_address) {
5463 err |= (fprintf(fp, "\n") < 0);
5466 return (err ? -1:0);
5469 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5471 gchar date[RFC822_DATE_BUFFSIZE];
5472 gchar buf[BUFFSIZE];
5474 const gchar *entstr;
5475 /* struct utsname utsbuf; */
5476 gboolean err = FALSE;
5478 cm_return_val_if_fail(fp != NULL, -1);
5479 cm_return_val_if_fail(compose->account != NULL, -1);
5480 cm_return_val_if_fail(compose->account->address != NULL, -1);
5483 if (prefs_common.hide_timezone)
5484 get_rfc822_date_hide_tz(date, sizeof(date));
5486 get_rfc822_date(date, sizeof(date));
5487 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5490 if (compose->account->name && *compose->account->name) {
5491 compose_convert_header
5492 (compose, buf, sizeof(buf), compose->account->name,
5493 strlen("From: "), TRUE);
5494 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5495 buf, compose->account->address) < 0);
5497 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5500 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5501 if (*entstr != '\0') {
5502 Xstrdup_a(str, entstr, return -1);
5505 compose_convert_header(compose, buf, sizeof(buf), str,
5506 strlen("Subject: "), FALSE);
5507 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5511 /* Resent-Message-ID */
5512 if (compose->account->gen_msgid) {
5513 gchar *addr = prefs_account_generate_msgid(compose->account);
5514 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5516 g_free(compose->msgid);
5517 compose->msgid = addr;
5519 compose->msgid = NULL;
5522 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5525 /* separator between header and body */
5526 err |= (fputs("\n", fp) == EOF);
5528 return (err ? -1:0);
5531 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5536 gchar rewrite_buf[BUFFSIZE];
5538 gboolean skip = FALSE;
5539 gboolean err = FALSE;
5540 gchar *not_included[]={
5541 "Return-Path:", "Delivered-To:", "Received:",
5542 "Subject:", "X-UIDL:", "AF:",
5543 "NF:", "PS:", "SRH:",
5544 "SFN:", "DSR:", "MID:",
5545 "CFG:", "PT:", "S:",
5546 "RQ:", "SSV:", "NSV:",
5547 "SSH:", "R:", "MAID:",
5548 "NAID:", "RMID:", "FMID:",
5549 "SCF:", "RRCPT:", "NG:",
5550 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5551 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5552 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5553 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5554 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5559 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5560 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5564 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5566 for (i = 0; not_included[i] != NULL; i++) {
5567 if (g_ascii_strncasecmp(buf, not_included[i],
5568 strlen(not_included[i])) == 0) {
5578 if (fputs(buf, fdest) == -1) {
5584 if (!prefs_common.redirect_keep_from) {
5585 if (g_ascii_strncasecmp(buf, "From:",
5586 strlen("From:")) == 0) {
5587 err |= (fputs(" (by way of ", fdest) == EOF);
5588 if (compose->account->name
5589 && *compose->account->name) {
5590 gchar buffer[BUFFSIZE];
5592 compose_convert_header
5593 (compose, buffer, sizeof(buffer),
5594 compose->account->name,
5597 err |= (fprintf(fdest, "%s <%s>",
5599 compose->account->address) < 0);
5601 err |= (fprintf(fdest, "%s",
5602 compose->account->address) < 0);
5603 err |= (fputs(")", fdest) == EOF);
5609 if (fputs("\n", fdest) == -1)
5616 if (compose_redirect_write_headers(compose, fdest))
5619 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5620 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5634 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5636 GtkTextBuffer *buffer;
5637 GtkTextIter start, end, tmp;
5638 gchar *chars, *tmp_enc_file, *content;
5640 const gchar *out_codeset;
5641 EncodingType encoding = ENC_UNKNOWN;
5642 MimeInfo *mimemsg, *mimetext;
5644 const gchar *src_codeset = CS_INTERNAL;
5645 gchar *from_addr = NULL;
5646 gchar *from_name = NULL;
5649 if (action == COMPOSE_WRITE_FOR_SEND) {
5650 attach_parts = TRUE;
5652 /* We're sending the message, generate a Message-ID
5654 if (compose->msgid == NULL &&
5655 compose->account->gen_msgid) {
5656 compose->msgid = prefs_account_generate_msgid(compose->account);
5660 /* create message MimeInfo */
5661 mimemsg = procmime_mimeinfo_new();
5662 mimemsg->type = MIMETYPE_MESSAGE;
5663 mimemsg->subtype = g_strdup("rfc822");
5664 mimemsg->content = MIMECONTENT_MEM;
5665 mimemsg->tmp = TRUE; /* must free content later */
5666 mimemsg->data.mem = compose_get_header(compose);
5668 /* Create text part MimeInfo */
5669 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5670 gtk_text_buffer_get_end_iter(buffer, &end);
5673 /* We make sure that there is a newline at the end. */
5674 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5675 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5676 if (*chars != '\n') {
5677 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5682 /* get all composed text */
5683 gtk_text_buffer_get_start_iter(buffer, &start);
5684 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5686 out_codeset = conv_get_charset_str(compose->out_encoding);
5688 if (!out_codeset && is_ascii_str(chars)) {
5689 out_codeset = CS_US_ASCII;
5690 } else if (prefs_common.outgoing_fallback_to_ascii &&
5691 is_ascii_str(chars)) {
5692 out_codeset = CS_US_ASCII;
5693 encoding = ENC_7BIT;
5697 gchar *test_conv_global_out = NULL;
5698 gchar *test_conv_reply = NULL;
5700 /* automatic mode. be automatic. */
5701 codeconv_set_strict(TRUE);
5703 out_codeset = conv_get_outgoing_charset_str();
5705 debug_print("trying to convert to %s\n", out_codeset);
5706 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5709 if (!test_conv_global_out && compose->orig_charset
5710 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5711 out_codeset = compose->orig_charset;
5712 debug_print("failure; trying to convert to %s\n", out_codeset);
5713 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5716 if (!test_conv_global_out && !test_conv_reply) {
5718 out_codeset = CS_INTERNAL;
5719 debug_print("failure; finally using %s\n", out_codeset);
5721 g_free(test_conv_global_out);
5722 g_free(test_conv_reply);
5723 codeconv_set_strict(FALSE);
5726 if (encoding == ENC_UNKNOWN) {
5727 if (prefs_common.encoding_method == CTE_BASE64)
5728 encoding = ENC_BASE64;
5729 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5730 encoding = ENC_QUOTED_PRINTABLE;
5731 else if (prefs_common.encoding_method == CTE_8BIT)
5732 encoding = ENC_8BIT;
5734 encoding = procmime_get_encoding_for_charset(out_codeset);
5737 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5738 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5740 if (action == COMPOSE_WRITE_FOR_SEND) {
5741 codeconv_set_strict(TRUE);
5742 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5743 codeconv_set_strict(FALSE);
5748 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5749 "to the specified %s charset.\n"
5750 "Send it as %s?"), out_codeset, src_codeset);
5751 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5752 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5756 if (aval != G_ALERTALTERNATE) {
5758 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5761 out_codeset = src_codeset;
5767 out_codeset = src_codeset;
5772 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5773 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5774 strstr(buf, "\nFrom ") != NULL) {
5775 encoding = ENC_QUOTED_PRINTABLE;
5779 mimetext = procmime_mimeinfo_new();
5780 mimetext->content = MIMECONTENT_MEM;
5781 mimetext->tmp = TRUE; /* must free content later */
5782 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5783 * and free the data, which we need later. */
5784 mimetext->data.mem = g_strdup(buf);
5785 mimetext->type = MIMETYPE_TEXT;
5786 mimetext->subtype = g_strdup("plain");
5787 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5788 g_strdup(out_codeset));
5790 /* protect trailing spaces when signing message */
5791 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5792 privacy_system_can_sign(compose->privacy_system)) {
5793 encoding = ENC_QUOTED_PRINTABLE;
5797 debug_print("main text: %Id bytes encoded as %s in %d\n",
5799 debug_print("main text: %zd bytes encoded as %s in %d\n",
5801 strlen(buf), out_codeset, encoding);
5803 /* check for line length limit */
5804 if (action == COMPOSE_WRITE_FOR_SEND &&
5805 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5806 check_line_length(buf, 1000, &line) < 0) {
5809 msg = g_strdup_printf
5810 (_("Line %d exceeds the line length limit (998 bytes).\n"
5811 "The contents of the message might be broken on the way to the delivery.\n"
5813 "Send it anyway?"), line + 1);
5814 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5817 if (aval != G_ALERTALTERNATE) {
5819 return COMPOSE_QUEUE_ERROR_NO_MSG;
5823 if (encoding != ENC_UNKNOWN)
5824 procmime_encode_content(mimetext, encoding);
5826 /* append attachment parts */
5827 if (compose_use_attach(compose) && attach_parts) {
5828 MimeInfo *mimempart;
5829 gchar *boundary = NULL;
5830 mimempart = procmime_mimeinfo_new();
5831 mimempart->content = MIMECONTENT_EMPTY;
5832 mimempart->type = MIMETYPE_MULTIPART;
5833 mimempart->subtype = g_strdup("mixed");
5837 boundary = generate_mime_boundary(NULL);
5838 } while (strstr(buf, boundary) != NULL);
5840 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5843 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5845 g_node_append(mimempart->node, mimetext->node);
5846 g_node_append(mimemsg->node, mimempart->node);
5848 if (compose_add_attachments(compose, mimempart) < 0)
5849 return COMPOSE_QUEUE_ERROR_NO_MSG;
5851 g_node_append(mimemsg->node, mimetext->node);
5855 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5856 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5857 /* extract name and address */
5858 if (strstr(spec, " <") && strstr(spec, ">")) {
5859 from_addr = g_strdup(strrchr(spec, '<')+1);
5860 *(strrchr(from_addr, '>')) = '\0';
5861 from_name = g_strdup(spec);
5862 *(strrchr(from_name, '<')) = '\0';
5869 /* sign message if sending */
5870 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5871 privacy_system_can_sign(compose->privacy_system))
5872 if (!privacy_sign(compose->privacy_system, mimemsg,
5873 compose->account, from_addr)) {
5876 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5881 if (compose->use_encryption) {
5882 if (compose->encdata != NULL &&
5883 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5885 /* First, write an unencrypted copy and save it to outbox, if
5886 * user wants that. */
5887 if (compose->account->save_encrypted_as_clear_text) {
5888 debug_print("saving sent message unencrypted...\n");
5889 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5893 /* fp now points to a file with headers written,
5894 * let's make a copy. */
5896 content = file_read_stream_to_str(fp);
5898 str_write_to_file(content, tmp_enc_file);
5901 /* Now write the unencrypted body. */
5902 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5903 procmime_write_mimeinfo(mimemsg, tmpfp);
5906 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5908 outbox = folder_get_default_outbox();
5910 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5911 claws_unlink(tmp_enc_file);
5913 g_warning("Can't open file '%s'", tmp_enc_file);
5916 g_warning("couldn't get tempfile");
5919 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5920 debug_print("Couldn't encrypt mime structure: %s.\n",
5921 privacy_get_error());
5922 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5927 procmime_write_mimeinfo(mimemsg, fp);
5929 procmime_mimeinfo_free_all(&mimemsg);
5934 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5936 GtkTextBuffer *buffer;
5937 GtkTextIter start, end;
5942 if ((fp = g_fopen(file, "wb")) == NULL) {
5943 FILE_OP_ERROR(file, "fopen");
5947 /* chmod for security */
5948 if (change_file_mode_rw(fp, file) < 0) {
5949 FILE_OP_ERROR(file, "chmod");
5950 g_warning("can't change file mode");
5953 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5954 gtk_text_buffer_get_start_iter(buffer, &start);
5955 gtk_text_buffer_get_end_iter(buffer, &end);
5956 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5958 chars = conv_codeset_strdup
5959 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5968 len = strlen(chars);
5969 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5970 FILE_OP_ERROR(file, "fwrite");
5979 if (fclose(fp) == EOF) {
5980 FILE_OP_ERROR(file, "fclose");
5987 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5990 MsgInfo *msginfo = compose->targetinfo;
5992 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5993 if (!msginfo) return -1;
5995 if (!force && MSG_IS_LOCKED(msginfo->flags))
5998 item = msginfo->folder;
5999 cm_return_val_if_fail(item != NULL, -1);
6001 if (procmsg_msg_exist(msginfo) &&
6002 (folder_has_parent_of_type(item, F_QUEUE) ||
6003 folder_has_parent_of_type(item, F_DRAFT)
6004 || msginfo == compose->autosaved_draft)) {
6005 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6006 g_warning("can't remove the old message");
6009 debug_print("removed reedit target %d\n", msginfo->msgnum);
6016 static void compose_remove_draft(Compose *compose)
6019 MsgInfo *msginfo = compose->targetinfo;
6020 drafts = account_get_special_folder(compose->account, F_DRAFT);
6022 if (procmsg_msg_exist(msginfo)) {
6023 folder_item_remove_msg(drafts, msginfo->msgnum);
6028 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6029 gboolean remove_reedit_target)
6031 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6034 static gboolean compose_warn_encryption(Compose *compose)
6036 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6037 AlertValue val = G_ALERTALTERNATE;
6039 if (warning == NULL)
6042 val = alertpanel_full(_("Encryption warning"), warning,
6043 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6044 TRUE, NULL, ALERT_WARNING);
6045 if (val & G_ALERTDISABLE) {
6046 val &= ~G_ALERTDISABLE;
6047 if (val == G_ALERTALTERNATE)
6048 privacy_inhibit_encrypt_warning(compose->privacy_system,
6052 if (val == G_ALERTALTERNATE) {
6059 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6060 gchar **msgpath, gboolean perform_checks,
6061 gboolean remove_reedit_target)
6068 PrefsAccount *mailac = NULL, *newsac = NULL;
6069 gboolean err = FALSE;
6071 debug_print("queueing message...\n");
6072 cm_return_val_if_fail(compose->account != NULL, -1);
6074 if (compose_check_entries(compose, perform_checks) == FALSE) {
6075 if (compose->batch) {
6076 gtk_widget_show_all(compose->window);
6078 return COMPOSE_QUEUE_ERROR_NO_MSG;
6081 if (!compose->to_list && !compose->newsgroup_list) {
6082 g_warning("can't get recipient list.");
6083 return COMPOSE_QUEUE_ERROR_NO_MSG;
6086 if (compose->to_list) {
6087 if (compose->account->protocol != A_NNTP)
6088 mailac = compose->account;
6089 else if (cur_account && cur_account->protocol != A_NNTP)
6090 mailac = cur_account;
6091 else if (!(mailac = compose_current_mail_account())) {
6092 alertpanel_error(_("No account for sending mails available!"));
6093 return COMPOSE_QUEUE_ERROR_NO_MSG;
6097 if (compose->newsgroup_list) {
6098 if (compose->account->protocol == A_NNTP)
6099 newsac = compose->account;
6101 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6102 return COMPOSE_QUEUE_ERROR_NO_MSG;
6106 /* write queue header */
6107 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6108 G_DIR_SEPARATOR, compose, (guint) rand());
6109 debug_print("queuing to %s\n", tmp);
6110 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6111 FILE_OP_ERROR(tmp, "fopen");
6113 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6116 if (change_file_mode_rw(fp, tmp) < 0) {
6117 FILE_OP_ERROR(tmp, "chmod");
6118 g_warning("can't change file mode");
6121 /* queueing variables */
6122 err |= (fprintf(fp, "AF:\n") < 0);
6123 err |= (fprintf(fp, "NF:0\n") < 0);
6124 err |= (fprintf(fp, "PS:10\n") < 0);
6125 err |= (fprintf(fp, "SRH:1\n") < 0);
6126 err |= (fprintf(fp, "SFN:\n") < 0);
6127 err |= (fprintf(fp, "DSR:\n") < 0);
6129 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6131 err |= (fprintf(fp, "MID:\n") < 0);
6132 err |= (fprintf(fp, "CFG:\n") < 0);
6133 err |= (fprintf(fp, "PT:0\n") < 0);
6134 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6135 err |= (fprintf(fp, "RQ:\n") < 0);
6137 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6139 err |= (fprintf(fp, "SSV:\n") < 0);
6141 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6143 err |= (fprintf(fp, "NSV:\n") < 0);
6144 err |= (fprintf(fp, "SSH:\n") < 0);
6145 /* write recipient list */
6146 if (compose->to_list) {
6147 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6148 for (cur = compose->to_list->next; cur != NULL;
6150 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6151 err |= (fprintf(fp, "\n") < 0);
6153 /* write newsgroup list */
6154 if (compose->newsgroup_list) {
6155 err |= (fprintf(fp, "NG:") < 0);
6156 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6157 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6158 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6159 err |= (fprintf(fp, "\n") < 0);
6163 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6165 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6168 if (compose->privacy_system != NULL) {
6169 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6170 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6171 if (compose->use_encryption) {
6172 if (!compose_warn_encryption(compose)) {
6176 return COMPOSE_QUEUE_ERROR_NO_MSG;
6178 if (mailac && mailac->encrypt_to_self) {
6179 GSList *tmp_list = g_slist_copy(compose->to_list);
6180 tmp_list = g_slist_append(tmp_list, compose->account->address);
6181 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6182 g_slist_free(tmp_list);
6184 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6186 if (compose->encdata != NULL) {
6187 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6188 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6189 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6190 compose->encdata) < 0);
6191 } /* else we finally dont want to encrypt */
6193 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6194 /* and if encdata was null, it means there's been a problem in
6197 g_warning("failed to write queue message");
6201 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6206 /* Save copy folder */
6207 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6208 gchar *savefolderid;
6210 savefolderid = compose_get_save_to(compose);
6211 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6212 g_free(savefolderid);
6214 /* Save copy folder */
6215 if (compose->return_receipt) {
6216 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6218 /* Message-ID of message replying to */
6219 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6220 gchar *folderid = NULL;
6222 if (compose->replyinfo->folder)
6223 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6224 if (folderid == NULL)
6225 folderid = g_strdup("NULL");
6227 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6230 /* Message-ID of message forwarding to */
6231 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6232 gchar *folderid = NULL;
6234 if (compose->fwdinfo->folder)
6235 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6236 if (folderid == NULL)
6237 folderid = g_strdup("NULL");
6239 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6243 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6244 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6246 /* end of headers */
6247 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6249 if (compose->redirect_filename != NULL) {
6250 if (compose_redirect_write_to_file(compose, fp) < 0) {
6254 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6258 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6266 g_warning("failed to write queue message");
6270 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6272 if (fclose(fp) == EOF) {
6273 FILE_OP_ERROR(tmp, "fclose");
6276 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6279 if (item && *item) {
6282 queue = account_get_special_folder(compose->account, F_QUEUE);
6285 g_warning("can't find queue folder");
6288 return COMPOSE_QUEUE_ERROR_NO_MSG;
6290 folder_item_scan(queue);
6291 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6292 g_warning("can't queue the message");
6295 return COMPOSE_QUEUE_ERROR_NO_MSG;
6298 if (msgpath == NULL) {
6304 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6305 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6307 procmsg_msginfo_change_flags(mi,
6308 compose->targetinfo->flags.perm_flags,
6309 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6312 g_slist_free(mi->tags);
6313 mi->tags = g_slist_copy(compose->targetinfo->tags);
6314 procmsg_msginfo_free(&mi);
6318 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6319 compose_remove_reedit_target(compose, FALSE);
6322 if ((msgnum != NULL) && (item != NULL)) {
6327 return COMPOSE_QUEUE_SUCCESS;
6330 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6333 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6338 GError *error = NULL;
6343 gchar *type, *subtype;
6344 GtkTreeModel *model;
6347 model = gtk_tree_view_get_model(tree_view);
6349 if (!gtk_tree_model_get_iter_first(model, &iter))
6352 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6354 if (!is_file_exist(ainfo->file)) {
6355 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6356 AlertValue val = alertpanel_full(_("Warning"), msg,
6357 _("Cancel sending"), _("Ignore attachment"), NULL,
6358 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6360 if (val == G_ALERTDEFAULT) {
6366 f = g_file_new_for_path(ainfo->file);
6367 fi = g_file_query_info(f, "standard::size",
6368 G_FILE_QUERY_INFO_NONE, NULL, &error);
6369 if (error != NULL) {
6370 g_warning(error->message);
6371 g_error_free(error);
6375 size = g_file_info_get_size(fi);
6379 if (g_stat(ainfo->file, &statbuf) < 0)
6381 size = statbuf.st_size;
6384 mimepart = procmime_mimeinfo_new();
6385 mimepart->content = MIMECONTENT_FILE;
6386 mimepart->data.filename = g_strdup(ainfo->file);
6387 mimepart->tmp = FALSE; /* or we destroy our attachment */
6388 mimepart->offset = 0;
6389 mimepart->length = size;
6391 type = g_strdup(ainfo->content_type);
6393 if (!strchr(type, '/')) {
6395 type = g_strdup("application/octet-stream");
6398 subtype = strchr(type, '/') + 1;
6399 *(subtype - 1) = '\0';
6400 mimepart->type = procmime_get_media_type(type);
6401 mimepart->subtype = g_strdup(subtype);
6404 if (mimepart->type == MIMETYPE_MESSAGE &&
6405 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6406 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6407 } else if (mimepart->type == MIMETYPE_TEXT) {
6408 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6409 /* Text parts with no name come from multipart/alternative
6410 * forwards. Make sure the recipient won't look at the
6411 * original HTML part by mistake. */
6412 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6413 ainfo->name = g_strdup_printf(_("Original %s part"),
6417 g_hash_table_insert(mimepart->typeparameters,
6418 g_strdup("charset"), g_strdup(ainfo->charset));
6420 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6421 if (mimepart->type == MIMETYPE_APPLICATION &&
6422 !strcmp2(mimepart->subtype, "octet-stream"))
6423 g_hash_table_insert(mimepart->typeparameters,
6424 g_strdup("name"), g_strdup(ainfo->name));
6425 g_hash_table_insert(mimepart->dispositionparameters,
6426 g_strdup("filename"), g_strdup(ainfo->name));
6427 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6430 if (mimepart->type == MIMETYPE_MESSAGE
6431 || mimepart->type == MIMETYPE_MULTIPART)
6432 ainfo->encoding = ENC_BINARY;
6433 else if (compose->use_signing) {
6434 if (ainfo->encoding == ENC_7BIT)
6435 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6436 else if (ainfo->encoding == ENC_8BIT)
6437 ainfo->encoding = ENC_BASE64;
6440 procmime_encode_content(mimepart, ainfo->encoding);
6442 g_node_append(parent->node, mimepart->node);
6443 } while (gtk_tree_model_iter_next(model, &iter));
6448 static gchar *compose_quote_list_of_addresses(gchar *str)
6450 GSList *list = NULL, *item = NULL;
6451 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6453 list = address_list_append_with_comments(list, str);
6454 for (item = list; item != NULL; item = item->next) {
6455 gchar *spec = item->data;
6456 gchar *endofname = strstr(spec, " <");
6457 if (endofname != NULL) {
6460 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6461 qqname = escape_internal_quotes(qname, '"');
6463 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6464 gchar *addr = g_strdup(endofname);
6465 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6466 faddr = g_strconcat(name, addr, NULL);
6469 debug_print("new auto-quoted address: '%s'\n", faddr);
6473 result = g_strdup((faddr != NULL)? faddr: spec);
6475 result = g_strconcat(result,
6477 (faddr != NULL)? faddr: spec,
6480 if (faddr != NULL) {
6485 slist_free_strings_full(list);
6490 #define IS_IN_CUSTOM_HEADER(header) \
6491 (compose->account->add_customhdr && \
6492 custom_header_find(compose->account->customhdr_list, header) != NULL)
6494 static const gchar *compose_untranslated_header_name(gchar *header_name)
6496 /* return the untranslated header name, if header_name is a known
6497 header name, in either its translated or untranslated form, with
6498 or without trailing colon. otherwise, returns header_name. */
6499 gchar *translated_header_name;
6500 gchar *translated_header_name_wcolon;
6501 const gchar *untranslated_header_name;
6502 const gchar *untranslated_header_name_wcolon;
6505 cm_return_val_if_fail(header_name != NULL, NULL);
6507 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6508 untranslated_header_name = HEADERS[i].header_name;
6509 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6511 translated_header_name = gettext(untranslated_header_name);
6512 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6514 if (!strcmp(header_name, untranslated_header_name) ||
6515 !strcmp(header_name, translated_header_name)) {
6516 return untranslated_header_name;
6518 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6519 !strcmp(header_name, translated_header_name_wcolon)) {
6520 return untranslated_header_name_wcolon;
6524 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6528 static void compose_add_headerfield_from_headerlist(Compose *compose,
6530 const gchar *fieldname,
6531 const gchar *seperator)
6533 gchar *str, *fieldname_w_colon;
6534 gboolean add_field = FALSE;
6536 ComposeHeaderEntry *headerentry;
6537 const gchar *headerentryname;
6538 const gchar *trans_fieldname;
6541 if (IS_IN_CUSTOM_HEADER(fieldname))
6544 debug_print("Adding %s-fields\n", fieldname);
6546 fieldstr = g_string_sized_new(64);
6548 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6549 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6551 for (list = compose->header_list; list; list = list->next) {
6552 headerentry = ((ComposeHeaderEntry *)list->data);
6553 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6555 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6556 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6558 str = compose_quote_list_of_addresses(ustr);
6560 if (str != NULL && str[0] != '\0') {
6562 g_string_append(fieldstr, seperator);
6563 g_string_append(fieldstr, str);
6572 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6573 compose_convert_header
6574 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6575 strlen(fieldname) + 2, TRUE);
6576 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6580 g_free(fieldname_w_colon);
6581 g_string_free(fieldstr, TRUE);
6586 static gchar *compose_get_manual_headers_info(Compose *compose)
6588 GString *sh_header = g_string_new(" ");
6590 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6592 for (list = compose->header_list; list; list = list->next) {
6593 ComposeHeaderEntry *headerentry;
6596 gchar *headername_wcolon;
6597 const gchar *headername_trans;
6599 gboolean standard_header = FALSE;
6601 headerentry = ((ComposeHeaderEntry *)list->data);
6603 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6605 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6610 if (!strstr(tmp, ":")) {
6611 headername_wcolon = g_strconcat(tmp, ":", NULL);
6612 headername = g_strdup(tmp);
6614 headername_wcolon = g_strdup(tmp);
6615 headername = g_strdup(strtok(tmp, ":"));
6619 string = std_headers;
6620 while (*string != NULL) {
6621 headername_trans = prefs_common_translated_header_name(*string);
6622 if (!strcmp(headername_trans, headername_wcolon))
6623 standard_header = TRUE;
6626 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6627 g_string_append_printf(sh_header, "%s ", headername);
6629 g_free(headername_wcolon);
6631 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6632 return g_string_free(sh_header, FALSE);
6635 static gchar *compose_get_header(Compose *compose)
6637 gchar date[RFC822_DATE_BUFFSIZE];
6638 gchar buf[BUFFSIZE];
6639 const gchar *entry_str;
6643 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6645 gchar *from_name = NULL, *from_address = NULL;
6648 cm_return_val_if_fail(compose->account != NULL, NULL);
6649 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6651 header = g_string_sized_new(64);
6654 if (prefs_common.hide_timezone)
6655 get_rfc822_date_hide_tz(date, sizeof(date));
6657 get_rfc822_date(date, sizeof(date));
6658 g_string_append_printf(header, "Date: %s\n", date);
6662 if (compose->account->name && *compose->account->name) {
6664 QUOTE_IF_REQUIRED(buf, compose->account->name);
6665 tmp = g_strdup_printf("%s <%s>",
6666 buf, compose->account->address);
6668 tmp = g_strdup_printf("%s",
6669 compose->account->address);
6671 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6672 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6674 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6675 from_address = g_strdup(compose->account->address);
6677 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6678 /* extract name and address */
6679 if (strstr(spec, " <") && strstr(spec, ">")) {
6680 from_address = g_strdup(strrchr(spec, '<')+1);
6681 *(strrchr(from_address, '>')) = '\0';
6682 from_name = g_strdup(spec);
6683 *(strrchr(from_name, '<')) = '\0';
6686 from_address = g_strdup(spec);
6693 if (from_name && *from_name) {
6695 compose_convert_header
6696 (compose, buf, sizeof(buf), from_name,
6697 strlen("From: "), TRUE);
6698 QUOTE_IF_REQUIRED(name, buf);
6699 qname = escape_internal_quotes(name, '"');
6701 g_string_append_printf(header, "From: %s <%s>\n",
6702 qname, from_address);
6703 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6704 compose->return_receipt) {
6705 compose_convert_header(compose, buf, sizeof(buf), from_name,
6706 strlen("Disposition-Notification-To: "),
6708 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6713 g_string_append_printf(header, "From: %s\n", from_address);
6714 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6715 compose->return_receipt)
6716 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6720 g_free(from_address);
6723 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6726 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6729 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6733 * If this account is a NNTP account remove Bcc header from
6734 * message body since it otherwise will be publicly shown
6736 if (compose->account->protocol != A_NNTP)
6737 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6740 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6742 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6745 compose_convert_header(compose, buf, sizeof(buf), str,
6746 strlen("Subject: "), FALSE);
6747 g_string_append_printf(header, "Subject: %s\n", buf);
6753 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6754 g_string_append_printf(header, "Message-ID: <%s>\n",
6758 if (compose->remove_references == FALSE) {
6760 if (compose->inreplyto && compose->to_list)
6761 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6764 if (compose->references)
6765 g_string_append_printf(header, "References: %s\n", compose->references);
6769 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6772 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6775 if (compose->account->organization &&
6776 strlen(compose->account->organization) &&
6777 !IS_IN_CUSTOM_HEADER("Organization")) {
6778 compose_convert_header(compose, buf, sizeof(buf),
6779 compose->account->organization,
6780 strlen("Organization: "), FALSE);
6781 g_string_append_printf(header, "Organization: %s\n", buf);
6784 /* Program version and system info */
6785 if (compose->account->gen_xmailer &&
6786 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6787 !compose->newsgroup_list) {
6788 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6790 gtk_major_version, gtk_minor_version, gtk_micro_version,
6793 if (compose->account->gen_xmailer &&
6794 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6795 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6797 gtk_major_version, gtk_minor_version, gtk_micro_version,
6801 /* custom headers */
6802 if (compose->account->add_customhdr) {
6805 for (cur = compose->account->customhdr_list; cur != NULL;
6807 CustomHeader *chdr = (CustomHeader *)cur->data;
6809 if (custom_header_is_allowed(chdr->name)
6810 && chdr->value != NULL
6811 && *(chdr->value) != '\0') {
6812 compose_convert_header
6813 (compose, buf, sizeof(buf),
6815 strlen(chdr->name) + 2, FALSE);
6816 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6821 /* Automatic Faces and X-Faces */
6822 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6823 g_string_append_printf(header, "X-Face: %s\n", buf);
6825 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6826 g_string_append_printf(header, "X-Face: %s\n", buf);
6828 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6829 g_string_append_printf(header, "Face: %s\n", buf);
6831 else if (get_default_face (buf, sizeof(buf)) == 0) {
6832 g_string_append_printf(header, "Face: %s\n", buf);
6836 switch (compose->priority) {
6837 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6838 "X-Priority: 1 (Highest)\n");
6840 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6841 "X-Priority: 2 (High)\n");
6843 case PRIORITY_NORMAL: break;
6844 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6845 "X-Priority: 4 (Low)\n");
6847 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6848 "X-Priority: 5 (Lowest)\n");
6850 default: debug_print("compose: priority unknown : %d\n",
6854 /* get special headers */
6855 for (list = compose->header_list; list; list = list->next) {
6856 ComposeHeaderEntry *headerentry;
6859 gchar *headername_wcolon;
6860 const gchar *headername_trans;
6863 gboolean standard_header = FALSE;
6865 headerentry = ((ComposeHeaderEntry *)list->data);
6867 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6869 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6874 if (!strstr(tmp, ":")) {
6875 headername_wcolon = g_strconcat(tmp, ":", NULL);
6876 headername = g_strdup(tmp);
6878 headername_wcolon = g_strdup(tmp);
6879 headername = g_strdup(strtok(tmp, ":"));
6883 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6884 Xstrdup_a(headervalue, entry_str, return NULL);
6885 subst_char(headervalue, '\r', ' ');
6886 subst_char(headervalue, '\n', ' ');
6887 g_strstrip(headervalue);
6888 if (*headervalue != '\0') {
6889 string = std_headers;
6890 while (*string != NULL && !standard_header) {
6891 headername_trans = prefs_common_translated_header_name(*string);
6892 /* support mixed translated and untranslated headers */
6893 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6894 standard_header = TRUE;
6897 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6898 /* store untranslated header name */
6899 g_string_append_printf(header, "%s %s\n",
6900 compose_untranslated_header_name(headername_wcolon), headervalue);
6904 g_free(headername_wcolon);
6908 g_string_free(header, FALSE);
6913 #undef IS_IN_CUSTOM_HEADER
6915 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6916 gint header_len, gboolean addr_field)
6918 gchar *tmpstr = NULL;
6919 const gchar *out_codeset = NULL;
6921 cm_return_if_fail(src != NULL);
6922 cm_return_if_fail(dest != NULL);
6924 if (len < 1) return;
6926 tmpstr = g_strdup(src);
6928 subst_char(tmpstr, '\n', ' ');
6929 subst_char(tmpstr, '\r', ' ');
6932 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6933 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6934 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6939 codeconv_set_strict(TRUE);
6940 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6941 conv_get_charset_str(compose->out_encoding));
6942 codeconv_set_strict(FALSE);
6944 if (!dest || *dest == '\0') {
6945 gchar *test_conv_global_out = NULL;
6946 gchar *test_conv_reply = NULL;
6948 /* automatic mode. be automatic. */
6949 codeconv_set_strict(TRUE);
6951 out_codeset = conv_get_outgoing_charset_str();
6953 debug_print("trying to convert to %s\n", out_codeset);
6954 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6957 if (!test_conv_global_out && compose->orig_charset
6958 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6959 out_codeset = compose->orig_charset;
6960 debug_print("failure; trying to convert to %s\n", out_codeset);
6961 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6964 if (!test_conv_global_out && !test_conv_reply) {
6966 out_codeset = CS_INTERNAL;
6967 debug_print("finally using %s\n", out_codeset);
6969 g_free(test_conv_global_out);
6970 g_free(test_conv_reply);
6971 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6973 codeconv_set_strict(FALSE);
6978 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6982 cm_return_if_fail(user_data != NULL);
6984 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6985 g_strstrip(address);
6986 if (*address != '\0') {
6987 gchar *name = procheader_get_fromname(address);
6988 extract_address(address);
6989 #ifndef USE_ALT_ADDRBOOK
6990 addressbook_add_contact(name, address, NULL, NULL);
6992 debug_print("%s: %s\n", name, address);
6993 if (addressadd_selection(name, address, NULL, NULL)) {
6994 debug_print( "addressbook_add_contact - added\n" );
7001 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7003 GtkWidget *menuitem;
7006 cm_return_if_fail(menu != NULL);
7007 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7009 menuitem = gtk_separator_menu_item_new();
7010 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7011 gtk_widget_show(menuitem);
7013 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7014 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7016 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7017 g_strstrip(address);
7018 if (*address == '\0') {
7019 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7022 g_signal_connect(G_OBJECT(menuitem), "activate",
7023 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7024 gtk_widget_show(menuitem);
7027 void compose_add_extra_header(gchar *header, GtkListStore *model)
7030 if (strcmp(header, "")) {
7031 COMBOBOX_ADD(model, header, COMPOSE_TO);
7035 void compose_add_extra_header_entries(GtkListStore *model)
7039 gchar buf[BUFFSIZE];
7042 if (extra_headers == NULL) {
7043 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7044 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7045 debug_print("extra headers file not found\n");
7046 goto extra_headers_done;
7048 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7049 lastc = strlen(buf) - 1; /* remove trailing control chars */
7050 while (lastc >= 0 && buf[lastc] != ':')
7051 buf[lastc--] = '\0';
7052 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7053 buf[lastc] = '\0'; /* remove trailing : for comparison */
7054 if (custom_header_is_allowed(buf)) {
7056 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7059 g_message("disallowed extra header line: %s\n", buf);
7063 g_message("invalid extra header line: %s\n", buf);
7069 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7070 extra_headers = g_slist_reverse(extra_headers);
7072 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7076 static void _ldap_srv_func(gpointer data, gpointer user_data)
7078 LdapServer *server = (LdapServer *)data;
7079 gboolean *enable = (gboolean *)user_data;
7081 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7082 server->searchFlag = *enable;
7086 static void compose_create_header_entry(Compose *compose)
7088 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7095 const gchar *header = NULL;
7096 ComposeHeaderEntry *headerentry;
7097 gboolean standard_header = FALSE;
7098 GtkListStore *model;
7101 headerentry = g_new0(ComposeHeaderEntry, 1);
7103 /* Combo box model */
7104 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7105 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7107 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7109 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7111 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7112 COMPOSE_NEWSGROUPS);
7113 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7115 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7116 COMPOSE_FOLLOWUPTO);
7117 compose_add_extra_header_entries(model);
7120 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7121 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7122 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7123 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7124 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7125 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7126 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7127 G_CALLBACK(compose_grab_focus_cb), compose);
7128 gtk_widget_show(combo);
7130 /* Putting only the combobox child into focus chain of its parent causes
7131 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7132 * This eliminates need to pres Tab twice in order to really get from the
7133 * combobox to next widget. */
7135 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7136 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7139 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7140 compose->header_nextrow, compose->header_nextrow+1,
7141 GTK_SHRINK, GTK_FILL, 0, 0);
7142 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7143 const gchar *last_header_entry = gtk_entry_get_text(
7144 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7146 while (*string != NULL) {
7147 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7148 standard_header = TRUE;
7151 if (standard_header)
7152 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7154 if (!compose->header_last || !standard_header) {
7155 switch(compose->account->protocol) {
7157 header = prefs_common_translated_header_name("Newsgroups:");
7160 header = prefs_common_translated_header_name("To:");
7165 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7167 gtk_editable_set_editable(
7168 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7169 prefs_common.type_any_header);
7171 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7172 G_CALLBACK(compose_grab_focus_cb), compose);
7174 /* Entry field with cleanup button */
7175 button = gtk_button_new();
7176 gtk_button_set_image(GTK_BUTTON(button),
7177 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7178 gtk_widget_show(button);
7179 CLAWS_SET_TIP(button,
7180 _("Delete entry contents"));
7181 entry = gtk_entry_new();
7182 gtk_widget_show(entry);
7183 CLAWS_SET_TIP(entry,
7184 _("Use <tab> to autocomplete from addressbook"));
7185 hbox = gtk_hbox_new (FALSE, 0);
7186 gtk_widget_show(hbox);
7187 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7188 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7189 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7190 compose->header_nextrow, compose->header_nextrow+1,
7191 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7193 g_signal_connect(G_OBJECT(entry), "key-press-event",
7194 G_CALLBACK(compose_headerentry_key_press_event_cb),
7196 g_signal_connect(G_OBJECT(entry), "changed",
7197 G_CALLBACK(compose_headerentry_changed_cb),
7199 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7200 G_CALLBACK(compose_grab_focus_cb), compose);
7202 g_signal_connect(G_OBJECT(button), "clicked",
7203 G_CALLBACK(compose_headerentry_button_clicked_cb),
7207 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7208 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7209 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7210 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7211 G_CALLBACK(compose_header_drag_received_cb),
7213 g_signal_connect(G_OBJECT(entry), "drag-drop",
7214 G_CALLBACK(compose_drag_drop),
7216 g_signal_connect(G_OBJECT(entry), "populate-popup",
7217 G_CALLBACK(compose_entry_popup_extend),
7221 #ifndef PASSWORD_CRYPTO_OLD
7222 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7223 if (pwd_servers != NULL && master_passphrase() == NULL) {
7224 gboolean enable = FALSE;
7225 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7226 /* Temporarily disable password-protected LDAP servers,
7227 * because user did not provide a master passphrase.
7228 * We can safely enable searchFlag on all servers in this list
7229 * later, since addrindex_get_password_protected_ldap_servers()
7230 * includes servers which have it enabled initially. */
7231 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7232 compose->passworded_ldap_servers = pwd_servers;
7234 #endif /* PASSWORD_CRYPTO_OLD */
7235 #endif /* USE_LDAP */
7237 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7239 headerentry->compose = compose;
7240 headerentry->combo = combo;
7241 headerentry->entry = entry;
7242 headerentry->button = button;
7243 headerentry->hbox = hbox;
7244 headerentry->headernum = compose->header_nextrow;
7245 headerentry->type = PREF_NONE;
7247 compose->header_nextrow++;
7248 compose->header_last = headerentry;
7249 compose->header_list =
7250 g_slist_append(compose->header_list,
7254 static void compose_add_header_entry(Compose *compose, const gchar *header,
7255 gchar *text, ComposePrefType pref_type)
7257 ComposeHeaderEntry *last_header = compose->header_last;
7258 gchar *tmp = g_strdup(text), *email;
7259 gboolean replyto_hdr;
7261 replyto_hdr = (!strcasecmp(header,
7262 prefs_common_translated_header_name("Reply-To:")) ||
7264 prefs_common_translated_header_name("Followup-To:")) ||
7266 prefs_common_translated_header_name("In-Reply-To:")));
7268 extract_address(tmp);
7269 email = g_utf8_strdown(tmp, -1);
7271 if (replyto_hdr == FALSE &&
7272 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7274 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7275 header, text, (gint) pref_type);
7281 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7282 gtk_entry_set_text(GTK_ENTRY(
7283 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7285 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7286 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7287 last_header->type = pref_type;
7289 if (replyto_hdr == FALSE)
7290 g_hash_table_insert(compose->email_hashtable, email,
7291 GUINT_TO_POINTER(1));
7298 static void compose_destroy_headerentry(Compose *compose,
7299 ComposeHeaderEntry *headerentry)
7301 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7304 extract_address(text);
7305 email = g_utf8_strdown(text, -1);
7306 g_hash_table_remove(compose->email_hashtable, email);
7310 gtk_widget_destroy(headerentry->combo);
7311 gtk_widget_destroy(headerentry->entry);
7312 gtk_widget_destroy(headerentry->button);
7313 gtk_widget_destroy(headerentry->hbox);
7314 g_free(headerentry);
7317 static void compose_remove_header_entries(Compose *compose)
7320 for (list = compose->header_list; list; list = list->next)
7321 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7323 compose->header_last = NULL;
7324 g_slist_free(compose->header_list);
7325 compose->header_list = NULL;
7326 compose->header_nextrow = 1;
7327 compose_create_header_entry(compose);
7330 static GtkWidget *compose_create_header(Compose *compose)
7332 GtkWidget *from_optmenu_hbox;
7333 GtkWidget *header_table_main;
7334 GtkWidget *header_scrolledwin;
7335 GtkWidget *header_table;
7337 /* parent with account selection and from header */
7338 header_table_main = gtk_table_new(2, 2, FALSE);
7339 gtk_widget_show(header_table_main);
7340 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7342 from_optmenu_hbox = compose_account_option_menu_create(compose);
7343 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7344 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7346 /* child with header labels and entries */
7347 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7348 gtk_widget_show(header_scrolledwin);
7349 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7351 header_table = gtk_table_new(2, 2, FALSE);
7352 gtk_widget_show(header_table);
7353 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7354 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7355 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7356 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7357 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7359 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7360 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7362 compose->header_table = header_table;
7363 compose->header_list = NULL;
7364 compose->header_nextrow = 0;
7366 compose_create_header_entry(compose);
7368 compose->table = NULL;
7370 return header_table_main;
7373 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7375 Compose *compose = (Compose *)data;
7376 GdkEventButton event;
7379 event.time = gtk_get_current_event_time();
7381 return attach_button_pressed(compose->attach_clist, &event, compose);
7384 static GtkWidget *compose_create_attach(Compose *compose)
7386 GtkWidget *attach_scrwin;
7387 GtkWidget *attach_clist;
7389 GtkListStore *store;
7390 GtkCellRenderer *renderer;
7391 GtkTreeViewColumn *column;
7392 GtkTreeSelection *selection;
7394 /* attachment list */
7395 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7396 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7397 GTK_POLICY_AUTOMATIC,
7398 GTK_POLICY_AUTOMATIC);
7399 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7401 store = gtk_list_store_new(N_ATTACH_COLS,
7407 G_TYPE_AUTO_POINTER,
7409 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7410 (GTK_TREE_MODEL(store)));
7411 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7412 g_object_unref(store);
7414 renderer = gtk_cell_renderer_text_new();
7415 column = gtk_tree_view_column_new_with_attributes
7416 (_("Mime type"), renderer, "text",
7417 COL_MIMETYPE, NULL);
7418 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7420 renderer = gtk_cell_renderer_text_new();
7421 column = gtk_tree_view_column_new_with_attributes
7422 (_("Size"), renderer, "text",
7424 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7426 renderer = gtk_cell_renderer_text_new();
7427 column = gtk_tree_view_column_new_with_attributes
7428 (_("Name"), renderer, "text",
7430 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7432 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7433 prefs_common.use_stripes_everywhere);
7434 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7435 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7437 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7438 G_CALLBACK(attach_selected), compose);
7439 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7440 G_CALLBACK(attach_button_pressed), compose);
7441 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7442 G_CALLBACK(popup_attach_button_pressed), compose);
7443 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7444 G_CALLBACK(attach_key_pressed), compose);
7447 gtk_drag_dest_set(attach_clist,
7448 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7449 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7450 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7451 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7452 G_CALLBACK(compose_attach_drag_received_cb),
7454 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7455 G_CALLBACK(compose_drag_drop),
7458 compose->attach_scrwin = attach_scrwin;
7459 compose->attach_clist = attach_clist;
7461 return attach_scrwin;
7464 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7465 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7467 static GtkWidget *compose_create_others(Compose *compose)
7470 GtkWidget *savemsg_checkbtn;
7471 GtkWidget *savemsg_combo;
7472 GtkWidget *savemsg_select;
7475 gchar *folderidentifier;
7477 /* Table for settings */
7478 table = gtk_table_new(3, 1, FALSE);
7479 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7480 gtk_widget_show(table);
7481 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7484 /* Save Message to folder */
7485 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7486 gtk_widget_show(savemsg_checkbtn);
7487 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7488 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7489 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7491 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7492 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7494 savemsg_combo = gtk_combo_box_text_new_with_entry();
7495 compose->savemsg_checkbtn = savemsg_checkbtn;
7496 compose->savemsg_combo = savemsg_combo;
7497 gtk_widget_show(savemsg_combo);
7499 if (prefs_common.compose_save_to_history)
7500 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7501 prefs_common.compose_save_to_history);
7502 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7503 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7504 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7505 G_CALLBACK(compose_grab_focus_cb), compose);
7506 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7507 folderidentifier = folder_item_get_identifier(account_get_special_folder
7508 (compose->account, F_OUTBOX));
7509 compose_set_save_to(compose, folderidentifier);
7510 g_free(folderidentifier);
7513 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7514 gtk_widget_show(savemsg_select);
7515 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7516 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7517 G_CALLBACK(compose_savemsg_select_cb),
7523 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7525 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7526 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7529 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7534 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7535 _("Select folder to save message to"));
7538 path = folder_item_get_identifier(dest);
7540 compose_set_save_to(compose, path);
7544 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7545 GdkAtom clip, GtkTextIter *insert_place);
7548 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7552 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7554 if (event->button == 3) {
7556 GtkTextIter sel_start, sel_end;
7557 gboolean stuff_selected;
7559 /* move the cursor to allow GtkAspell to check the word
7560 * under the mouse */
7561 if (event->x && event->y) {
7562 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7563 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7565 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7568 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7569 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7572 stuff_selected = gtk_text_buffer_get_selection_bounds(
7574 &sel_start, &sel_end);
7576 gtk_text_buffer_place_cursor (buffer, &iter);
7577 /* reselect stuff */
7579 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7580 gtk_text_buffer_select_range(buffer,
7581 &sel_start, &sel_end);
7583 return FALSE; /* pass the event so that the right-click goes through */
7586 if (event->button == 2) {
7591 /* get the middle-click position to paste at the correct place */
7592 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7593 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7595 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7598 entry_paste_clipboard(compose, text,
7599 prefs_common.linewrap_pastes,
7600 GDK_SELECTION_PRIMARY, &iter);
7608 static void compose_spell_menu_changed(void *data)
7610 Compose *compose = (Compose *)data;
7612 GtkWidget *menuitem;
7613 GtkWidget *parent_item;
7614 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7617 if (compose->gtkaspell == NULL)
7620 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7621 "/Menu/Spelling/Options");
7623 /* setting the submenu removes /Spelling/Options from the factory
7624 * so we need to save it */
7626 if (parent_item == NULL) {
7627 parent_item = compose->aspell_options_menu;
7628 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7630 compose->aspell_options_menu = parent_item;
7632 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7634 spell_menu = g_slist_reverse(spell_menu);
7635 for (items = spell_menu;
7636 items; items = items->next) {
7637 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7638 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7639 gtk_widget_show(GTK_WIDGET(menuitem));
7641 g_slist_free(spell_menu);
7643 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7644 gtk_widget_show(parent_item);
7647 static void compose_dict_changed(void *data)
7649 Compose *compose = (Compose *) data;
7651 if(!compose->gtkaspell)
7653 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7656 gtkaspell_highlight_all(compose->gtkaspell);
7657 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7661 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7663 Compose *compose = (Compose *)data;
7664 GdkEventButton event;
7667 event.time = gtk_get_current_event_time();
7671 return text_clicked(compose->text, &event, compose);
7674 static gboolean compose_force_window_origin = TRUE;
7675 static Compose *compose_create(PrefsAccount *account,
7684 GtkWidget *handlebox;
7686 GtkWidget *notebook;
7688 GtkWidget *attach_hbox;
7689 GtkWidget *attach_lab1;
7690 GtkWidget *attach_lab2;
7695 GtkWidget *subject_hbox;
7696 GtkWidget *subject_frame;
7697 GtkWidget *subject_entry;
7701 GtkWidget *edit_vbox;
7702 GtkWidget *ruler_hbox;
7704 GtkWidget *scrolledwin;
7706 GtkTextBuffer *buffer;
7707 GtkClipboard *clipboard;
7709 UndoMain *undostruct;
7711 GtkWidget *popupmenu;
7712 GtkWidget *tmpl_menu;
7713 GtkActionGroup *action_group = NULL;
7716 GtkAspell * gtkaspell = NULL;
7719 static GdkGeometry geometry;
7721 cm_return_val_if_fail(account != NULL, NULL);
7723 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7724 &default_header_bgcolor);
7725 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7726 &default_header_color);
7728 debug_print("Creating compose window...\n");
7729 compose = g_new0(Compose, 1);
7731 compose->batch = batch;
7732 compose->account = account;
7733 compose->folder = folder;
7735 compose->mutex = cm_mutex_new();
7736 compose->set_cursor_pos = -1;
7738 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7740 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7741 gtk_widget_set_size_request(window, prefs_common.compose_width,
7742 prefs_common.compose_height);
7744 if (!geometry.max_width) {
7745 geometry.max_width = gdk_screen_width();
7746 geometry.max_height = gdk_screen_height();
7749 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7750 &geometry, GDK_HINT_MAX_SIZE);
7751 if (!geometry.min_width) {
7752 geometry.min_width = 600;
7753 geometry.min_height = 440;
7755 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7756 &geometry, GDK_HINT_MIN_SIZE);
7758 #ifndef GENERIC_UMPC
7759 if (compose_force_window_origin)
7760 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7761 prefs_common.compose_y);
7763 g_signal_connect(G_OBJECT(window), "delete_event",
7764 G_CALLBACK(compose_delete_cb), compose);
7765 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7766 gtk_widget_realize(window);
7768 gtkut_widget_set_composer_icon(window);
7770 vbox = gtk_vbox_new(FALSE, 0);
7771 gtk_container_add(GTK_CONTAINER(window), vbox);
7773 compose->ui_manager = gtk_ui_manager_new();
7774 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7775 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7776 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7777 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7778 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7779 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7780 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7781 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7782 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7783 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7793 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7798 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7817 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7818 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7822 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7831 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7833 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7849 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7902 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)
7903 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)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7909 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)
7910 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)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7915 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)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7919 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)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7925 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)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7932 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)
7933 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)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7940 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7946 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)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7964 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7965 gtk_widget_show_all(menubar);
7967 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7968 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7970 if (prefs_common.toolbar_detachable) {
7971 handlebox = gtk_handle_box_new();
7973 handlebox = gtk_hbox_new(FALSE, 0);
7975 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7977 gtk_widget_realize(handlebox);
7978 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7981 vbox2 = gtk_vbox_new(FALSE, 2);
7982 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7983 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7986 notebook = gtk_notebook_new();
7987 gtk_widget_show(notebook);
7989 /* header labels and entries */
7990 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7991 compose_create_header(compose),
7992 gtk_label_new_with_mnemonic(_("Hea_der")));
7993 /* attachment list */
7994 attach_hbox = gtk_hbox_new(FALSE, 0);
7995 gtk_widget_show(attach_hbox);
7997 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7998 gtk_widget_show(attach_lab1);
7999 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8001 attach_lab2 = gtk_label_new("");
8002 gtk_widget_show(attach_lab2);
8003 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8005 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8006 compose_create_attach(compose),
8009 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8010 compose_create_others(compose),
8011 gtk_label_new_with_mnemonic(_("Othe_rs")));
8014 subject_hbox = gtk_hbox_new(FALSE, 0);
8015 gtk_widget_show(subject_hbox);
8017 subject_frame = gtk_frame_new(NULL);
8018 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8019 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8020 gtk_widget_show(subject_frame);
8022 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8023 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8024 gtk_widget_show(subject);
8026 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8027 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8028 gtk_widget_show(label);
8031 subject_entry = claws_spell_entry_new();
8033 subject_entry = gtk_entry_new();
8035 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8036 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8037 G_CALLBACK(compose_grab_focus_cb), compose);
8038 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8039 gtk_widget_show(subject_entry);
8040 compose->subject_entry = subject_entry;
8041 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8043 edit_vbox = gtk_vbox_new(FALSE, 0);
8045 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8048 ruler_hbox = gtk_hbox_new(FALSE, 0);
8049 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8051 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8052 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8053 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8057 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8058 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8059 GTK_POLICY_AUTOMATIC,
8060 GTK_POLICY_AUTOMATIC);
8061 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8063 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8065 text = gtk_text_view_new();
8066 if (prefs_common.show_compose_margin) {
8067 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8068 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8070 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8071 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8072 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8073 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8074 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8076 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8077 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8078 G_CALLBACK(compose_edit_size_alloc),
8080 g_signal_connect(G_OBJECT(buffer), "changed",
8081 G_CALLBACK(compose_changed_cb), compose);
8082 g_signal_connect(G_OBJECT(text), "grab_focus",
8083 G_CALLBACK(compose_grab_focus_cb), compose);
8084 g_signal_connect(G_OBJECT(buffer), "insert_text",
8085 G_CALLBACK(text_inserted), compose);
8086 g_signal_connect(G_OBJECT(text), "button_press_event",
8087 G_CALLBACK(text_clicked), compose);
8088 g_signal_connect(G_OBJECT(text), "popup-menu",
8089 G_CALLBACK(compose_popup_menu), compose);
8090 g_signal_connect(G_OBJECT(subject_entry), "changed",
8091 G_CALLBACK(compose_changed_cb), compose);
8092 g_signal_connect(G_OBJECT(subject_entry), "activate",
8093 G_CALLBACK(compose_subject_entry_activated), compose);
8096 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8097 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8098 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8099 g_signal_connect(G_OBJECT(text), "drag_data_received",
8100 G_CALLBACK(compose_insert_drag_received_cb),
8102 g_signal_connect(G_OBJECT(text), "drag-drop",
8103 G_CALLBACK(compose_drag_drop),
8105 g_signal_connect(G_OBJECT(text), "key-press-event",
8106 G_CALLBACK(completion_set_focus_to_subject),
8108 gtk_widget_show_all(vbox);
8110 /* pane between attach clist and text */
8111 paned = gtk_vpaned_new();
8112 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8113 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8114 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8115 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8116 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8117 G_CALLBACK(compose_notebook_size_alloc), paned);
8119 gtk_widget_show_all(paned);
8122 if (prefs_common.textfont) {
8123 PangoFontDescription *font_desc;
8125 font_desc = pango_font_description_from_string
8126 (prefs_common.textfont);
8128 gtk_widget_modify_font(text, font_desc);
8129 pango_font_description_free(font_desc);
8133 gtk_action_group_add_actions(action_group, compose_popup_entries,
8134 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8142 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8144 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8145 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8146 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8148 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8150 undostruct = undo_init(text);
8151 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8154 address_completion_start(window);
8156 compose->window = window;
8157 compose->vbox = vbox;
8158 compose->menubar = menubar;
8159 compose->handlebox = handlebox;
8161 compose->vbox2 = vbox2;
8163 compose->paned = paned;
8165 compose->attach_label = attach_lab2;
8167 compose->notebook = notebook;
8168 compose->edit_vbox = edit_vbox;
8169 compose->ruler_hbox = ruler_hbox;
8170 compose->ruler = ruler;
8171 compose->scrolledwin = scrolledwin;
8172 compose->text = text;
8174 compose->focused_editable = NULL;
8176 compose->popupmenu = popupmenu;
8178 compose->tmpl_menu = tmpl_menu;
8180 compose->mode = mode;
8181 compose->rmode = mode;
8183 compose->targetinfo = NULL;
8184 compose->replyinfo = NULL;
8185 compose->fwdinfo = NULL;
8187 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8188 g_str_equal, (GDestroyNotify) g_free, NULL);
8190 compose->replyto = NULL;
8192 compose->bcc = NULL;
8193 compose->followup_to = NULL;
8195 compose->ml_post = NULL;
8197 compose->inreplyto = NULL;
8198 compose->references = NULL;
8199 compose->msgid = NULL;
8200 compose->boundary = NULL;
8202 compose->autowrap = prefs_common.autowrap;
8203 compose->autoindent = prefs_common.auto_indent;
8204 compose->use_signing = FALSE;
8205 compose->use_encryption = FALSE;
8206 compose->privacy_system = NULL;
8207 compose->encdata = NULL;
8209 compose->modified = FALSE;
8211 compose->return_receipt = FALSE;
8213 compose->to_list = NULL;
8214 compose->newsgroup_list = NULL;
8216 compose->undostruct = undostruct;
8218 compose->sig_str = NULL;
8220 compose->exteditor_file = NULL;
8221 compose->exteditor_pid = -1;
8222 compose->exteditor_tag = -1;
8223 compose->exteditor_socket = NULL;
8224 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8226 compose->folder_update_callback_id =
8227 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8228 compose_update_folder_hook,
8229 (gpointer) compose);
8232 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8233 if (mode != COMPOSE_REDIRECT) {
8234 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8235 strcmp(prefs_common.dictionary, "")) {
8236 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8237 prefs_common.alt_dictionary,
8238 conv_get_locale_charset_str(),
8239 prefs_common.color[COL_MISSPELLED],
8240 prefs_common.check_while_typing,
8241 prefs_common.recheck_when_changing_dict,
8242 prefs_common.use_alternate,
8243 prefs_common.use_both_dicts,
8244 GTK_TEXT_VIEW(text),
8245 GTK_WINDOW(compose->window),
8246 compose_dict_changed,
8247 compose_spell_menu_changed,
8250 alertpanel_error(_("Spell checker could not "
8252 gtkaspell_checkers_strerror());
8253 gtkaspell_checkers_reset_error();
8255 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8259 compose->gtkaspell = gtkaspell;
8260 compose_spell_menu_changed(compose);
8261 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8264 compose_select_account(compose, account, TRUE);
8266 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8267 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8269 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8270 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8272 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8273 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8275 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8276 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8278 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8279 if (account->protocol != A_NNTP)
8280 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8281 prefs_common_translated_header_name("To:"));
8283 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8284 prefs_common_translated_header_name("Newsgroups:"));
8286 #ifndef USE_ALT_ADDRBOOK
8287 addressbook_set_target_compose(compose);
8289 if (mode != COMPOSE_REDIRECT)
8290 compose_set_template_menu(compose);
8292 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8295 compose_list = g_list_append(compose_list, compose);
8297 if (!prefs_common.show_ruler)
8298 gtk_widget_hide(ruler_hbox);
8300 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8303 compose->priority = PRIORITY_NORMAL;
8304 compose_update_priority_menu_item(compose);
8306 compose_set_out_encoding(compose);
8309 compose_update_actions_menu(compose);
8311 /* Privacy Systems menu */
8312 compose_update_privacy_systems_menu(compose);
8314 activate_privacy_system(compose, account, TRUE);
8315 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8317 gtk_widget_realize(window);
8319 gtk_widget_show(window);
8325 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8330 GtkWidget *optmenubox;
8331 GtkWidget *fromlabel;
8334 GtkWidget *from_name = NULL;
8336 gint num = 0, def_menu = 0;
8338 accounts = account_get_list();
8339 cm_return_val_if_fail(accounts != NULL, NULL);
8341 optmenubox = gtk_event_box_new();
8342 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8343 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8345 hbox = gtk_hbox_new(FALSE, 4);
8346 from_name = gtk_entry_new();
8348 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8349 G_CALLBACK(compose_grab_focus_cb), compose);
8350 g_signal_connect_after(G_OBJECT(from_name), "activate",
8351 G_CALLBACK(from_name_activate_cb), optmenu);
8353 for (; accounts != NULL; accounts = accounts->next, num++) {
8354 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8355 gchar *name, *from = NULL;
8357 if (ac == compose->account) def_menu = num;
8359 name = g_markup_printf_escaped("<i>%s</i>",
8362 if (ac == compose->account) {
8363 if (ac->name && *ac->name) {
8365 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8366 from = g_strdup_printf("%s <%s>",
8368 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8370 from = g_strdup_printf("%s",
8372 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8374 if (cur_account != compose->account) {
8375 gtk_widget_modify_base(
8376 GTK_WIDGET(from_name),
8377 GTK_STATE_NORMAL, &default_header_bgcolor);
8378 gtk_widget_modify_text(
8379 GTK_WIDGET(from_name),
8380 GTK_STATE_NORMAL, &default_header_color);
8383 COMBOBOX_ADD(menu, name, ac->account_id);
8388 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8390 g_signal_connect(G_OBJECT(optmenu), "changed",
8391 G_CALLBACK(account_activated),
8393 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8394 G_CALLBACK(compose_entry_popup_extend),
8397 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8398 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8400 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8401 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8402 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8404 /* Putting only the GtkEntry into focus chain of parent hbox causes
8405 * the account selector combobox next to it to be unreachable when
8406 * navigating widgets in GtkTable with up/down arrow keys.
8407 * Note: gtk_widget_set_can_focus() was not enough. */
8409 l = g_list_prepend(l, from_name);
8410 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8413 CLAWS_SET_TIP(optmenubox,
8414 _("Account to use for this email"));
8415 CLAWS_SET_TIP(from_name,
8416 _("Sender address to be used"));
8418 compose->account_combo = optmenu;
8419 compose->from_name = from_name;
8424 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8426 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8427 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8428 Compose *compose = (Compose *) data;
8430 compose->priority = value;
8434 static void compose_reply_change_mode(Compose *compose,
8437 gboolean was_modified = compose->modified;
8439 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8441 cm_return_if_fail(compose->replyinfo != NULL);
8443 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8445 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8447 if (action == COMPOSE_REPLY_TO_ALL)
8449 if (action == COMPOSE_REPLY_TO_SENDER)
8451 if (action == COMPOSE_REPLY_TO_LIST)
8454 compose_remove_header_entries(compose);
8455 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8456 if (compose->account->set_autocc && compose->account->auto_cc)
8457 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8459 if (compose->account->set_autobcc && compose->account->auto_bcc)
8460 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8462 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8463 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8464 compose_show_first_last_header(compose, TRUE);
8465 compose->modified = was_modified;
8466 compose_set_title(compose);
8469 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8471 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8472 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8473 Compose *compose = (Compose *) data;
8476 compose_reply_change_mode(compose, value);
8479 static void compose_update_priority_menu_item(Compose * compose)
8481 GtkWidget *menuitem = NULL;
8482 switch (compose->priority) {
8483 case PRIORITY_HIGHEST:
8484 menuitem = gtk_ui_manager_get_widget
8485 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8488 menuitem = gtk_ui_manager_get_widget
8489 (compose->ui_manager, "/Menu/Options/Priority/High");
8491 case PRIORITY_NORMAL:
8492 menuitem = gtk_ui_manager_get_widget
8493 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8496 menuitem = gtk_ui_manager_get_widget
8497 (compose->ui_manager, "/Menu/Options/Priority/Low");
8499 case PRIORITY_LOWEST:
8500 menuitem = gtk_ui_manager_get_widget
8501 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8504 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8507 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8509 Compose *compose = (Compose *) data;
8511 gboolean can_sign = FALSE, can_encrypt = FALSE;
8513 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8515 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8518 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8519 g_free(compose->privacy_system);
8520 compose->privacy_system = NULL;
8521 g_free(compose->encdata);
8522 compose->encdata = NULL;
8523 if (systemid != NULL) {
8524 compose->privacy_system = g_strdup(systemid);
8526 can_sign = privacy_system_can_sign(systemid);
8527 can_encrypt = privacy_system_can_encrypt(systemid);
8530 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8532 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8533 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8534 if (compose->toolbar->privacy_sign_btn != NULL) {
8535 gtk_widget_set_sensitive(
8536 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8538 gtk_toggle_tool_button_set_active(
8539 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8540 can_sign ? compose->use_signing : FALSE);
8542 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8543 gtk_widget_set_sensitive(
8544 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8546 gtk_toggle_tool_button_set_active(
8547 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8548 can_encrypt ? compose->use_encryption : FALSE);
8552 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8554 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8555 GtkWidget *menuitem = NULL;
8556 GList *children, *amenu;
8557 gboolean can_sign = FALSE, can_encrypt = FALSE;
8558 gboolean found = FALSE;
8560 if (compose->privacy_system != NULL) {
8562 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8563 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8564 cm_return_if_fail(menuitem != NULL);
8566 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8569 while (amenu != NULL) {
8570 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8571 if (systemid != NULL) {
8572 if (strcmp(systemid, compose->privacy_system) == 0 &&
8573 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8574 menuitem = GTK_WIDGET(amenu->data);
8576 can_sign = privacy_system_can_sign(systemid);
8577 can_encrypt = privacy_system_can_encrypt(systemid);
8581 } else if (strlen(compose->privacy_system) == 0 &&
8582 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8583 menuitem = GTK_WIDGET(amenu->data);
8586 can_encrypt = FALSE;
8591 amenu = amenu->next;
8593 g_list_free(children);
8594 if (menuitem != NULL)
8595 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8597 if (warn && !found && strlen(compose->privacy_system)) {
8598 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8599 "will not be able to sign or encrypt this message."),
8600 compose->privacy_system);
8604 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8605 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8606 if (compose->toolbar->privacy_sign_btn != NULL) {
8607 gtk_widget_set_sensitive(
8608 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8611 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8612 gtk_widget_set_sensitive(
8613 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8618 static void compose_set_out_encoding(Compose *compose)
8620 CharSet out_encoding;
8621 const gchar *branch = NULL;
8622 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8624 switch(out_encoding) {
8625 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8626 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8627 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8628 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8629 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8630 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8631 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8632 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8633 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8634 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8635 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8636 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8637 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8638 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8639 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8640 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8641 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8642 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8643 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8644 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8645 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8646 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8647 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8648 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8649 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8650 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8651 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8652 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8653 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8654 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8655 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8656 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8657 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8658 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8660 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8663 static void compose_set_template_menu(Compose *compose)
8665 GSList *tmpl_list, *cur;
8669 tmpl_list = template_get_config();
8671 menu = gtk_menu_new();
8673 gtk_menu_set_accel_group (GTK_MENU (menu),
8674 gtk_ui_manager_get_accel_group(compose->ui_manager));
8675 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8676 Template *tmpl = (Template *)cur->data;
8677 gchar *accel_path = NULL;
8678 item = gtk_menu_item_new_with_label(tmpl->name);
8679 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8680 g_signal_connect(G_OBJECT(item), "activate",
8681 G_CALLBACK(compose_template_activate_cb),
8683 g_object_set_data(G_OBJECT(item), "template", tmpl);
8684 gtk_widget_show(item);
8685 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8686 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8690 gtk_widget_show(menu);
8691 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8694 void compose_update_actions_menu(Compose *compose)
8696 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8699 static void compose_update_privacy_systems_menu(Compose *compose)
8701 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8702 GSList *systems, *cur;
8704 GtkWidget *system_none;
8706 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8707 GtkWidget *privacy_menu = gtk_menu_new();
8709 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8710 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8712 g_signal_connect(G_OBJECT(system_none), "activate",
8713 G_CALLBACK(compose_set_privacy_system_cb), compose);
8715 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8716 gtk_widget_show(system_none);
8718 systems = privacy_get_system_ids();
8719 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8720 gchar *systemid = cur->data;
8722 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8723 widget = gtk_radio_menu_item_new_with_label(group,
8724 privacy_system_get_name(systemid));
8725 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8726 g_strdup(systemid), g_free);
8727 g_signal_connect(G_OBJECT(widget), "activate",
8728 G_CALLBACK(compose_set_privacy_system_cb), compose);
8730 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8731 gtk_widget_show(widget);
8734 g_slist_free(systems);
8735 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8736 gtk_widget_show_all(privacy_menu);
8737 gtk_widget_show_all(privacy_menuitem);
8740 void compose_reflect_prefs_all(void)
8745 for (cur = compose_list; cur != NULL; cur = cur->next) {
8746 compose = (Compose *)cur->data;
8747 compose_set_template_menu(compose);
8751 void compose_reflect_prefs_pixmap_theme(void)
8756 for (cur = compose_list; cur != NULL; cur = cur->next) {
8757 compose = (Compose *)cur->data;
8758 toolbar_update(TOOLBAR_COMPOSE, compose);
8762 static const gchar *compose_quote_char_from_context(Compose *compose)
8764 const gchar *qmark = NULL;
8766 cm_return_val_if_fail(compose != NULL, NULL);
8768 switch (compose->mode) {
8769 /* use forward-specific quote char */
8770 case COMPOSE_FORWARD:
8771 case COMPOSE_FORWARD_AS_ATTACH:
8772 case COMPOSE_FORWARD_INLINE:
8773 if (compose->folder && compose->folder->prefs &&
8774 compose->folder->prefs->forward_with_format)
8775 qmark = compose->folder->prefs->forward_quotemark;
8776 else if (compose->account->forward_with_format)
8777 qmark = compose->account->forward_quotemark;
8779 qmark = prefs_common.fw_quotemark;
8782 /* use reply-specific quote char in all other modes */
8784 if (compose->folder && compose->folder->prefs &&
8785 compose->folder->prefs->reply_with_format)
8786 qmark = compose->folder->prefs->reply_quotemark;
8787 else if (compose->account->reply_with_format)
8788 qmark = compose->account->reply_quotemark;
8790 qmark = prefs_common.quotemark;
8794 if (qmark == NULL || *qmark == '\0')
8800 static void compose_template_apply(Compose *compose, Template *tmpl,
8804 GtkTextBuffer *buffer;
8808 gchar *parsed_str = NULL;
8809 gint cursor_pos = 0;
8810 const gchar *err_msg = _("The body of the template has an error at line %d.");
8813 /* process the body */
8815 text = GTK_TEXT_VIEW(compose->text);
8816 buffer = gtk_text_view_get_buffer(text);
8819 qmark = compose_quote_char_from_context(compose);
8821 if (compose->replyinfo != NULL) {
8824 gtk_text_buffer_set_text(buffer, "", -1);
8825 mark = gtk_text_buffer_get_insert(buffer);
8826 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8828 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8829 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8831 } else if (compose->fwdinfo != NULL) {
8834 gtk_text_buffer_set_text(buffer, "", -1);
8835 mark = gtk_text_buffer_get_insert(buffer);
8836 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8838 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8839 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8842 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8844 GtkTextIter start, end;
8847 gtk_text_buffer_get_start_iter(buffer, &start);
8848 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8849 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8851 /* clear the buffer now */
8853 gtk_text_buffer_set_text(buffer, "", -1);
8855 parsed_str = compose_quote_fmt(compose, dummyinfo,
8856 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8857 procmsg_msginfo_free( &dummyinfo );
8863 gtk_text_buffer_set_text(buffer, "", -1);
8864 mark = gtk_text_buffer_get_insert(buffer);
8865 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8868 if (replace && parsed_str && compose->account->auto_sig)
8869 compose_insert_sig(compose, FALSE);
8871 if (replace && parsed_str) {
8872 gtk_text_buffer_get_start_iter(buffer, &iter);
8873 gtk_text_buffer_place_cursor(buffer, &iter);
8877 cursor_pos = quote_fmt_get_cursor_pos();
8878 compose->set_cursor_pos = cursor_pos;
8879 if (cursor_pos == -1)
8881 gtk_text_buffer_get_start_iter(buffer, &iter);
8882 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8883 gtk_text_buffer_place_cursor(buffer, &iter);
8886 /* process the other fields */
8888 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8889 compose_template_apply_fields(compose, tmpl);
8890 quote_fmt_reset_vartable();
8891 quote_fmtlex_destroy();
8893 compose_changed_cb(NULL, compose);
8896 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8897 gtkaspell_highlight_all(compose->gtkaspell);
8901 static void compose_template_apply_fields_error(const gchar *header)
8906 tr = g_strdup(C_("'%s' stands for a header name",
8907 "Template '%s' format error."));
8908 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8909 alertpanel_error("%s", text);
8915 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8917 MsgInfo* dummyinfo = NULL;
8918 MsgInfo *msginfo = NULL;
8921 if (compose->replyinfo != NULL)
8922 msginfo = compose->replyinfo;
8923 else if (compose->fwdinfo != NULL)
8924 msginfo = compose->fwdinfo;
8926 dummyinfo = compose_msginfo_new_from_compose(compose);
8927 msginfo = dummyinfo;
8930 if (tmpl->from && *tmpl->from != '\0') {
8932 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8933 compose->gtkaspell);
8935 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8937 quote_fmt_scan_string(tmpl->from);
8940 buf = quote_fmt_get_buffer();
8942 compose_template_apply_fields_error("From");
8944 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8947 quote_fmt_reset_vartable();
8948 quote_fmtlex_destroy();
8951 if (tmpl->to && *tmpl->to != '\0') {
8953 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8954 compose->gtkaspell);
8956 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8958 quote_fmt_scan_string(tmpl->to);
8961 buf = quote_fmt_get_buffer();
8963 compose_template_apply_fields_error("To");
8965 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8968 quote_fmt_reset_vartable();
8969 quote_fmtlex_destroy();
8972 if (tmpl->cc && *tmpl->cc != '\0') {
8974 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8975 compose->gtkaspell);
8977 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8979 quote_fmt_scan_string(tmpl->cc);
8982 buf = quote_fmt_get_buffer();
8984 compose_template_apply_fields_error("Cc");
8986 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8989 quote_fmt_reset_vartable();
8990 quote_fmtlex_destroy();
8993 if (tmpl->bcc && *tmpl->bcc != '\0') {
8995 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8996 compose->gtkaspell);
8998 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9000 quote_fmt_scan_string(tmpl->bcc);
9003 buf = quote_fmt_get_buffer();
9005 compose_template_apply_fields_error("Bcc");
9007 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9010 quote_fmt_reset_vartable();
9011 quote_fmtlex_destroy();
9014 if (tmpl->replyto && *tmpl->replyto != '\0') {
9016 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9017 compose->gtkaspell);
9019 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9021 quote_fmt_scan_string(tmpl->replyto);
9024 buf = quote_fmt_get_buffer();
9026 compose_template_apply_fields_error("Reply-To");
9028 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9031 quote_fmt_reset_vartable();
9032 quote_fmtlex_destroy();
9035 /* process the subject */
9036 if (tmpl->subject && *tmpl->subject != '\0') {
9038 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9039 compose->gtkaspell);
9041 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9043 quote_fmt_scan_string(tmpl->subject);
9046 buf = quote_fmt_get_buffer();
9048 compose_template_apply_fields_error("Subject");
9050 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9053 quote_fmt_reset_vartable();
9054 quote_fmtlex_destroy();
9057 procmsg_msginfo_free( &dummyinfo );
9060 static void compose_destroy(Compose *compose)
9062 GtkAllocation allocation;
9063 GtkTextBuffer *buffer;
9064 GtkClipboard *clipboard;
9066 compose_list = g_list_remove(compose_list, compose);
9069 gboolean enable = TRUE;
9070 g_slist_foreach(compose->passworded_ldap_servers,
9071 _ldap_srv_func, &enable);
9072 g_slist_free(compose->passworded_ldap_servers);
9075 if (compose->updating) {
9076 debug_print("danger, not destroying anything now\n");
9077 compose->deferred_destroy = TRUE;
9081 /* NOTE: address_completion_end() does nothing with the window
9082 * however this may change. */
9083 address_completion_end(compose->window);
9085 slist_free_strings_full(compose->to_list);
9086 slist_free_strings_full(compose->newsgroup_list);
9087 slist_free_strings_full(compose->header_list);
9089 slist_free_strings_full(extra_headers);
9090 extra_headers = NULL;
9092 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9094 g_hash_table_destroy(compose->email_hashtable);
9096 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9097 compose->folder_update_callback_id);
9099 procmsg_msginfo_free(&(compose->targetinfo));
9100 procmsg_msginfo_free(&(compose->replyinfo));
9101 procmsg_msginfo_free(&(compose->fwdinfo));
9103 g_free(compose->replyto);
9104 g_free(compose->cc);
9105 g_free(compose->bcc);
9106 g_free(compose->newsgroups);
9107 g_free(compose->followup_to);
9109 g_free(compose->ml_post);
9111 g_free(compose->inreplyto);
9112 g_free(compose->references);
9113 g_free(compose->msgid);
9114 g_free(compose->boundary);
9116 g_free(compose->redirect_filename);
9117 if (compose->undostruct)
9118 undo_destroy(compose->undostruct);
9120 g_free(compose->sig_str);
9122 g_free(compose->exteditor_file);
9124 g_free(compose->orig_charset);
9126 g_free(compose->privacy_system);
9127 g_free(compose->encdata);
9129 #ifndef USE_ALT_ADDRBOOK
9130 if (addressbook_get_target_compose() == compose)
9131 addressbook_set_target_compose(NULL);
9134 if (compose->gtkaspell) {
9135 gtkaspell_delete(compose->gtkaspell);
9136 compose->gtkaspell = NULL;
9140 if (!compose->batch) {
9141 gtk_widget_get_allocation(compose->window, &allocation);
9142 prefs_common.compose_width = allocation.width;
9143 prefs_common.compose_height = allocation.height;
9146 if (!gtk_widget_get_parent(compose->paned))
9147 gtk_widget_destroy(compose->paned);
9148 gtk_widget_destroy(compose->popupmenu);
9150 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9151 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9152 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9154 gtk_widget_destroy(compose->window);
9155 toolbar_destroy(compose->toolbar);
9156 g_free(compose->toolbar);
9157 cm_mutex_free(compose->mutex);
9161 static void compose_attach_info_free(AttachInfo *ainfo)
9163 g_free(ainfo->file);
9164 g_free(ainfo->content_type);
9165 g_free(ainfo->name);
9166 g_free(ainfo->charset);
9170 static void compose_attach_update_label(Compose *compose)
9175 GtkTreeModel *model;
9179 if (compose == NULL)
9182 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9183 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9184 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9188 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9189 total_size = ainfo->size;
9190 while(gtk_tree_model_iter_next(model, &iter)) {
9191 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9192 total_size += ainfo->size;
9195 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9196 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9200 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9202 Compose *compose = (Compose *)data;
9203 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9204 GtkTreeSelection *selection;
9206 GtkTreeModel *model;
9208 selection = gtk_tree_view_get_selection(tree_view);
9209 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9210 cm_return_if_fail(sel);
9212 for (cur = sel; cur != NULL; cur = cur->next) {
9213 GtkTreePath *path = cur->data;
9214 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9217 gtk_tree_path_free(path);
9220 for (cur = sel; cur != NULL; cur = cur->next) {
9221 GtkTreeRowReference *ref = cur->data;
9222 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9225 if (gtk_tree_model_get_iter(model, &iter, path))
9226 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9228 gtk_tree_path_free(path);
9229 gtk_tree_row_reference_free(ref);
9233 compose_attach_update_label(compose);
9236 static struct _AttachProperty
9239 GtkWidget *mimetype_entry;
9240 GtkWidget *encoding_optmenu;
9241 GtkWidget *path_entry;
9242 GtkWidget *filename_entry;
9244 GtkWidget *cancel_btn;
9247 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9249 gtk_tree_path_free((GtkTreePath *)ptr);
9252 static void compose_attach_property(GtkAction *action, gpointer data)
9254 Compose *compose = (Compose *)data;
9255 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9257 GtkComboBox *optmenu;
9258 GtkTreeSelection *selection;
9260 GtkTreeModel *model;
9263 static gboolean cancelled;
9265 /* only if one selected */
9266 selection = gtk_tree_view_get_selection(tree_view);
9267 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9270 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9271 cm_return_if_fail(sel);
9273 path = (GtkTreePath *) sel->data;
9274 gtk_tree_model_get_iter(model, &iter, path);
9275 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9278 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9284 if (!attach_prop.window)
9285 compose_attach_property_create(&cancelled);
9286 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9287 gtk_widget_grab_focus(attach_prop.ok_btn);
9288 gtk_widget_show(attach_prop.window);
9289 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9290 GTK_WINDOW(compose->window));
9292 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9293 if (ainfo->encoding == ENC_UNKNOWN)
9294 combobox_select_by_data(optmenu, ENC_BASE64);
9296 combobox_select_by_data(optmenu, ainfo->encoding);
9298 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9299 ainfo->content_type ? ainfo->content_type : "");
9300 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9301 ainfo->file ? ainfo->file : "");
9302 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9303 ainfo->name ? ainfo->name : "");
9306 const gchar *entry_text;
9308 gchar *cnttype = NULL;
9315 gtk_widget_hide(attach_prop.window);
9316 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9321 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9322 if (*entry_text != '\0') {
9325 text = g_strstrip(g_strdup(entry_text));
9326 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9327 cnttype = g_strdup(text);
9330 alertpanel_error(_("Invalid MIME type."));
9336 ainfo->encoding = combobox_get_active_data(optmenu);
9338 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9339 if (*entry_text != '\0') {
9340 if (is_file_exist(entry_text) &&
9341 (size = get_file_size(entry_text)) > 0)
9342 file = g_strdup(entry_text);
9345 (_("File doesn't exist or is empty."));
9351 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9352 if (*entry_text != '\0') {
9353 g_free(ainfo->name);
9354 ainfo->name = g_strdup(entry_text);
9358 g_free(ainfo->content_type);
9359 ainfo->content_type = cnttype;
9362 g_free(ainfo->file);
9366 ainfo->size = (goffset)size;
9368 /* update tree store */
9369 text = to_human_readable(ainfo->size);
9370 gtk_tree_model_get_iter(model, &iter, path);
9371 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9372 COL_MIMETYPE, ainfo->content_type,
9374 COL_NAME, ainfo->name,
9375 COL_CHARSET, ainfo->charset,
9381 gtk_tree_path_free(path);
9384 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9386 label = gtk_label_new(str); \
9387 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9388 GTK_FILL, 0, 0, 0); \
9389 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9391 entry = gtk_entry_new(); \
9392 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9393 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9396 static void compose_attach_property_create(gboolean *cancelled)
9402 GtkWidget *mimetype_entry;
9405 GtkListStore *optmenu_menu;
9406 GtkWidget *path_entry;
9407 GtkWidget *filename_entry;
9410 GtkWidget *cancel_btn;
9411 GList *mime_type_list, *strlist;
9414 debug_print("Creating attach_property window...\n");
9416 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9417 gtk_widget_set_size_request(window, 480, -1);
9418 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9419 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9420 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9421 g_signal_connect(G_OBJECT(window), "delete_event",
9422 G_CALLBACK(attach_property_delete_event),
9424 g_signal_connect(G_OBJECT(window), "key_press_event",
9425 G_CALLBACK(attach_property_key_pressed),
9428 vbox = gtk_vbox_new(FALSE, 8);
9429 gtk_container_add(GTK_CONTAINER(window), vbox);
9431 table = gtk_table_new(4, 2, FALSE);
9432 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9433 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9434 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9436 label = gtk_label_new(_("MIME type"));
9437 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9439 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9440 mimetype_entry = gtk_combo_box_text_new_with_entry();
9441 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9442 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9444 /* stuff with list */
9445 mime_type_list = procmime_get_mime_type_list();
9447 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9448 MimeType *type = (MimeType *) mime_type_list->data;
9451 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9453 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9456 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9457 (GCompareFunc)strcmp2);
9460 for (mime_type_list = strlist; mime_type_list != NULL;
9461 mime_type_list = mime_type_list->next) {
9462 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9463 g_free(mime_type_list->data);
9465 g_list_free(strlist);
9466 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9467 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9469 label = gtk_label_new(_("Encoding"));
9470 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9472 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9474 hbox = gtk_hbox_new(FALSE, 0);
9475 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9476 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9478 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9479 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9481 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9482 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9483 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9484 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9485 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9487 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9489 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9490 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9492 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9493 &ok_btn, GTK_STOCK_OK,
9495 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9496 gtk_widget_grab_default(ok_btn);
9498 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9499 G_CALLBACK(attach_property_ok),
9501 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9502 G_CALLBACK(attach_property_cancel),
9505 gtk_widget_show_all(vbox);
9507 attach_prop.window = window;
9508 attach_prop.mimetype_entry = mimetype_entry;
9509 attach_prop.encoding_optmenu = optmenu;
9510 attach_prop.path_entry = path_entry;
9511 attach_prop.filename_entry = filename_entry;
9512 attach_prop.ok_btn = ok_btn;
9513 attach_prop.cancel_btn = cancel_btn;
9516 #undef SET_LABEL_AND_ENTRY
9518 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9524 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9530 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9531 gboolean *cancelled)
9539 static gboolean attach_property_key_pressed(GtkWidget *widget,
9541 gboolean *cancelled)
9543 if (event && event->keyval == GDK_KEY_Escape) {
9547 if (event && event->keyval == GDK_KEY_Return) {
9555 static void compose_exec_ext_editor(Compose *compose)
9560 GdkNativeWindow socket_wid = 0;
9564 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9565 G_DIR_SEPARATOR, compose);
9567 if (compose_get_ext_editor_uses_socket()) {
9568 /* Only allow one socket */
9569 if (compose->exteditor_socket != NULL) {
9570 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9571 /* Move the focus off of the socket */
9572 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9577 /* Create the receiving GtkSocket */
9578 socket = gtk_socket_new ();
9579 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9580 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9582 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9583 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9584 /* Realize the socket so that we can use its ID */
9585 gtk_widget_realize(socket);
9586 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9587 compose->exteditor_socket = socket;
9590 if (pipe(pipe_fds) < 0) {
9596 if ((pid = fork()) < 0) {
9603 /* close the write side of the pipe */
9606 compose->exteditor_file = g_strdup(tmp);
9607 compose->exteditor_pid = pid;
9609 compose_set_ext_editor_sensitive(compose, FALSE);
9612 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9614 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9616 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9620 } else { /* process-monitoring process */
9626 /* close the read side of the pipe */
9629 if (compose_write_body_to_file(compose, tmp) < 0) {
9630 fd_write_all(pipe_fds[1], "2\n", 2);
9634 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9636 fd_write_all(pipe_fds[1], "1\n", 2);
9640 /* wait until editor is terminated */
9641 waitpid(pid_ed, NULL, 0);
9643 fd_write_all(pipe_fds[1], "0\n", 2);
9650 #endif /* G_OS_UNIX */
9653 static gboolean compose_can_autosave(Compose *compose)
9655 if (compose->privacy_system && compose->use_encryption)
9656 return prefs_common.autosave && prefs_common.autosave_encrypted;
9658 return prefs_common.autosave;
9662 static gboolean compose_get_ext_editor_cmd_valid()
9664 gboolean has_s = FALSE;
9665 gboolean has_w = FALSE;
9666 const gchar *p = prefs_common_get_ext_editor_cmd();
9669 while ((p = strchr(p, '%'))) {
9675 } else if (*p == 'w') {
9686 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9693 cm_return_val_if_fail(file != NULL, -1);
9695 if ((pid = fork()) < 0) {
9700 if (pid != 0) return pid;
9702 /* grandchild process */
9704 if (setpgid(0, getppid()))
9707 if (compose_get_ext_editor_cmd_valid()) {
9708 if (compose_get_ext_editor_uses_socket()) {
9709 p = g_strdup(prefs_common_get_ext_editor_cmd());
9710 s = strstr(p, "%w");
9712 if (strstr(p, "%s") < s)
9713 buf = g_strdup_printf(p, file, socket_wid);
9715 buf = g_strdup_printf(p, socket_wid, file);
9718 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9721 if (prefs_common_get_ext_editor_cmd())
9722 g_warning("External editor command-line is invalid: '%s'",
9723 prefs_common_get_ext_editor_cmd());
9724 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9727 cmdline = strsplit_with_quote(buf, " ", 0);
9729 execvp(cmdline[0], cmdline);
9732 g_strfreev(cmdline);
9737 static gboolean compose_ext_editor_kill(Compose *compose)
9739 pid_t pgid = compose->exteditor_pid * -1;
9742 ret = kill(pgid, 0);
9744 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9748 msg = g_strdup_printf
9749 (_("The external editor is still working.\n"
9750 "Force terminating the process?\n"
9751 "process group id: %d"), -pgid);
9752 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9753 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9758 if (val == G_ALERTALTERNATE) {
9759 g_source_remove(compose->exteditor_tag);
9760 g_io_channel_shutdown(compose->exteditor_ch,
9762 g_io_channel_unref(compose->exteditor_ch);
9764 if (kill(pgid, SIGTERM) < 0) perror("kill");
9765 waitpid(compose->exteditor_pid, NULL, 0);
9767 g_warning("Terminated process group id: %d. "
9768 "Temporary file: %s", -pgid, compose->exteditor_file);
9770 compose_set_ext_editor_sensitive(compose, TRUE);
9772 g_free(compose->exteditor_file);
9773 compose->exteditor_file = NULL;
9774 compose->exteditor_pid = -1;
9775 compose->exteditor_ch = NULL;
9776 compose->exteditor_tag = -1;
9784 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9788 Compose *compose = (Compose *)data;
9791 debug_print("Compose: input from monitoring process\n");
9793 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9798 g_io_channel_shutdown(source, FALSE, NULL);
9799 g_io_channel_unref(source);
9801 waitpid(compose->exteditor_pid, NULL, 0);
9803 if (buf[0] == '0') { /* success */
9804 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9805 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9806 GtkTextIter start, end;
9809 gtk_text_buffer_set_text(buffer, "", -1);
9810 compose_insert_file(compose, compose->exteditor_file);
9811 compose_changed_cb(NULL, compose);
9813 /* Check if we should save the draft or not */
9814 if (compose_can_autosave(compose))
9815 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9817 if (claws_unlink(compose->exteditor_file) < 0)
9818 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9820 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9821 gtk_text_buffer_get_start_iter(buffer, &start);
9822 gtk_text_buffer_get_end_iter(buffer, &end);
9823 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9824 if (chars && strlen(chars) > 0)
9825 compose->modified = TRUE;
9827 } else if (buf[0] == '1') { /* failed */
9828 g_warning("Couldn't exec external editor");
9829 if (claws_unlink(compose->exteditor_file) < 0)
9830 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9831 } else if (buf[0] == '2') {
9832 g_warning("Couldn't write to file");
9833 } else if (buf[0] == '3') {
9834 g_warning("Pipe read failed");
9837 compose_set_ext_editor_sensitive(compose, TRUE);
9839 g_free(compose->exteditor_file);
9840 compose->exteditor_file = NULL;
9841 compose->exteditor_pid = -1;
9842 compose->exteditor_ch = NULL;
9843 compose->exteditor_tag = -1;
9844 if (compose->exteditor_socket) {
9845 gtk_widget_destroy(compose->exteditor_socket);
9846 compose->exteditor_socket = NULL;
9853 static char *ext_editor_menu_entries[] = {
9854 "Menu/Message/Send",
9855 "Menu/Message/SendLater",
9856 "Menu/Message/InsertFile",
9857 "Menu/Message/InsertSig",
9858 "Menu/Message/ReplaceSig",
9859 "Menu/Message/Save",
9860 "Menu/Message/Print",
9865 "Menu/Tools/ShowRuler",
9866 "Menu/Tools/Actions",
9871 static void compose_set_ext_editor_sensitive(Compose *compose,
9876 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9877 cm_menu_set_sensitive_full(compose->ui_manager,
9878 ext_editor_menu_entries[i], sensitive);
9881 if (compose_get_ext_editor_uses_socket()) {
9883 if (compose->exteditor_socket)
9884 gtk_widget_hide(compose->exteditor_socket);
9885 gtk_widget_show(compose->scrolledwin);
9886 if (prefs_common.show_ruler)
9887 gtk_widget_show(compose->ruler_hbox);
9888 /* Fix the focus, as it doesn't go anywhere when the
9889 * socket is hidden or destroyed */
9890 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9892 g_assert (compose->exteditor_socket != NULL);
9893 /* Fix the focus, as it doesn't go anywhere when the
9894 * edit box is hidden */
9895 if (gtk_widget_is_focus(compose->text))
9896 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9897 gtk_widget_hide(compose->scrolledwin);
9898 gtk_widget_hide(compose->ruler_hbox);
9899 gtk_widget_show(compose->exteditor_socket);
9902 gtk_widget_set_sensitive(compose->text, sensitive);
9904 if (compose->toolbar->send_btn)
9905 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9906 if (compose->toolbar->sendl_btn)
9907 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9908 if (compose->toolbar->draft_btn)
9909 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9910 if (compose->toolbar->insert_btn)
9911 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9912 if (compose->toolbar->sig_btn)
9913 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9914 if (compose->toolbar->exteditor_btn)
9915 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9916 if (compose->toolbar->linewrap_current_btn)
9917 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9918 if (compose->toolbar->linewrap_all_btn)
9919 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9922 static gboolean compose_get_ext_editor_uses_socket()
9924 return (prefs_common_get_ext_editor_cmd() &&
9925 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9928 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9930 compose->exteditor_socket = NULL;
9931 /* returning FALSE allows destruction of the socket */
9934 #endif /* G_OS_UNIX */
9937 * compose_undo_state_changed:
9939 * Change the sensivity of the menuentries undo and redo
9941 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9942 gint redo_state, gpointer data)
9944 Compose *compose = (Compose *)data;
9946 switch (undo_state) {
9947 case UNDO_STATE_TRUE:
9948 if (!undostruct->undo_state) {
9949 undostruct->undo_state = TRUE;
9950 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9953 case UNDO_STATE_FALSE:
9954 if (undostruct->undo_state) {
9955 undostruct->undo_state = FALSE;
9956 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9959 case UNDO_STATE_UNCHANGED:
9961 case UNDO_STATE_REFRESH:
9962 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9965 g_warning("Undo state not recognized");
9969 switch (redo_state) {
9970 case UNDO_STATE_TRUE:
9971 if (!undostruct->redo_state) {
9972 undostruct->redo_state = TRUE;
9973 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9976 case UNDO_STATE_FALSE:
9977 if (undostruct->redo_state) {
9978 undostruct->redo_state = FALSE;
9979 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9982 case UNDO_STATE_UNCHANGED:
9984 case UNDO_STATE_REFRESH:
9985 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9988 g_warning("Redo state not recognized");
9993 /* callback functions */
9995 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9996 GtkAllocation *allocation,
9999 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10002 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10003 * includes "non-client" (windows-izm) in calculation, so this calculation
10004 * may not be accurate.
10006 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10007 GtkAllocation *allocation,
10008 GtkSHRuler *shruler)
10010 if (prefs_common.show_ruler) {
10011 gint char_width = 0, char_height = 0;
10012 gint line_width_in_chars;
10014 gtkut_get_font_size(GTK_WIDGET(widget),
10015 &char_width, &char_height);
10016 line_width_in_chars =
10017 (allocation->width - allocation->x) / char_width;
10019 /* got the maximum */
10020 gtk_shruler_set_range(GTK_SHRULER(shruler),
10021 0.0, line_width_in_chars, 0);
10030 ComposePrefType type;
10031 gboolean entry_marked;
10032 } HeaderEntryState;
10034 static void account_activated(GtkComboBox *optmenu, gpointer data)
10036 Compose *compose = (Compose *)data;
10039 gchar *folderidentifier;
10040 gint account_id = 0;
10041 GtkTreeModel *menu;
10043 GSList *list, *saved_list = NULL;
10044 HeaderEntryState *state;
10046 /* Get ID of active account in the combo box */
10047 menu = gtk_combo_box_get_model(optmenu);
10048 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10049 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10051 ac = account_find_from_id(account_id);
10052 cm_return_if_fail(ac != NULL);
10054 if (ac != compose->account) {
10055 compose_select_account(compose, ac, FALSE);
10057 for (list = compose->header_list; list; list = list->next) {
10058 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10060 if (hentry->type == PREF_ACCOUNT || !list->next) {
10061 compose_destroy_headerentry(compose, hentry);
10064 state = g_malloc0(sizeof(HeaderEntryState));
10065 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10066 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10067 state->entry = gtk_editable_get_chars(
10068 GTK_EDITABLE(hentry->entry), 0, -1);
10069 state->type = hentry->type;
10071 saved_list = g_slist_append(saved_list, state);
10072 compose_destroy_headerentry(compose, hentry);
10075 compose->header_last = NULL;
10076 g_slist_free(compose->header_list);
10077 compose->header_list = NULL;
10078 compose->header_nextrow = 1;
10079 compose_create_header_entry(compose);
10081 if (ac->set_autocc && ac->auto_cc)
10082 compose_entry_append(compose, ac->auto_cc,
10083 COMPOSE_CC, PREF_ACCOUNT);
10084 if (ac->set_autobcc && ac->auto_bcc)
10085 compose_entry_append(compose, ac->auto_bcc,
10086 COMPOSE_BCC, PREF_ACCOUNT);
10087 if (ac->set_autoreplyto && ac->auto_replyto)
10088 compose_entry_append(compose, ac->auto_replyto,
10089 COMPOSE_REPLYTO, PREF_ACCOUNT);
10091 for (list = saved_list; list; list = list->next) {
10092 state = (HeaderEntryState *) list->data;
10094 compose_add_header_entry(compose, state->header,
10095 state->entry, state->type);
10097 g_free(state->header);
10098 g_free(state->entry);
10101 g_slist_free(saved_list);
10103 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10104 (ac->protocol == A_NNTP) ?
10105 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10108 /* Set message save folder */
10109 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10110 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10112 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10113 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10115 compose_set_save_to(compose, NULL);
10116 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10117 folderidentifier = folder_item_get_identifier(account_get_special_folder
10118 (compose->account, F_OUTBOX));
10119 compose_set_save_to(compose, folderidentifier);
10120 g_free(folderidentifier);
10124 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10125 GtkTreeViewColumn *column, Compose *compose)
10127 compose_attach_property(NULL, compose);
10130 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10133 Compose *compose = (Compose *)data;
10134 GtkTreeSelection *attach_selection;
10135 gint attach_nr_selected;
10138 if (!event) return FALSE;
10140 if (event->button == 3) {
10141 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10142 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10144 /* If no rows, or just one row is selected, right-click should
10145 * open menu relevant to the row being right-clicked on. We
10146 * achieve that by selecting the clicked row first. If more
10147 * than one row is selected, we shouldn't modify the selection,
10148 * as user may want to remove selected rows (attachments). */
10149 if (attach_nr_selected < 2) {
10150 gtk_tree_selection_unselect_all(attach_selection);
10151 attach_nr_selected = 0;
10152 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10153 event->x, event->y, &path, NULL, NULL, NULL);
10154 if (path != NULL) {
10155 gtk_tree_selection_select_path(attach_selection, path);
10156 gtk_tree_path_free(path);
10157 attach_nr_selected++;
10161 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10162 /* Properties menu item makes no sense with more than one row
10163 * selected, the properties dialog can only edit one attachment. */
10164 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10166 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10167 NULL, NULL, event->button, event->time);
10174 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10177 Compose *compose = (Compose *)data;
10179 if (!event) return FALSE;
10181 switch (event->keyval) {
10182 case GDK_KEY_Delete:
10183 compose_attach_remove_selected(NULL, compose);
10189 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10191 toolbar_comp_set_sensitive(compose, allow);
10192 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10193 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10195 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10197 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10198 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10199 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10201 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10205 static void compose_send_cb(GtkAction *action, gpointer data)
10207 Compose *compose = (Compose *)data;
10210 if (compose->exteditor_tag != -1) {
10211 debug_print("ignoring send: external editor still open\n");
10215 if (prefs_common.work_offline &&
10216 !inc_offline_should_override(TRUE,
10217 _("Claws Mail needs network access in order "
10218 "to send this email.")))
10221 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10222 g_source_remove(compose->draft_timeout_tag);
10223 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10226 compose_send(compose);
10229 static void compose_send_later_cb(GtkAction *action, gpointer data)
10231 Compose *compose = (Compose *)data;
10232 ComposeQueueResult val;
10235 compose_allow_user_actions(compose, FALSE);
10236 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10237 compose_allow_user_actions(compose, TRUE);
10240 if (val == COMPOSE_QUEUE_SUCCESS) {
10241 compose_close(compose);
10243 _display_queue_error(val);
10246 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10249 #define DRAFTED_AT_EXIT "drafted_at_exit"
10250 static void compose_register_draft(MsgInfo *info)
10252 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10253 DRAFTED_AT_EXIT, NULL);
10254 FILE *fp = g_fopen(filepath, "ab");
10257 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10265 gboolean compose_draft (gpointer data, guint action)
10267 Compose *compose = (Compose *)data;
10272 MsgFlags flag = {0, 0};
10273 static gboolean lock = FALSE;
10274 MsgInfo *newmsginfo;
10276 gboolean target_locked = FALSE;
10277 gboolean err = FALSE;
10279 if (lock) return FALSE;
10281 if (compose->sending)
10284 draft = account_get_special_folder(compose->account, F_DRAFT);
10285 cm_return_val_if_fail(draft != NULL, FALSE);
10287 if (!g_mutex_trylock(compose->mutex)) {
10288 /* we don't want to lock the mutex once it's available,
10289 * because as the only other part of compose.c locking
10290 * it is compose_close - which means once unlocked,
10291 * the compose struct will be freed */
10292 debug_print("couldn't lock mutex, probably sending\n");
10298 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10299 G_DIR_SEPARATOR, compose);
10300 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10301 FILE_OP_ERROR(tmp, "fopen");
10305 /* chmod for security */
10306 if (change_file_mode_rw(fp, tmp) < 0) {
10307 FILE_OP_ERROR(tmp, "chmod");
10308 g_warning("can't change file mode");
10311 /* Save draft infos */
10312 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10313 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10315 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10316 gchar *savefolderid;
10318 savefolderid = compose_get_save_to(compose);
10319 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10320 g_free(savefolderid);
10322 if (compose->return_receipt) {
10323 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10325 if (compose->privacy_system) {
10326 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10327 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10328 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10331 /* Message-ID of message replying to */
10332 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10333 gchar *folderid = NULL;
10335 if (compose->replyinfo->folder)
10336 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10337 if (folderid == NULL)
10338 folderid = g_strdup("NULL");
10340 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10343 /* Message-ID of message forwarding to */
10344 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10345 gchar *folderid = NULL;
10347 if (compose->fwdinfo->folder)
10348 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10349 if (folderid == NULL)
10350 folderid = g_strdup("NULL");
10352 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10356 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10357 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10359 sheaders = compose_get_manual_headers_info(compose);
10360 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10363 /* end of headers */
10364 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10371 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10375 if (fclose(fp) == EOF) {
10379 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10380 if (compose->targetinfo) {
10381 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10383 flag.perm_flags |= MSG_LOCKED;
10385 flag.tmp_flags = MSG_DRAFT;
10387 folder_item_scan(draft);
10388 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10389 MsgInfo *tmpinfo = NULL;
10390 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10391 if (compose->msgid) {
10392 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10395 msgnum = tmpinfo->msgnum;
10396 procmsg_msginfo_free(&tmpinfo);
10397 debug_print("got draft msgnum %d from scanning\n", msgnum);
10399 debug_print("didn't get draft msgnum after scanning\n");
10402 debug_print("got draft msgnum %d from adding\n", msgnum);
10408 if (action != COMPOSE_AUTO_SAVE) {
10409 if (action != COMPOSE_DRAFT_FOR_EXIT)
10410 alertpanel_error(_("Could not save draft."));
10413 gtkut_window_popup(compose->window);
10414 val = alertpanel_full(_("Could not save draft"),
10415 _("Could not save draft.\n"
10416 "Do you want to cancel exit or discard this email?"),
10417 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10418 FALSE, NULL, ALERT_QUESTION);
10419 if (val == G_ALERTALTERNATE) {
10421 g_mutex_unlock(compose->mutex); /* must be done before closing */
10422 compose_close(compose);
10426 g_mutex_unlock(compose->mutex); /* must be done before closing */
10435 if (compose->mode == COMPOSE_REEDIT) {
10436 compose_remove_reedit_target(compose, TRUE);
10439 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10442 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10444 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10446 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10447 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10448 procmsg_msginfo_set_flags(newmsginfo, 0,
10449 MSG_HAS_ATTACHMENT);
10451 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10452 compose_register_draft(newmsginfo);
10454 procmsg_msginfo_free(&newmsginfo);
10457 folder_item_scan(draft);
10459 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10461 g_mutex_unlock(compose->mutex); /* must be done before closing */
10462 compose_close(compose);
10469 GError *error = NULL;
10474 goffset size, mtime;
10476 path = folder_item_fetch_msg(draft, msgnum);
10477 if (path == NULL) {
10478 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10482 f = g_file_new_for_path(path);
10483 fi = g_file_query_info(f, "standard::size,time::modified",
10484 G_FILE_QUERY_INFO_NONE, NULL, &error);
10485 if (error != NULL) {
10486 debug_print("couldn't query file info for '%s': %s\n",
10487 path, error->message);
10488 g_error_free(error);
10493 size = g_file_info_get_size(fi);
10494 g_file_info_get_modification_time(fi, &tv);
10496 g_object_unref(fi);
10499 if (g_stat(path, &s) < 0) {
10500 FILE_OP_ERROR(path, "stat");
10505 mtime = s.st_mtime;
10509 procmsg_msginfo_free(&(compose->targetinfo));
10510 compose->targetinfo = procmsg_msginfo_new();
10511 compose->targetinfo->msgnum = msgnum;
10512 compose->targetinfo->size = size;
10513 compose->targetinfo->mtime = mtime;
10514 compose->targetinfo->folder = draft;
10516 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10517 compose->mode = COMPOSE_REEDIT;
10519 if (action == COMPOSE_AUTO_SAVE) {
10520 compose->autosaved_draft = compose->targetinfo;
10522 compose->modified = FALSE;
10523 compose_set_title(compose);
10527 g_mutex_unlock(compose->mutex);
10531 void compose_clear_exit_drafts(void)
10533 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10534 DRAFTED_AT_EXIT, NULL);
10535 if (is_file_exist(filepath))
10536 claws_unlink(filepath);
10541 void compose_reopen_exit_drafts(void)
10543 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10544 DRAFTED_AT_EXIT, NULL);
10545 FILE *fp = g_fopen(filepath, "rb");
10549 while (fgets(buf, sizeof(buf), fp)) {
10550 gchar **parts = g_strsplit(buf, "\t", 2);
10551 const gchar *folder = parts[0];
10552 int msgnum = parts[1] ? atoi(parts[1]):-1;
10554 if (folder && *folder && msgnum > -1) {
10555 FolderItem *item = folder_find_item_from_identifier(folder);
10556 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10558 compose_reedit(info, FALSE);
10565 compose_clear_exit_drafts();
10568 static void compose_save_cb(GtkAction *action, gpointer data)
10570 Compose *compose = (Compose *)data;
10571 compose_draft(compose, COMPOSE_KEEP_EDITING);
10572 compose->rmode = COMPOSE_REEDIT;
10575 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10577 if (compose && file_list) {
10580 for ( tmp = file_list; tmp; tmp = tmp->next) {
10581 gchar *file = (gchar *) tmp->data;
10582 gchar *utf8_filename = conv_filename_to_utf8(file);
10583 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10584 compose_changed_cb(NULL, compose);
10589 g_free(utf8_filename);
10594 static void compose_attach_cb(GtkAction *action, gpointer data)
10596 Compose *compose = (Compose *)data;
10599 if (compose->redirect_filename != NULL)
10602 /* Set focus_window properly, in case we were called via popup menu,
10603 * which unsets it (via focus_out_event callback on compose window). */
10604 manage_window_focus_in(compose->window, NULL, NULL);
10606 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10609 compose_attach_from_list(compose, file_list, TRUE);
10610 g_list_free(file_list);
10614 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10616 Compose *compose = (Compose *)data;
10618 gint files_inserted = 0;
10620 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10625 for ( tmp = file_list; tmp; tmp = tmp->next) {
10626 gchar *file = (gchar *) tmp->data;
10627 gchar *filedup = g_strdup(file);
10628 gchar *shortfile = g_path_get_basename(filedup);
10629 ComposeInsertResult res;
10630 /* insert the file if the file is short or if the user confirmed that
10631 he/she wants to insert the large file */
10632 res = compose_insert_file(compose, file);
10633 if (res == COMPOSE_INSERT_READ_ERROR) {
10634 alertpanel_error(_("File '%s' could not be read."), shortfile);
10635 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10636 alertpanel_error(_("File '%s' contained invalid characters\n"
10637 "for the current encoding, insertion may be incorrect."),
10639 } else if (res == COMPOSE_INSERT_SUCCESS)
10646 g_list_free(file_list);
10650 if (files_inserted > 0 && compose->gtkaspell &&
10651 compose->gtkaspell->check_while_typing)
10652 gtkaspell_highlight_all(compose->gtkaspell);
10656 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10658 Compose *compose = (Compose *)data;
10660 compose_insert_sig(compose, FALSE);
10663 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10665 Compose *compose = (Compose *)data;
10667 compose_insert_sig(compose, TRUE);
10670 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10674 Compose *compose = (Compose *)data;
10676 gtkut_widget_get_uposition(widget, &x, &y);
10677 if (!compose->batch) {
10678 prefs_common.compose_x = x;
10679 prefs_common.compose_y = y;
10681 if (compose->sending || compose->updating)
10683 compose_close_cb(NULL, compose);
10687 void compose_close_toolbar(Compose *compose)
10689 compose_close_cb(NULL, compose);
10692 static void compose_close_cb(GtkAction *action, gpointer data)
10694 Compose *compose = (Compose *)data;
10698 if (compose->exteditor_tag != -1) {
10699 if (!compose_ext_editor_kill(compose))
10704 if (compose->modified) {
10705 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10706 if (!g_mutex_trylock(compose->mutex)) {
10707 /* we don't want to lock the mutex once it's available,
10708 * because as the only other part of compose.c locking
10709 * it is compose_close - which means once unlocked,
10710 * the compose struct will be freed */
10711 debug_print("couldn't lock mutex, probably sending\n");
10715 val = alertpanel(_("Discard message"),
10716 _("This message has been modified. Discard it?"),
10717 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10720 val = alertpanel(_("Save changes"),
10721 _("This message has been modified. Save the latest changes?"),
10722 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10723 ALERTFOCUS_SECOND);
10725 g_mutex_unlock(compose->mutex);
10727 case G_ALERTDEFAULT:
10728 if (compose_can_autosave(compose) && !reedit)
10729 compose_remove_draft(compose);
10731 case G_ALERTALTERNATE:
10732 compose_draft(data, COMPOSE_QUIT_EDITING);
10739 compose_close(compose);
10742 static void compose_print_cb(GtkAction *action, gpointer data)
10744 Compose *compose = (Compose *) data;
10746 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10747 if (compose->targetinfo)
10748 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10751 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10753 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10754 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10755 Compose *compose = (Compose *) data;
10758 compose->out_encoding = (CharSet)value;
10761 static void compose_address_cb(GtkAction *action, gpointer data)
10763 Compose *compose = (Compose *)data;
10765 #ifndef USE_ALT_ADDRBOOK
10766 addressbook_open(compose);
10768 GError* error = NULL;
10769 addressbook_connect_signals(compose);
10770 addressbook_dbus_open(TRUE, &error);
10772 g_warning("%s", error->message);
10773 g_error_free(error);
10778 static void about_show_cb(GtkAction *action, gpointer data)
10783 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10785 Compose *compose = (Compose *)data;
10790 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10791 cm_return_if_fail(tmpl != NULL);
10793 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10795 val = alertpanel(_("Apply template"), msg,
10796 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10799 if (val == G_ALERTDEFAULT)
10800 compose_template_apply(compose, tmpl, TRUE);
10801 else if (val == G_ALERTALTERNATE)
10802 compose_template_apply(compose, tmpl, FALSE);
10805 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10807 Compose *compose = (Compose *)data;
10810 if (compose->exteditor_tag != -1) {
10811 debug_print("ignoring open external editor: external editor still open\n");
10815 compose_exec_ext_editor(compose);
10818 static void compose_undo_cb(GtkAction *action, gpointer data)
10820 Compose *compose = (Compose *)data;
10821 gboolean prev_autowrap = compose->autowrap;
10823 compose->autowrap = FALSE;
10824 undo_undo(compose->undostruct);
10825 compose->autowrap = prev_autowrap;
10828 static void compose_redo_cb(GtkAction *action, gpointer data)
10830 Compose *compose = (Compose *)data;
10831 gboolean prev_autowrap = compose->autowrap;
10833 compose->autowrap = FALSE;
10834 undo_redo(compose->undostruct);
10835 compose->autowrap = prev_autowrap;
10838 static void entry_cut_clipboard(GtkWidget *entry)
10840 if (GTK_IS_EDITABLE(entry))
10841 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10842 else if (GTK_IS_TEXT_VIEW(entry))
10843 gtk_text_buffer_cut_clipboard(
10844 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10845 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10849 static void entry_copy_clipboard(GtkWidget *entry)
10851 if (GTK_IS_EDITABLE(entry))
10852 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10853 else if (GTK_IS_TEXT_VIEW(entry))
10854 gtk_text_buffer_copy_clipboard(
10855 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10856 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10859 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10860 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10862 if (GTK_IS_TEXT_VIEW(entry)) {
10863 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10864 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10865 GtkTextIter start_iter, end_iter;
10867 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10869 if (contents == NULL)
10872 /* we shouldn't delete the selection when middle-click-pasting, or we
10873 * can't mid-click-paste our own selection */
10874 if (clip != GDK_SELECTION_PRIMARY) {
10875 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10876 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10879 if (insert_place == NULL) {
10880 /* if insert_place isn't specified, insert at the cursor.
10881 * used for Ctrl-V pasting */
10882 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10883 start = gtk_text_iter_get_offset(&start_iter);
10884 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10886 /* if insert_place is specified, paste here.
10887 * used for mid-click-pasting */
10888 start = gtk_text_iter_get_offset(insert_place);
10889 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10890 if (prefs_common.primary_paste_unselects)
10891 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10895 /* paste unwrapped: mark the paste so it's not wrapped later */
10896 end = start + strlen(contents);
10897 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10898 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10899 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10900 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10901 /* rewrap paragraph now (after a mid-click-paste) */
10902 mark_start = gtk_text_buffer_get_insert(buffer);
10903 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10904 gtk_text_iter_backward_char(&start_iter);
10905 compose_beautify_paragraph(compose, &start_iter, TRUE);
10907 } else if (GTK_IS_EDITABLE(entry))
10908 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10910 compose->modified = TRUE;
10913 static void entry_allsel(GtkWidget *entry)
10915 if (GTK_IS_EDITABLE(entry))
10916 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10917 else if (GTK_IS_TEXT_VIEW(entry)) {
10918 GtkTextIter startiter, enditer;
10919 GtkTextBuffer *textbuf;
10921 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10922 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10923 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10925 gtk_text_buffer_move_mark_by_name(textbuf,
10926 "selection_bound", &startiter);
10927 gtk_text_buffer_move_mark_by_name(textbuf,
10928 "insert", &enditer);
10932 static void compose_cut_cb(GtkAction *action, gpointer data)
10934 Compose *compose = (Compose *)data;
10935 if (compose->focused_editable
10936 #ifndef GENERIC_UMPC
10937 && gtk_widget_has_focus(compose->focused_editable)
10940 entry_cut_clipboard(compose->focused_editable);
10943 static void compose_copy_cb(GtkAction *action, gpointer data)
10945 Compose *compose = (Compose *)data;
10946 if (compose->focused_editable
10947 #ifndef GENERIC_UMPC
10948 && gtk_widget_has_focus(compose->focused_editable)
10951 entry_copy_clipboard(compose->focused_editable);
10954 static void compose_paste_cb(GtkAction *action, gpointer data)
10956 Compose *compose = (Compose *)data;
10957 gint prev_autowrap;
10958 GtkTextBuffer *buffer;
10960 if (compose->focused_editable
10961 #ifndef GENERIC_UMPC
10962 && gtk_widget_has_focus(compose->focused_editable)
10965 entry_paste_clipboard(compose, compose->focused_editable,
10966 prefs_common.linewrap_pastes,
10967 GDK_SELECTION_CLIPBOARD, NULL);
10972 #ifndef GENERIC_UMPC
10973 gtk_widget_has_focus(compose->text) &&
10975 compose->gtkaspell &&
10976 compose->gtkaspell->check_while_typing)
10977 gtkaspell_highlight_all(compose->gtkaspell);
10981 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10983 Compose *compose = (Compose *)data;
10984 gint wrap_quote = prefs_common.linewrap_quote;
10985 if (compose->focused_editable
10986 #ifndef GENERIC_UMPC
10987 && gtk_widget_has_focus(compose->focused_editable)
10990 /* let text_insert() (called directly or at a later time
10991 * after the gtk_editable_paste_clipboard) know that
10992 * text is to be inserted as a quotation. implemented
10993 * by using a simple refcount... */
10994 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10995 G_OBJECT(compose->focused_editable),
10996 "paste_as_quotation"));
10997 g_object_set_data(G_OBJECT(compose->focused_editable),
10998 "paste_as_quotation",
10999 GINT_TO_POINTER(paste_as_quotation + 1));
11000 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11001 entry_paste_clipboard(compose, compose->focused_editable,
11002 prefs_common.linewrap_pastes,
11003 GDK_SELECTION_CLIPBOARD, NULL);
11004 prefs_common.linewrap_quote = wrap_quote;
11008 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11010 Compose *compose = (Compose *)data;
11011 gint prev_autowrap;
11012 GtkTextBuffer *buffer;
11014 if (compose->focused_editable
11015 #ifndef GENERIC_UMPC
11016 && gtk_widget_has_focus(compose->focused_editable)
11019 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11020 GDK_SELECTION_CLIPBOARD, NULL);
11025 #ifndef GENERIC_UMPC
11026 gtk_widget_has_focus(compose->text) &&
11028 compose->gtkaspell &&
11029 compose->gtkaspell->check_while_typing)
11030 gtkaspell_highlight_all(compose->gtkaspell);
11034 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11036 Compose *compose = (Compose *)data;
11037 gint prev_autowrap;
11038 GtkTextBuffer *buffer;
11040 if (compose->focused_editable
11041 #ifndef GENERIC_UMPC
11042 && gtk_widget_has_focus(compose->focused_editable)
11045 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11046 GDK_SELECTION_CLIPBOARD, NULL);
11051 #ifndef GENERIC_UMPC
11052 gtk_widget_has_focus(compose->text) &&
11054 compose->gtkaspell &&
11055 compose->gtkaspell->check_while_typing)
11056 gtkaspell_highlight_all(compose->gtkaspell);
11060 static void compose_allsel_cb(GtkAction *action, gpointer data)
11062 Compose *compose = (Compose *)data;
11063 if (compose->focused_editable
11064 #ifndef GENERIC_UMPC
11065 && gtk_widget_has_focus(compose->focused_editable)
11068 entry_allsel(compose->focused_editable);
11071 static void textview_move_beginning_of_line (GtkTextView *text)
11073 GtkTextBuffer *buffer;
11077 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11079 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11080 mark = gtk_text_buffer_get_insert(buffer);
11081 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11082 gtk_text_iter_set_line_offset(&ins, 0);
11083 gtk_text_buffer_place_cursor(buffer, &ins);
11086 static void textview_move_forward_character (GtkTextView *text)
11088 GtkTextBuffer *buffer;
11092 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11094 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11095 mark = gtk_text_buffer_get_insert(buffer);
11096 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11097 if (gtk_text_iter_forward_cursor_position(&ins))
11098 gtk_text_buffer_place_cursor(buffer, &ins);
11101 static void textview_move_backward_character (GtkTextView *text)
11103 GtkTextBuffer *buffer;
11107 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11109 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11110 mark = gtk_text_buffer_get_insert(buffer);
11111 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11112 if (gtk_text_iter_backward_cursor_position(&ins))
11113 gtk_text_buffer_place_cursor(buffer, &ins);
11116 static void textview_move_forward_word (GtkTextView *text)
11118 GtkTextBuffer *buffer;
11123 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11125 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11126 mark = gtk_text_buffer_get_insert(buffer);
11127 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11128 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11129 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11130 gtk_text_iter_backward_word_start(&ins);
11131 gtk_text_buffer_place_cursor(buffer, &ins);
11135 static void textview_move_backward_word (GtkTextView *text)
11137 GtkTextBuffer *buffer;
11141 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11143 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11144 mark = gtk_text_buffer_get_insert(buffer);
11145 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11146 if (gtk_text_iter_backward_word_starts(&ins, 1))
11147 gtk_text_buffer_place_cursor(buffer, &ins);
11150 static void textview_move_end_of_line (GtkTextView *text)
11152 GtkTextBuffer *buffer;
11156 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11158 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11159 mark = gtk_text_buffer_get_insert(buffer);
11160 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11161 if (gtk_text_iter_forward_to_line_end(&ins))
11162 gtk_text_buffer_place_cursor(buffer, &ins);
11165 static void textview_move_next_line (GtkTextView *text)
11167 GtkTextBuffer *buffer;
11172 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11174 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11175 mark = gtk_text_buffer_get_insert(buffer);
11176 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11177 offset = gtk_text_iter_get_line_offset(&ins);
11178 if (gtk_text_iter_forward_line(&ins)) {
11179 gtk_text_iter_set_line_offset(&ins, offset);
11180 gtk_text_buffer_place_cursor(buffer, &ins);
11184 static void textview_move_previous_line (GtkTextView *text)
11186 GtkTextBuffer *buffer;
11191 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11193 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11194 mark = gtk_text_buffer_get_insert(buffer);
11195 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11196 offset = gtk_text_iter_get_line_offset(&ins);
11197 if (gtk_text_iter_backward_line(&ins)) {
11198 gtk_text_iter_set_line_offset(&ins, offset);
11199 gtk_text_buffer_place_cursor(buffer, &ins);
11203 static void textview_delete_forward_character (GtkTextView *text)
11205 GtkTextBuffer *buffer;
11207 GtkTextIter ins, end_iter;
11209 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11211 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11212 mark = gtk_text_buffer_get_insert(buffer);
11213 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11215 if (gtk_text_iter_forward_char(&end_iter)) {
11216 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11220 static void textview_delete_backward_character (GtkTextView *text)
11222 GtkTextBuffer *buffer;
11224 GtkTextIter ins, end_iter;
11226 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11228 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11229 mark = gtk_text_buffer_get_insert(buffer);
11230 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11232 if (gtk_text_iter_backward_char(&end_iter)) {
11233 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11237 static void textview_delete_forward_word (GtkTextView *text)
11239 GtkTextBuffer *buffer;
11241 GtkTextIter ins, end_iter;
11243 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11245 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11246 mark = gtk_text_buffer_get_insert(buffer);
11247 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11249 if (gtk_text_iter_forward_word_end(&end_iter)) {
11250 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11254 static void textview_delete_backward_word (GtkTextView *text)
11256 GtkTextBuffer *buffer;
11258 GtkTextIter ins, end_iter;
11260 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11262 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11263 mark = gtk_text_buffer_get_insert(buffer);
11264 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11266 if (gtk_text_iter_backward_word_start(&end_iter)) {
11267 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11271 static void textview_delete_line (GtkTextView *text)
11273 GtkTextBuffer *buffer;
11275 GtkTextIter ins, start_iter, end_iter;
11277 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11279 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11280 mark = gtk_text_buffer_get_insert(buffer);
11281 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11284 gtk_text_iter_set_line_offset(&start_iter, 0);
11287 if (gtk_text_iter_ends_line(&end_iter)){
11288 if (!gtk_text_iter_forward_char(&end_iter))
11289 gtk_text_iter_backward_char(&start_iter);
11292 gtk_text_iter_forward_to_line_end(&end_iter);
11293 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11296 static void textview_delete_to_line_end (GtkTextView *text)
11298 GtkTextBuffer *buffer;
11300 GtkTextIter ins, end_iter;
11302 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11304 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11305 mark = gtk_text_buffer_get_insert(buffer);
11306 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11308 if (gtk_text_iter_ends_line(&end_iter))
11309 gtk_text_iter_forward_char(&end_iter);
11311 gtk_text_iter_forward_to_line_end(&end_iter);
11312 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11315 #define DO_ACTION(name, act) { \
11316 if(!strcmp(name, a_name)) { \
11320 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11322 const gchar *a_name = gtk_action_get_name(action);
11323 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11324 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11325 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11326 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11327 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11328 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11329 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11330 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11331 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11332 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11333 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11334 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11335 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11336 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11337 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11340 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11342 Compose *compose = (Compose *)data;
11343 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11344 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11346 action = compose_call_advanced_action_from_path(gaction);
11349 void (*do_action) (GtkTextView *text);
11350 } action_table[] = {
11351 {textview_move_beginning_of_line},
11352 {textview_move_forward_character},
11353 {textview_move_backward_character},
11354 {textview_move_forward_word},
11355 {textview_move_backward_word},
11356 {textview_move_end_of_line},
11357 {textview_move_next_line},
11358 {textview_move_previous_line},
11359 {textview_delete_forward_character},
11360 {textview_delete_backward_character},
11361 {textview_delete_forward_word},
11362 {textview_delete_backward_word},
11363 {textview_delete_line},
11364 {textview_delete_to_line_end}
11367 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11369 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11370 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11371 if (action_table[action].do_action)
11372 action_table[action].do_action(text);
11374 g_warning("Not implemented yet.");
11378 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11380 GtkAllocation allocation;
11384 if (GTK_IS_EDITABLE(widget)) {
11385 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11386 gtk_editable_set_position(GTK_EDITABLE(widget),
11389 if ((parent = gtk_widget_get_parent(widget))
11390 && (parent = gtk_widget_get_parent(parent))
11391 && (parent = gtk_widget_get_parent(parent))) {
11392 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11393 gtk_widget_get_allocation(widget, &allocation);
11394 gint y = allocation.y;
11395 gint height = allocation.height;
11396 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11397 (GTK_SCROLLED_WINDOW(parent));
11399 gfloat value = gtk_adjustment_get_value(shown);
11400 gfloat upper = gtk_adjustment_get_upper(shown);
11401 gfloat page_size = gtk_adjustment_get_page_size(shown);
11402 if (y < (int)value) {
11403 gtk_adjustment_set_value(shown, y - 1);
11405 if ((y + height) > ((int)value + (int)page_size)) {
11406 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11407 gtk_adjustment_set_value(shown,
11408 y + height - (int)page_size - 1);
11410 gtk_adjustment_set_value(shown,
11411 (int)upper - (int)page_size - 1);
11418 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11419 compose->focused_editable = widget;
11421 #ifdef GENERIC_UMPC
11422 if (GTK_IS_TEXT_VIEW(widget)
11423 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11424 g_object_ref(compose->notebook);
11425 g_object_ref(compose->edit_vbox);
11426 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11427 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11428 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11429 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11430 g_object_unref(compose->notebook);
11431 g_object_unref(compose->edit_vbox);
11432 g_signal_handlers_block_by_func(G_OBJECT(widget),
11433 G_CALLBACK(compose_grab_focus_cb),
11435 gtk_widget_grab_focus(widget);
11436 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11437 G_CALLBACK(compose_grab_focus_cb),
11439 } else if (!GTK_IS_TEXT_VIEW(widget)
11440 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11441 g_object_ref(compose->notebook);
11442 g_object_ref(compose->edit_vbox);
11443 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11444 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11445 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11446 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11447 g_object_unref(compose->notebook);
11448 g_object_unref(compose->edit_vbox);
11449 g_signal_handlers_block_by_func(G_OBJECT(widget),
11450 G_CALLBACK(compose_grab_focus_cb),
11452 gtk_widget_grab_focus(widget);
11453 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11454 G_CALLBACK(compose_grab_focus_cb),
11460 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11462 compose->modified = TRUE;
11463 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11464 #ifndef GENERIC_UMPC
11465 compose_set_title(compose);
11469 static void compose_wrap_cb(GtkAction *action, gpointer data)
11471 Compose *compose = (Compose *)data;
11472 compose_beautify_paragraph(compose, NULL, TRUE);
11475 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11477 Compose *compose = (Compose *)data;
11478 compose_wrap_all_full(compose, TRUE);
11481 static void compose_find_cb(GtkAction *action, gpointer data)
11483 Compose *compose = (Compose *)data;
11485 message_search_compose(compose);
11488 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11491 Compose *compose = (Compose *)data;
11492 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11493 if (compose->autowrap)
11494 compose_wrap_all_full(compose, TRUE);
11495 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11498 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11501 Compose *compose = (Compose *)data;
11502 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11505 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11507 Compose *compose = (Compose *)data;
11509 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11510 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11513 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11515 Compose *compose = (Compose *)data;
11517 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11518 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11521 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11523 g_free(compose->privacy_system);
11524 g_free(compose->encdata);
11526 compose->privacy_system = g_strdup(account->default_privacy_system);
11527 compose_update_privacy_system_menu_item(compose, warn);
11530 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11532 Compose *compose = (Compose *)data;
11534 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11535 gtk_widget_show(compose->ruler_hbox);
11536 prefs_common.show_ruler = TRUE;
11538 gtk_widget_hide(compose->ruler_hbox);
11539 gtk_widget_queue_resize(compose->edit_vbox);
11540 prefs_common.show_ruler = FALSE;
11544 static void compose_attach_drag_received_cb (GtkWidget *widget,
11545 GdkDragContext *context,
11548 GtkSelectionData *data,
11551 gpointer user_data)
11553 Compose *compose = (Compose *)user_data;
11557 type = gtk_selection_data_get_data_type(data);
11558 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11559 && gtk_drag_get_source_widget(context) !=
11560 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11561 list = uri_list_extract_filenames(
11562 (const gchar *)gtk_selection_data_get_data(data));
11563 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11564 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11565 compose_attach_append
11566 (compose, (const gchar *)tmp->data,
11567 utf8_filename, NULL, NULL);
11568 g_free(utf8_filename);
11571 compose_changed_cb(NULL, compose);
11572 list_free_strings_full(list);
11573 } else if (gtk_drag_get_source_widget(context)
11574 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11575 /* comes from our summaryview */
11576 SummaryView * summaryview = NULL;
11577 GSList * list = NULL, *cur = NULL;
11579 if (mainwindow_get_mainwindow())
11580 summaryview = mainwindow_get_mainwindow()->summaryview;
11583 list = summary_get_selected_msg_list(summaryview);
11585 for (cur = list; cur; cur = cur->next) {
11586 MsgInfo *msginfo = (MsgInfo *)cur->data;
11587 gchar *file = NULL;
11589 file = procmsg_get_message_file_full(msginfo,
11592 compose_attach_append(compose, (const gchar *)file,
11593 (const gchar *)file, "message/rfc822", NULL);
11597 g_slist_free(list);
11601 static gboolean compose_drag_drop(GtkWidget *widget,
11602 GdkDragContext *drag_context,
11604 guint time, gpointer user_data)
11606 /* not handling this signal makes compose_insert_drag_received_cb
11611 static gboolean completion_set_focus_to_subject
11612 (GtkWidget *widget,
11613 GdkEventKey *event,
11616 cm_return_val_if_fail(compose != NULL, FALSE);
11618 /* make backtab move to subject field */
11619 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11620 gtk_widget_grab_focus(compose->subject_entry);
11626 static void compose_insert_drag_received_cb (GtkWidget *widget,
11627 GdkDragContext *drag_context,
11630 GtkSelectionData *data,
11633 gpointer user_data)
11635 Compose *compose = (Compose *)user_data;
11641 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11643 type = gtk_selection_data_get_data_type(data);
11644 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11645 AlertValue val = G_ALERTDEFAULT;
11646 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11648 list = uri_list_extract_filenames(ddata);
11649 num_files = g_list_length(list);
11650 if (list == NULL && strstr(ddata, "://")) {
11651 /* Assume a list of no files, and data has ://, is a remote link */
11652 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11653 gchar *tmpfile = get_tmp_file();
11654 str_write_to_file(tmpdata, tmpfile);
11656 compose_insert_file(compose, tmpfile);
11657 claws_unlink(tmpfile);
11659 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11660 compose_beautify_paragraph(compose, NULL, TRUE);
11663 switch (prefs_common.compose_dnd_mode) {
11664 case COMPOSE_DND_ASK:
11665 msg = g_strdup_printf(
11667 "Do you want to insert the contents of the file "
11668 "into the message body, or attach it to the email?",
11669 "Do you want to insert the contents of the %d files "
11670 "into the message body, or attach them to the email?",
11673 val = alertpanel_full(_("Insert or attach?"), msg,
11674 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11676 TRUE, NULL, ALERT_QUESTION);
11679 case COMPOSE_DND_INSERT:
11680 val = G_ALERTALTERNATE;
11682 case COMPOSE_DND_ATTACH:
11683 val = G_ALERTOTHER;
11686 /* unexpected case */
11687 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11690 if (val & G_ALERTDISABLE) {
11691 val &= ~G_ALERTDISABLE;
11692 /* remember what action to perform by default, only if we don't click Cancel */
11693 if (val == G_ALERTALTERNATE)
11694 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11695 else if (val == G_ALERTOTHER)
11696 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11699 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11700 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11701 list_free_strings_full(list);
11703 } else if (val == G_ALERTOTHER) {
11704 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11705 list_free_strings_full(list);
11709 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11710 compose_insert_file(compose, (const gchar *)tmp->data);
11712 list_free_strings_full(list);
11713 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11718 static void compose_header_drag_received_cb (GtkWidget *widget,
11719 GdkDragContext *drag_context,
11722 GtkSelectionData *data,
11725 gpointer user_data)
11727 GtkEditable *entry = (GtkEditable *)user_data;
11728 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11730 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11733 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11734 gchar *decoded=g_new(gchar, strlen(email));
11737 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11738 gtk_editable_delete_text(entry, 0, -1);
11739 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11740 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11744 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11747 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11749 Compose *compose = (Compose *)data;
11751 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11752 compose->return_receipt = TRUE;
11754 compose->return_receipt = FALSE;
11757 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11759 Compose *compose = (Compose *)data;
11761 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11762 compose->remove_references = TRUE;
11764 compose->remove_references = FALSE;
11767 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11768 ComposeHeaderEntry *headerentry)
11770 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11771 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11772 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11776 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11777 GdkEventKey *event,
11778 ComposeHeaderEntry *headerentry)
11780 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11781 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11782 !(event->state & GDK_MODIFIER_MASK) &&
11783 (event->keyval == GDK_KEY_BackSpace) &&
11784 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11785 gtk_container_remove
11786 (GTK_CONTAINER(headerentry->compose->header_table),
11787 headerentry->combo);
11788 gtk_container_remove
11789 (GTK_CONTAINER(headerentry->compose->header_table),
11790 headerentry->entry);
11791 headerentry->compose->header_list =
11792 g_slist_remove(headerentry->compose->header_list,
11794 g_free(headerentry);
11795 } else if (event->keyval == GDK_KEY_Tab) {
11796 if (headerentry->compose->header_last == headerentry) {
11797 /* Override default next focus, and give it to subject_entry
11798 * instead of notebook tabs
11800 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11801 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11808 static gboolean scroll_postpone(gpointer data)
11810 Compose *compose = (Compose *)data;
11812 if (compose->batch)
11815 GTK_EVENTS_FLUSH();
11816 compose_show_first_last_header(compose, FALSE);
11820 static void compose_headerentry_changed_cb(GtkWidget *entry,
11821 ComposeHeaderEntry *headerentry)
11823 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11824 compose_create_header_entry(headerentry->compose);
11825 g_signal_handlers_disconnect_matched
11826 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11827 0, 0, NULL, NULL, headerentry);
11829 if (!headerentry->compose->batch)
11830 g_timeout_add(0, scroll_postpone, headerentry->compose);
11834 static gboolean compose_defer_auto_save_draft(Compose *compose)
11836 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11837 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11841 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11843 GtkAdjustment *vadj;
11845 cm_return_if_fail(compose);
11850 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11851 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11852 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11853 gtk_widget_get_parent(compose->header_table)));
11854 gtk_adjustment_set_value(vadj, (show_first ?
11855 gtk_adjustment_get_lower(vadj) :
11856 (gtk_adjustment_get_upper(vadj) -
11857 gtk_adjustment_get_page_size(vadj))));
11858 gtk_adjustment_changed(vadj);
11861 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11862 const gchar *text, gint len, Compose *compose)
11864 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11865 (G_OBJECT(compose->text), "paste_as_quotation"));
11868 cm_return_if_fail(text != NULL);
11870 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11871 G_CALLBACK(text_inserted),
11873 if (paste_as_quotation) {
11875 const gchar *qmark;
11877 GtkTextIter start_iter;
11880 len = strlen(text);
11882 new_text = g_strndup(text, len);
11884 qmark = compose_quote_char_from_context(compose);
11886 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11887 gtk_text_buffer_place_cursor(buffer, iter);
11889 pos = gtk_text_iter_get_offset(iter);
11891 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11892 _("Quote format error at line %d."));
11893 quote_fmt_reset_vartable();
11895 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11896 GINT_TO_POINTER(paste_as_quotation - 1));
11898 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11899 gtk_text_buffer_place_cursor(buffer, iter);
11900 gtk_text_buffer_delete_mark(buffer, mark);
11902 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11903 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11904 compose_beautify_paragraph(compose, &start_iter, FALSE);
11905 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11906 gtk_text_buffer_delete_mark(buffer, mark);
11908 if (strcmp(text, "\n") || compose->automatic_break
11909 || gtk_text_iter_starts_line(iter)) {
11910 GtkTextIter before_ins;
11911 gtk_text_buffer_insert(buffer, iter, text, len);
11912 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11913 before_ins = *iter;
11914 gtk_text_iter_backward_chars(&before_ins, len);
11915 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11918 /* check if the preceding is just whitespace or quote */
11919 GtkTextIter start_line;
11920 gchar *tmp = NULL, *quote = NULL;
11921 gint quote_len = 0, is_normal = 0;
11922 start_line = *iter;
11923 gtk_text_iter_set_line_offset(&start_line, 0);
11924 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11927 if (*tmp == '\0') {
11930 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11938 gtk_text_buffer_insert(buffer, iter, text, len);
11940 gtk_text_buffer_insert_with_tags_by_name(buffer,
11941 iter, text, len, "no_join", NULL);
11946 if (!paste_as_quotation) {
11947 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11948 compose_beautify_paragraph(compose, iter, FALSE);
11949 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11950 gtk_text_buffer_delete_mark(buffer, mark);
11953 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11954 G_CALLBACK(text_inserted),
11956 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11958 if (compose_can_autosave(compose) &&
11959 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11960 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11961 compose->draft_timeout_tag = g_timeout_add
11962 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11966 static void compose_check_all(GtkAction *action, gpointer data)
11968 Compose *compose = (Compose *)data;
11969 if (!compose->gtkaspell)
11972 if (gtk_widget_has_focus(compose->subject_entry))
11973 claws_spell_entry_check_all(
11974 CLAWS_SPELL_ENTRY(compose->subject_entry));
11976 gtkaspell_check_all(compose->gtkaspell);
11979 static void compose_highlight_all(GtkAction *action, gpointer data)
11981 Compose *compose = (Compose *)data;
11982 if (compose->gtkaspell) {
11983 claws_spell_entry_recheck_all(
11984 CLAWS_SPELL_ENTRY(compose->subject_entry));
11985 gtkaspell_highlight_all(compose->gtkaspell);
11989 static void compose_check_backwards(GtkAction *action, gpointer data)
11991 Compose *compose = (Compose *)data;
11992 if (!compose->gtkaspell) {
11993 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11997 if (gtk_widget_has_focus(compose->subject_entry))
11998 claws_spell_entry_check_backwards(
11999 CLAWS_SPELL_ENTRY(compose->subject_entry));
12001 gtkaspell_check_backwards(compose->gtkaspell);
12004 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12006 Compose *compose = (Compose *)data;
12007 if (!compose->gtkaspell) {
12008 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12012 if (gtk_widget_has_focus(compose->subject_entry))
12013 claws_spell_entry_check_forwards_go(
12014 CLAWS_SPELL_ENTRY(compose->subject_entry));
12016 gtkaspell_check_forwards_go(compose->gtkaspell);
12021 *\brief Guess originating forward account from MsgInfo and several
12022 * "common preference" settings. Return NULL if no guess.
12024 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12026 PrefsAccount *account = NULL;
12028 cm_return_val_if_fail(msginfo, NULL);
12029 cm_return_val_if_fail(msginfo->folder, NULL);
12030 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12032 if (msginfo->folder->prefs->enable_default_account)
12033 account = account_find_from_id(msginfo->folder->prefs->default_account);
12035 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12037 Xstrdup_a(to, msginfo->to, return NULL);
12038 extract_address(to);
12039 account = account_find_from_address(to, FALSE);
12042 if (!account && prefs_common.forward_account_autosel) {
12044 if (!procheader_get_header_from_msginfo
12045 (msginfo, &cc, "Cc:")) {
12046 gchar *buf = cc + strlen("Cc:");
12047 extract_address(buf);
12048 account = account_find_from_address(buf, FALSE);
12053 if (!account && prefs_common.forward_account_autosel) {
12054 gchar *deliveredto = NULL;
12055 if (!procheader_get_header_from_msginfo
12056 (msginfo, &deliveredto, "Delivered-To:")) {
12057 gchar *buf = deliveredto + strlen("Delivered-To:");
12058 extract_address(buf);
12059 account = account_find_from_address(buf, FALSE);
12060 g_free(deliveredto);
12065 account = msginfo->folder->folder->account;
12070 gboolean compose_close(Compose *compose)
12074 cm_return_val_if_fail(compose, FALSE);
12076 if (!g_mutex_trylock(compose->mutex)) {
12077 /* we have to wait for the (possibly deferred by auto-save)
12078 * drafting to be done, before destroying the compose under
12080 debug_print("waiting for drafting to finish...\n");
12081 compose_allow_user_actions(compose, FALSE);
12082 if (compose->close_timeout_tag == 0) {
12083 compose->close_timeout_tag =
12084 g_timeout_add (500, (GSourceFunc) compose_close,
12090 if (compose->draft_timeout_tag >= 0) {
12091 g_source_remove(compose->draft_timeout_tag);
12092 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12095 gtkut_widget_get_uposition(compose->window, &x, &y);
12096 if (!compose->batch) {
12097 prefs_common.compose_x = x;
12098 prefs_common.compose_y = y;
12100 g_mutex_unlock(compose->mutex);
12101 compose_destroy(compose);
12106 * Add entry field for each address in list.
12107 * \param compose E-Mail composition object.
12108 * \param listAddress List of (formatted) E-Mail addresses.
12110 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12113 node = listAddress;
12115 addr = ( gchar * ) node->data;
12116 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12117 node = g_list_next( node );
12121 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12122 guint action, gboolean opening_multiple)
12124 gchar *body = NULL;
12125 GSList *new_msglist = NULL;
12126 MsgInfo *tmp_msginfo = NULL;
12127 gboolean originally_enc = FALSE;
12128 gboolean originally_sig = FALSE;
12129 Compose *compose = NULL;
12130 gchar *s_system = NULL;
12132 cm_return_if_fail(msgview != NULL);
12134 cm_return_if_fail(msginfo_list != NULL);
12136 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12137 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12138 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12140 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12141 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12142 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12143 orig_msginfo, mimeinfo);
12144 if (tmp_msginfo != NULL) {
12145 new_msglist = g_slist_append(NULL, tmp_msginfo);
12147 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12148 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12149 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12151 tmp_msginfo->folder = orig_msginfo->folder;
12152 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12153 if (orig_msginfo->tags) {
12154 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12155 tmp_msginfo->folder->tags_dirty = TRUE;
12161 if (!opening_multiple)
12162 body = messageview_get_selection(msgview);
12165 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12166 procmsg_msginfo_free(&tmp_msginfo);
12167 g_slist_free(new_msglist);
12169 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12171 if (compose && originally_enc) {
12172 compose_force_encryption(compose, compose->account, FALSE, s_system);
12175 if (compose && originally_sig && compose->account->default_sign_reply) {
12176 compose_force_signing(compose, compose->account, s_system);
12180 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12183 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12186 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12187 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12188 GSList *cur = msginfo_list;
12189 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12190 "messages. Opening the windows "
12191 "could take some time. Do you "
12192 "want to continue?"),
12193 g_slist_length(msginfo_list));
12194 if (g_slist_length(msginfo_list) > 9
12195 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12196 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12201 /* We'll open multiple compose windows */
12202 /* let the WM place the next windows */
12203 compose_force_window_origin = FALSE;
12204 for (; cur; cur = cur->next) {
12206 tmplist.data = cur->data;
12207 tmplist.next = NULL;
12208 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12210 compose_force_window_origin = TRUE;
12212 /* forwarding multiple mails as attachments is done via a
12213 * single compose window */
12214 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12218 void compose_check_for_email_account(Compose *compose)
12220 PrefsAccount *ac = NULL, *curr = NULL;
12226 if (compose->account && compose->account->protocol == A_NNTP) {
12227 ac = account_get_cur_account();
12228 if (ac->protocol == A_NNTP) {
12229 list = account_get_list();
12231 for( ; list != NULL ; list = g_list_next(list)) {
12232 curr = (PrefsAccount *) list->data;
12233 if (curr->protocol != A_NNTP) {
12239 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12244 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12245 const gchar *address)
12247 GSList *msginfo_list = NULL;
12248 gchar *body = messageview_get_selection(msgview);
12251 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12253 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12254 compose_check_for_email_account(compose);
12255 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12256 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12257 compose_reply_set_subject(compose, msginfo);
12260 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12263 void compose_set_position(Compose *compose, gint pos)
12265 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12267 gtkut_text_view_set_position(text, pos);
12270 gboolean compose_search_string(Compose *compose,
12271 const gchar *str, gboolean case_sens)
12273 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12275 return gtkut_text_view_search_string(text, str, case_sens);
12278 gboolean compose_search_string_backward(Compose *compose,
12279 const gchar *str, gboolean case_sens)
12281 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12283 return gtkut_text_view_search_string_backward(text, str, case_sens);
12286 /* allocate a msginfo structure and populate its data from a compose data structure */
12287 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12289 MsgInfo *newmsginfo;
12291 gchar date[RFC822_DATE_BUFFSIZE];
12293 cm_return_val_if_fail( compose != NULL, NULL );
12295 newmsginfo = procmsg_msginfo_new();
12298 get_rfc822_date(date, sizeof(date));
12299 newmsginfo->date = g_strdup(date);
12302 if (compose->from_name) {
12303 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12304 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12308 if (compose->subject_entry)
12309 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12311 /* to, cc, reply-to, newsgroups */
12312 for (list = compose->header_list; list; list = list->next) {
12313 gchar *header = gtk_editable_get_chars(
12315 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12316 gchar *entry = gtk_editable_get_chars(
12317 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12319 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12320 if ( newmsginfo->to == NULL ) {
12321 newmsginfo->to = g_strdup(entry);
12322 } else if (entry && *entry) {
12323 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12324 g_free(newmsginfo->to);
12325 newmsginfo->to = tmp;
12328 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12329 if ( newmsginfo->cc == NULL ) {
12330 newmsginfo->cc = g_strdup(entry);
12331 } else if (entry && *entry) {
12332 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12333 g_free(newmsginfo->cc);
12334 newmsginfo->cc = tmp;
12337 if ( strcasecmp(header,
12338 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12339 if ( newmsginfo->newsgroups == NULL ) {
12340 newmsginfo->newsgroups = g_strdup(entry);
12341 } else if (entry && *entry) {
12342 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12343 g_free(newmsginfo->newsgroups);
12344 newmsginfo->newsgroups = tmp;
12352 /* other data is unset */
12358 /* update compose's dictionaries from folder dict settings */
12359 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12360 FolderItem *folder_item)
12362 cm_return_if_fail(compose != NULL);
12364 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12365 FolderItemPrefs *prefs = folder_item->prefs;
12367 if (prefs->enable_default_dictionary)
12368 gtkaspell_change_dict(compose->gtkaspell,
12369 prefs->default_dictionary, FALSE);
12370 if (folder_item->prefs->enable_default_alt_dictionary)
12371 gtkaspell_change_alt_dict(compose->gtkaspell,
12372 prefs->default_alt_dictionary);
12373 if (prefs->enable_default_dictionary
12374 || prefs->enable_default_alt_dictionary)
12375 compose_spell_menu_changed(compose);
12380 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12382 Compose *compose = (Compose *)data;
12384 cm_return_if_fail(compose != NULL);
12386 gtk_widget_grab_focus(compose->text);
12389 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12391 gtk_combo_box_popup(GTK_COMBO_BOX(data));