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 compose_activate_privacy_system (Compose *compose,
495 PrefsAccount *account,
497 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
498 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
500 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
502 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
503 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
504 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
506 static void compose_attach_drag_received_cb (GtkWidget *widget,
507 GdkDragContext *drag_context,
510 GtkSelectionData *data,
514 static void compose_insert_drag_received_cb (GtkWidget *widget,
515 GdkDragContext *drag_context,
518 GtkSelectionData *data,
522 static void compose_header_drag_received_cb (GtkWidget *widget,
523 GdkDragContext *drag_context,
526 GtkSelectionData *data,
531 static gboolean compose_drag_drop (GtkWidget *widget,
532 GdkDragContext *drag_context,
534 guint time, gpointer user_data);
535 static gboolean completion_set_focus_to_subject
540 static void text_inserted (GtkTextBuffer *buffer,
545 static Compose *compose_generic_reply(MsgInfo *msginfo,
546 ComposeQuoteMode quote_mode,
550 gboolean followup_and_reply_to,
553 static void compose_headerentry_changed_cb (GtkWidget *entry,
554 ComposeHeaderEntry *headerentry);
555 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
557 ComposeHeaderEntry *headerentry);
558 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
559 ComposeHeaderEntry *headerentry);
561 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
563 static void compose_allow_user_actions (Compose *compose, gboolean allow);
565 static void compose_nothing_cb (GtkAction *action, gpointer data)
571 static void compose_check_all (GtkAction *action, gpointer data);
572 static void compose_highlight_all (GtkAction *action, gpointer data);
573 static void compose_check_backwards (GtkAction *action, gpointer data);
574 static void compose_check_forwards_go (GtkAction *action, gpointer data);
577 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
579 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
582 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
583 FolderItem *folder_item);
585 static void compose_attach_update_label(Compose *compose);
586 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
587 gboolean respect_default_to);
588 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
589 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
591 static GtkActionEntry compose_popup_entries[] =
593 {"Compose", NULL, "Compose", NULL, NULL, NULL },
594 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
595 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
596 {"Compose/---", NULL, "---", NULL, NULL, NULL },
597 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
600 static GtkActionEntry compose_entries[] =
602 {"Menu", NULL, "Menu", NULL, NULL, NULL },
604 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
605 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
607 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
609 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
610 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
611 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
613 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
614 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
615 {"Message/---", NULL, "---", NULL, NULL, NULL },
617 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
618 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
619 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
620 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
621 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
622 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
623 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
624 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
625 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
626 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
629 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
630 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
631 {"Edit/---", NULL, "---", NULL, NULL, NULL },
633 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
634 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
635 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
637 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
638 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
639 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
640 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
642 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
644 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
645 {"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*/
646 {"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*/
647 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
648 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
649 {"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*/
650 {"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*/
651 {"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*/
652 {"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*/
653 {"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*/
654 {"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*/
655 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
656 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
657 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
658 {"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*/
660 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
661 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
663 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
664 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
665 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
666 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
667 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
670 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
671 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
672 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
673 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
675 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
676 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
680 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
681 {"Options/---", NULL, "---", NULL, NULL, NULL },
682 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
683 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
685 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
686 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
688 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
689 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
690 #define ENC_ACTION(cs_char,c_char,string) \
691 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
693 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
694 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
695 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
696 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
697 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
698 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
699 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
700 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
701 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
704 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
706 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
707 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
708 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
709 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
712 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
715 static GtkToggleActionEntry compose_toggle_entries[] =
717 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
718 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
719 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
720 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
721 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
722 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
723 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
726 static GtkRadioActionEntry compose_radio_rm_entries[] =
728 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
729 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
730 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
731 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
734 static GtkRadioActionEntry compose_radio_prio_entries[] =
736 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
738 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
739 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
740 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
743 static GtkRadioActionEntry compose_radio_enc_entries[] =
745 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
780 static GtkTargetEntry compose_mime_types[] =
782 {"text/uri-list", 0, 0},
783 {"UTF8_STRING", 0, 0},
787 static gboolean compose_put_existing_to_front(MsgInfo *info)
789 const GList *compose_list = compose_get_compose_list();
790 const GList *elem = NULL;
793 for (elem = compose_list; elem != NULL && elem->data != NULL;
795 Compose *c = (Compose*)elem->data;
797 if (!c->targetinfo || !c->targetinfo->msgid ||
801 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
802 gtkut_window_popup(c->window);
810 static GdkColor quote_color1 =
811 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
812 static GdkColor quote_color2 =
813 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_color3 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
817 static GdkColor quote_bgcolor1 =
818 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 static GdkColor quote_bgcolor2 =
820 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor quote_bgcolor3 =
822 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
824 static GdkColor signature_color = {
831 static GdkColor uri_color = {
838 static void compose_create_tags(GtkTextView *text, Compose *compose)
840 GtkTextBuffer *buffer;
841 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
843 buffer = gtk_text_view_get_buffer(text);
845 if (prefs_common.enable_color) {
846 /* grab the quote colors, converting from an int to a GdkColor */
847 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
849 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
851 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
853 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
855 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
857 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
859 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
861 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
864 signature_color = quote_color1 = quote_color2 = quote_color3 =
865 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
868 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
869 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
870 "foreground-gdk", "e_color1,
871 "paragraph-background-gdk", "e_bgcolor1,
873 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
874 "foreground-gdk", "e_color2,
875 "paragraph-background-gdk", "e_bgcolor2,
877 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
878 "foreground-gdk", "e_color3,
879 "paragraph-background-gdk", "e_bgcolor3,
882 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
883 "foreground-gdk", "e_color1,
885 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
886 "foreground-gdk", "e_color2,
888 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
889 "foreground-gdk", "e_color3,
893 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
894 "foreground-gdk", &signature_color,
897 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
898 "foreground-gdk", &uri_color,
900 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
901 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
904 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
907 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
910 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
912 return compose_generic_new(account, mailto, item, NULL, NULL);
915 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
917 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
920 #define SCROLL_TO_CURSOR(compose) { \
921 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
922 gtk_text_view_get_buffer( \
923 GTK_TEXT_VIEW(compose->text))); \
924 gtk_text_view_scroll_mark_onscreen( \
925 GTK_TEXT_VIEW(compose->text), \
929 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
932 if (folderidentifier) {
933 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
934 prefs_common.compose_save_to_history = add_history(
935 prefs_common.compose_save_to_history, folderidentifier);
936 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
937 prefs_common.compose_save_to_history);
940 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
941 if (folderidentifier)
942 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
944 gtk_entry_set_text(GTK_ENTRY(entry), "");
947 static gchar *compose_get_save_to(Compose *compose)
950 gchar *result = NULL;
951 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
952 result = gtk_editable_get_chars(entry, 0, -1);
955 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
956 prefs_common.compose_save_to_history = add_history(
957 prefs_common.compose_save_to_history, result);
958 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
959 prefs_common.compose_save_to_history);
964 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
965 GList *attach_files, GList *listAddress )
968 GtkTextView *textview;
969 GtkTextBuffer *textbuf;
971 const gchar *subject_format = NULL;
972 const gchar *body_format = NULL;
973 gchar *mailto_from = NULL;
974 PrefsAccount *mailto_account = NULL;
975 MsgInfo* dummyinfo = NULL;
976 gint cursor_pos = -1;
977 MailField mfield = NO_FIELD_PRESENT;
981 /* check if mailto defines a from */
982 if (mailto && *mailto != '\0') {
983 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
984 /* mailto defines a from, check if we can get account prefs from it,
985 if not, the account prefs will be guessed using other ways, but we'll keep
988 mailto_account = account_find_from_address(mailto_from, TRUE);
989 if (mailto_account == NULL) {
991 Xstrdup_a(tmp_from, mailto_from, return NULL);
992 extract_address(tmp_from);
993 mailto_account = account_find_from_address(tmp_from, TRUE);
997 account = mailto_account;
1000 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1001 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1002 account = account_find_from_id(item->prefs->default_account);
1004 /* if no account prefs set, fallback to the current one */
1005 if (!account) account = cur_account;
1006 cm_return_val_if_fail(account != NULL, NULL);
1008 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1009 compose_apply_folder_privacy_settings(compose, item);
1011 /* override from name if mailto asked for it */
1013 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1014 g_free(mailto_from);
1016 /* override from name according to folder properties */
1017 if (item && item->prefs &&
1018 item->prefs->compose_with_format &&
1019 item->prefs->compose_override_from_format &&
1020 *item->prefs->compose_override_from_format != '\0') {
1025 dummyinfo = compose_msginfo_new_from_compose(compose);
1027 /* decode \-escape sequences in the internal representation of the quote format */
1028 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1029 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1032 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1033 compose->gtkaspell);
1035 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1037 quote_fmt_scan_string(tmp);
1040 buf = quote_fmt_get_buffer();
1042 alertpanel_error(_("New message From format error."));
1044 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1045 quote_fmt_reset_vartable();
1046 quote_fmtlex_destroy();
1051 compose->replyinfo = NULL;
1052 compose->fwdinfo = NULL;
1054 textview = GTK_TEXT_VIEW(compose->text);
1055 textbuf = gtk_text_view_get_buffer(textview);
1056 compose_create_tags(textview, compose);
1058 undo_block(compose->undostruct);
1060 compose_set_dictionaries_from_folder_prefs(compose, item);
1063 if (account->auto_sig)
1064 compose_insert_sig(compose, FALSE);
1065 gtk_text_buffer_get_start_iter(textbuf, &iter);
1066 gtk_text_buffer_place_cursor(textbuf, &iter);
1068 if (account->protocol != A_NNTP) {
1069 if (mailto && *mailto != '\0') {
1070 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1073 compose_set_folder_prefs(compose, item, TRUE);
1075 if (item && item->ret_rcpt) {
1076 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1079 if (mailto && *mailto != '\0') {
1080 if (!strchr(mailto, '@'))
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1083 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1084 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1085 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1086 mfield = TO_FIELD_PRESENT;
1089 * CLAWS: just don't allow return receipt request, even if the user
1090 * may want to send an email. simple but foolproof.
1092 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1094 compose_add_field_list( compose, listAddress );
1096 if (item && item->prefs && item->prefs->compose_with_format) {
1097 subject_format = item->prefs->compose_subject_format;
1098 body_format = item->prefs->compose_body_format;
1099 } else if (account->compose_with_format) {
1100 subject_format = account->compose_subject_format;
1101 body_format = account->compose_body_format;
1102 } else if (prefs_common.compose_with_format) {
1103 subject_format = prefs_common.compose_subject_format;
1104 body_format = prefs_common.compose_body_format;
1107 if (subject_format || body_format) {
1110 && *subject_format != '\0' )
1112 gchar *subject = NULL;
1117 dummyinfo = compose_msginfo_new_from_compose(compose);
1119 /* decode \-escape sequences in the internal representation of the quote format */
1120 tmp = g_malloc(strlen(subject_format)+1);
1121 pref_get_unescaped_pref(tmp, subject_format);
1123 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1125 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1126 compose->gtkaspell);
1128 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1130 quote_fmt_scan_string(tmp);
1133 buf = quote_fmt_get_buffer();
1135 alertpanel_error(_("New message subject format error."));
1137 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1138 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1139 quote_fmt_reset_vartable();
1140 quote_fmtlex_destroy();
1144 mfield = SUBJECT_FIELD_PRESENT;
1148 && *body_format != '\0' )
1151 GtkTextBuffer *buffer;
1152 GtkTextIter start, end;
1156 dummyinfo = compose_msginfo_new_from_compose(compose);
1158 text = GTK_TEXT_VIEW(compose->text);
1159 buffer = gtk_text_view_get_buffer(text);
1160 gtk_text_buffer_get_start_iter(buffer, &start);
1161 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1162 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1164 compose_quote_fmt(compose, dummyinfo,
1166 NULL, tmp, FALSE, TRUE,
1167 _("The body of the \"New message\" template has an error at line %d."));
1168 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1169 quote_fmt_reset_vartable();
1173 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1174 gtkaspell_highlight_all(compose->gtkaspell);
1176 mfield = BODY_FIELD_PRESENT;
1180 procmsg_msginfo_free( &dummyinfo );
1186 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1187 ainfo = (AttachInfo *) curr->data;
1189 compose_insert_file(compose, ainfo->file);
1191 compose_attach_append(compose, ainfo->file, ainfo->file,
1192 ainfo->content_type, ainfo->charset);
1196 compose_show_first_last_header(compose, TRUE);
1198 /* Set save folder */
1199 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1200 gchar *folderidentifier;
1202 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1203 folderidentifier = folder_item_get_identifier(item);
1204 compose_set_save_to(compose, folderidentifier);
1205 g_free(folderidentifier);
1208 /* Place cursor according to provided input (mfield) */
1210 case NO_FIELD_PRESENT:
1211 if (compose->header_last)
1212 gtk_widget_grab_focus(compose->header_last->entry);
1214 case TO_FIELD_PRESENT:
1215 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1217 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1220 gtk_widget_grab_focus(compose->subject_entry);
1222 case SUBJECT_FIELD_PRESENT:
1223 textview = GTK_TEXT_VIEW(compose->text);
1226 textbuf = gtk_text_view_get_buffer(textview);
1229 mark = gtk_text_buffer_get_insert(textbuf);
1230 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1231 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1233 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1234 * only defers where it comes to the variable body
1235 * is not null. If no body is present compose->text
1236 * will be null in which case you cannot place the
1237 * cursor inside the component so. An empty component
1238 * is therefore created before placing the cursor
1240 case BODY_FIELD_PRESENT:
1241 cursor_pos = quote_fmt_get_cursor_pos();
1242 if (cursor_pos == -1)
1243 gtk_widget_grab_focus(compose->header_last->entry);
1245 gtk_widget_grab_focus(compose->text);
1249 undo_unblock(compose->undostruct);
1251 if (prefs_common.auto_exteditor)
1252 compose_exec_ext_editor(compose);
1254 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1256 SCROLL_TO_CURSOR(compose);
1258 compose->modified = FALSE;
1259 compose_set_title(compose);
1261 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1266 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1267 gboolean override_pref, const gchar *system)
1269 const gchar *privacy = NULL;
1271 cm_return_if_fail(compose != NULL);
1272 cm_return_if_fail(account != NULL);
1274 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1277 if (account->default_privacy_system && strlen(account->default_privacy_system))
1278 privacy = account->default_privacy_system;
1282 GSList *privacy_avail = privacy_get_system_ids();
1283 if (privacy_avail && g_slist_length(privacy_avail)) {
1284 privacy = (gchar *)(privacy_avail->data);
1286 g_slist_free_full(privacy_avail, g_free);
1288 if (privacy != NULL) {
1290 g_free(compose->privacy_system);
1291 compose->privacy_system = NULL;
1292 g_free(compose->encdata);
1293 compose->encdata = NULL;
1295 if (compose->privacy_system == NULL)
1296 compose->privacy_system = g_strdup(privacy);
1297 else if (*(compose->privacy_system) == '\0') {
1298 g_free(compose->privacy_system);
1299 g_free(compose->encdata);
1300 compose->encdata = NULL;
1301 compose->privacy_system = g_strdup(privacy);
1303 compose_update_privacy_system_menu_item(compose, FALSE);
1304 compose_use_encryption(compose, TRUE);
1308 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1310 const gchar *privacy = NULL;
1312 if (account->default_privacy_system && strlen(account->default_privacy_system))
1313 privacy = account->default_privacy_system;
1317 GSList *privacy_avail = privacy_get_system_ids();
1318 if (privacy_avail && g_slist_length(privacy_avail)) {
1319 privacy = (gchar *)(privacy_avail->data);
1323 if (privacy != NULL) {
1325 g_free(compose->privacy_system);
1326 compose->privacy_system = NULL;
1327 g_free(compose->encdata);
1328 compose->encdata = NULL;
1330 if (compose->privacy_system == NULL)
1331 compose->privacy_system = g_strdup(privacy);
1332 compose_update_privacy_system_menu_item(compose, FALSE);
1333 compose_use_signing(compose, TRUE);
1337 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1341 Compose *compose = NULL;
1343 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1345 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1346 cm_return_val_if_fail(msginfo != NULL, NULL);
1348 list_len = g_slist_length(msginfo_list);
1352 case COMPOSE_REPLY_TO_ADDRESS:
1353 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1354 FALSE, prefs_common.default_reply_list, FALSE, body);
1356 case COMPOSE_REPLY_WITH_QUOTE:
1357 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1358 FALSE, prefs_common.default_reply_list, FALSE, body);
1360 case COMPOSE_REPLY_WITHOUT_QUOTE:
1361 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1362 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1364 case COMPOSE_REPLY_TO_SENDER:
1365 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1366 FALSE, FALSE, TRUE, body);
1368 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1369 compose = compose_followup_and_reply_to(msginfo,
1370 COMPOSE_QUOTE_CHECK,
1371 FALSE, FALSE, body);
1373 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1375 FALSE, FALSE, TRUE, body);
1377 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1379 FALSE, FALSE, TRUE, NULL);
1381 case COMPOSE_REPLY_TO_ALL:
1382 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1383 TRUE, FALSE, FALSE, body);
1385 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1386 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1387 TRUE, FALSE, FALSE, body);
1389 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1391 TRUE, FALSE, FALSE, NULL);
1393 case COMPOSE_REPLY_TO_LIST:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1395 FALSE, TRUE, FALSE, body);
1397 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1399 FALSE, TRUE, FALSE, body);
1401 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1403 FALSE, TRUE, FALSE, NULL);
1405 case COMPOSE_FORWARD:
1406 if (prefs_common.forward_as_attachment) {
1407 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1410 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1414 case COMPOSE_FORWARD_INLINE:
1415 /* check if we reply to more than one Message */
1416 if (list_len == 1) {
1417 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1420 /* more messages FALL THROUGH */
1421 case COMPOSE_FORWARD_AS_ATTACH:
1422 compose = compose_forward_multiple(NULL, msginfo_list);
1424 case COMPOSE_REDIRECT:
1425 compose = compose_redirect(NULL, msginfo, FALSE);
1428 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1431 if (compose == NULL) {
1432 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1436 compose->rmode = mode;
1437 switch (compose->rmode) {
1439 case COMPOSE_REPLY_WITH_QUOTE:
1440 case COMPOSE_REPLY_WITHOUT_QUOTE:
1441 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1442 debug_print("reply mode Normal\n");
1443 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1444 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1446 case COMPOSE_REPLY_TO_SENDER:
1447 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1448 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1449 debug_print("reply mode Sender\n");
1450 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1452 case COMPOSE_REPLY_TO_ALL:
1453 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1454 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1455 debug_print("reply mode All\n");
1456 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1458 case COMPOSE_REPLY_TO_LIST:
1459 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1460 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1461 debug_print("reply mode List\n");
1462 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1464 case COMPOSE_REPLY_TO_ADDRESS:
1465 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1473 static Compose *compose_reply(MsgInfo *msginfo,
1474 ComposeQuoteMode quote_mode,
1480 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1481 to_sender, FALSE, body);
1484 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1485 ComposeQuoteMode quote_mode,
1490 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1491 to_sender, TRUE, body);
1494 static void compose_extract_original_charset(Compose *compose)
1496 MsgInfo *info = NULL;
1497 if (compose->replyinfo) {
1498 info = compose->replyinfo;
1499 } else if (compose->fwdinfo) {
1500 info = compose->fwdinfo;
1501 } else if (compose->targetinfo) {
1502 info = compose->targetinfo;
1505 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1506 MimeInfo *partinfo = mimeinfo;
1507 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1508 partinfo = procmime_mimeinfo_next(partinfo);
1510 compose->orig_charset =
1511 g_strdup(procmime_mimeinfo_get_parameter(
1512 partinfo, "charset"));
1514 procmime_mimeinfo_free_all(&mimeinfo);
1518 #define SIGNAL_BLOCK(buffer) { \
1519 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1520 G_CALLBACK(compose_changed_cb), \
1522 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1523 G_CALLBACK(text_inserted), \
1527 #define SIGNAL_UNBLOCK(buffer) { \
1528 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1529 G_CALLBACK(compose_changed_cb), \
1531 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1532 G_CALLBACK(text_inserted), \
1536 static Compose *compose_generic_reply(MsgInfo *msginfo,
1537 ComposeQuoteMode quote_mode,
1538 gboolean to_all, gboolean to_ml,
1540 gboolean followup_and_reply_to,
1544 PrefsAccount *account = NULL;
1545 GtkTextView *textview;
1546 GtkTextBuffer *textbuf;
1547 gboolean quote = FALSE;
1548 const gchar *qmark = NULL;
1549 const gchar *body_fmt = NULL;
1550 gchar *s_system = NULL;
1552 cm_return_val_if_fail(msginfo != NULL, NULL);
1553 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1555 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1557 cm_return_val_if_fail(account != NULL, NULL);
1559 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1560 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1562 compose->updating = TRUE;
1564 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1567 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1568 if (!compose->replyinfo)
1569 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1571 compose_extract_original_charset(compose);
1573 if (msginfo->folder && msginfo->folder->ret_rcpt)
1574 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1576 /* Set save folder */
1577 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1578 gchar *folderidentifier;
1580 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1581 folderidentifier = folder_item_get_identifier(msginfo->folder);
1582 compose_set_save_to(compose, folderidentifier);
1583 g_free(folderidentifier);
1586 if (compose_parse_header(compose, msginfo) < 0) {
1587 compose->updating = FALSE;
1588 compose_destroy(compose);
1592 /* override from name according to folder properties */
1593 if (msginfo->folder && msginfo->folder->prefs &&
1594 msginfo->folder->prefs->reply_with_format &&
1595 msginfo->folder->prefs->reply_override_from_format &&
1596 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1601 /* decode \-escape sequences in the internal representation of the quote format */
1602 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1603 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1606 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1607 compose->gtkaspell);
1609 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1611 quote_fmt_scan_string(tmp);
1614 buf = quote_fmt_get_buffer();
1616 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1618 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1619 quote_fmt_reset_vartable();
1620 quote_fmtlex_destroy();
1625 textview = (GTK_TEXT_VIEW(compose->text));
1626 textbuf = gtk_text_view_get_buffer(textview);
1627 compose_create_tags(textview, compose);
1629 undo_block(compose->undostruct);
1631 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1632 gtkaspell_block_check(compose->gtkaspell);
1635 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1636 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1637 /* use the reply format of folder (if enabled), or the account's one
1638 (if enabled) or fallback to the global reply format, which is always
1639 enabled (even if empty), and use the relevant quotemark */
1641 if (msginfo->folder && msginfo->folder->prefs &&
1642 msginfo->folder->prefs->reply_with_format) {
1643 qmark = msginfo->folder->prefs->reply_quotemark;
1644 body_fmt = msginfo->folder->prefs->reply_body_format;
1646 } else if (account->reply_with_format) {
1647 qmark = account->reply_quotemark;
1648 body_fmt = account->reply_body_format;
1651 qmark = prefs_common.quotemark;
1652 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1653 body_fmt = gettext(prefs_common.quotefmt);
1660 /* empty quotemark is not allowed */
1661 if (qmark == NULL || *qmark == '\0')
1663 compose_quote_fmt(compose, compose->replyinfo,
1664 body_fmt, qmark, body, FALSE, TRUE,
1665 _("The body of the \"Reply\" template has an error at line %d."));
1666 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1667 quote_fmt_reset_vartable();
1670 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1671 compose_force_encryption(compose, account, FALSE, s_system);
1674 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1675 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1676 compose_force_signing(compose, account, s_system);
1680 SIGNAL_BLOCK(textbuf);
1682 if (account->auto_sig)
1683 compose_insert_sig(compose, FALSE);
1685 compose_wrap_all(compose);
1688 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1689 gtkaspell_highlight_all(compose->gtkaspell);
1690 gtkaspell_unblock_check(compose->gtkaspell);
1692 SIGNAL_UNBLOCK(textbuf);
1694 gtk_widget_grab_focus(compose->text);
1696 undo_unblock(compose->undostruct);
1698 if (prefs_common.auto_exteditor)
1699 compose_exec_ext_editor(compose);
1701 compose->modified = FALSE;
1702 compose_set_title(compose);
1704 compose->updating = FALSE;
1705 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1706 SCROLL_TO_CURSOR(compose);
1708 if (compose->deferred_destroy) {
1709 compose_destroy(compose);
1717 #define INSERT_FW_HEADER(var, hdr) \
1718 if (msginfo->var && *msginfo->var) { \
1719 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1720 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1721 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1724 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1725 gboolean as_attach, const gchar *body,
1726 gboolean no_extedit,
1730 GtkTextView *textview;
1731 GtkTextBuffer *textbuf;
1732 gint cursor_pos = -1;
1735 cm_return_val_if_fail(msginfo != NULL, NULL);
1736 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1738 if (!account && !(account = compose_find_account(msginfo)))
1739 account = cur_account;
1741 if (!prefs_common.forward_as_attachment)
1742 mode = COMPOSE_FORWARD_INLINE;
1744 mode = COMPOSE_FORWARD;
1745 compose = compose_create(account, msginfo->folder, mode, batch);
1746 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1748 compose->updating = TRUE;
1749 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1750 if (!compose->fwdinfo)
1751 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1753 compose_extract_original_charset(compose);
1755 if (msginfo->subject && *msginfo->subject) {
1756 gchar *buf, *buf2, *p;
1758 buf = p = g_strdup(msginfo->subject);
1759 p += subject_get_prefix_length(p);
1760 memmove(buf, p, strlen(p) + 1);
1762 buf2 = g_strdup_printf("Fw: %s", buf);
1763 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1769 /* override from name according to folder properties */
1770 if (msginfo->folder && msginfo->folder->prefs &&
1771 msginfo->folder->prefs->forward_with_format &&
1772 msginfo->folder->prefs->forward_override_from_format &&
1773 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1777 MsgInfo *full_msginfo = NULL;
1780 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1782 full_msginfo = procmsg_msginfo_copy(msginfo);
1784 /* decode \-escape sequences in the internal representation of the quote format */
1785 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1786 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1789 gtkaspell_block_check(compose->gtkaspell);
1790 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1791 compose->gtkaspell);
1793 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1795 quote_fmt_scan_string(tmp);
1798 buf = quote_fmt_get_buffer();
1800 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1802 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1803 quote_fmt_reset_vartable();
1804 quote_fmtlex_destroy();
1807 procmsg_msginfo_free(&full_msginfo);
1810 textview = GTK_TEXT_VIEW(compose->text);
1811 textbuf = gtk_text_view_get_buffer(textview);
1812 compose_create_tags(textview, compose);
1814 undo_block(compose->undostruct);
1818 msgfile = procmsg_get_message_file(msginfo);
1819 if (!is_file_exist(msgfile))
1820 g_warning("%s: file does not exist", msgfile);
1822 compose_attach_append(compose, msgfile, msgfile,
1823 "message/rfc822", NULL);
1827 const gchar *qmark = NULL;
1828 const gchar *body_fmt = NULL;
1829 MsgInfo *full_msginfo;
1831 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1833 full_msginfo = procmsg_msginfo_copy(msginfo);
1835 /* use the forward format of folder (if enabled), or the account's one
1836 (if enabled) or fallback to the global forward format, which is always
1837 enabled (even if empty), and use the relevant quotemark */
1838 if (msginfo->folder && msginfo->folder->prefs &&
1839 msginfo->folder->prefs->forward_with_format) {
1840 qmark = msginfo->folder->prefs->forward_quotemark;
1841 body_fmt = msginfo->folder->prefs->forward_body_format;
1843 } else if (account->forward_with_format) {
1844 qmark = account->forward_quotemark;
1845 body_fmt = account->forward_body_format;
1848 qmark = prefs_common.fw_quotemark;
1849 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1850 body_fmt = gettext(prefs_common.fw_quotefmt);
1855 /* empty quotemark is not allowed */
1856 if (qmark == NULL || *qmark == '\0')
1859 compose_quote_fmt(compose, full_msginfo,
1860 body_fmt, qmark, body, FALSE, TRUE,
1861 _("The body of the \"Forward\" template has an error at line %d."));
1862 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1863 quote_fmt_reset_vartable();
1864 compose_attach_parts(compose, msginfo);
1866 procmsg_msginfo_free(&full_msginfo);
1869 SIGNAL_BLOCK(textbuf);
1871 if (account->auto_sig)
1872 compose_insert_sig(compose, FALSE);
1874 compose_wrap_all(compose);
1877 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1878 gtkaspell_highlight_all(compose->gtkaspell);
1879 gtkaspell_unblock_check(compose->gtkaspell);
1881 SIGNAL_UNBLOCK(textbuf);
1883 cursor_pos = quote_fmt_get_cursor_pos();
1884 if (cursor_pos == -1)
1885 gtk_widget_grab_focus(compose->header_last->entry);
1887 gtk_widget_grab_focus(compose->text);
1889 if (!no_extedit && prefs_common.auto_exteditor)
1890 compose_exec_ext_editor(compose);
1893 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1894 gchar *folderidentifier;
1896 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1897 folderidentifier = folder_item_get_identifier(msginfo->folder);
1898 compose_set_save_to(compose, folderidentifier);
1899 g_free(folderidentifier);
1902 undo_unblock(compose->undostruct);
1904 compose->modified = FALSE;
1905 compose_set_title(compose);
1907 compose->updating = FALSE;
1908 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1909 SCROLL_TO_CURSOR(compose);
1911 if (compose->deferred_destroy) {
1912 compose_destroy(compose);
1916 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1921 #undef INSERT_FW_HEADER
1923 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1926 GtkTextView *textview;
1927 GtkTextBuffer *textbuf;
1931 gboolean single_mail = TRUE;
1933 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1935 if (g_slist_length(msginfo_list) > 1)
1936 single_mail = FALSE;
1938 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1939 if (((MsgInfo *)msginfo->data)->folder == NULL)
1942 /* guess account from first selected message */
1944 !(account = compose_find_account(msginfo_list->data)))
1945 account = cur_account;
1947 cm_return_val_if_fail(account != NULL, NULL);
1949 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1950 if (msginfo->data) {
1951 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1952 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1956 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1957 g_warning("no msginfo_list");
1961 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1962 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1964 compose->updating = TRUE;
1966 /* override from name according to folder properties */
1967 if (msginfo_list->data) {
1968 MsgInfo *msginfo = msginfo_list->data;
1970 if (msginfo->folder && msginfo->folder->prefs &&
1971 msginfo->folder->prefs->forward_with_format &&
1972 msginfo->folder->prefs->forward_override_from_format &&
1973 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1978 /* decode \-escape sequences in the internal representation of the quote format */
1979 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1980 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1983 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1984 compose->gtkaspell);
1986 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1988 quote_fmt_scan_string(tmp);
1991 buf = quote_fmt_get_buffer();
1993 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1995 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1996 quote_fmt_reset_vartable();
1997 quote_fmtlex_destroy();
2003 textview = GTK_TEXT_VIEW(compose->text);
2004 textbuf = gtk_text_view_get_buffer(textview);
2005 compose_create_tags(textview, compose);
2007 undo_block(compose->undostruct);
2008 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2009 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2011 if (!is_file_exist(msgfile))
2012 g_warning("%s: file does not exist", msgfile);
2014 compose_attach_append(compose, msgfile, msgfile,
2015 "message/rfc822", NULL);
2020 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2021 if (info->subject && *info->subject) {
2022 gchar *buf, *buf2, *p;
2024 buf = p = g_strdup(info->subject);
2025 p += subject_get_prefix_length(p);
2026 memmove(buf, p, strlen(p) + 1);
2028 buf2 = g_strdup_printf("Fw: %s", buf);
2029 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2035 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2036 _("Fw: multiple emails"));
2039 SIGNAL_BLOCK(textbuf);
2041 if (account->auto_sig)
2042 compose_insert_sig(compose, FALSE);
2044 compose_wrap_all(compose);
2046 SIGNAL_UNBLOCK(textbuf);
2048 gtk_text_buffer_get_start_iter(textbuf, &iter);
2049 gtk_text_buffer_place_cursor(textbuf, &iter);
2051 if (prefs_common.auto_exteditor)
2052 compose_exec_ext_editor(compose);
2054 gtk_widget_grab_focus(compose->header_last->entry);
2055 undo_unblock(compose->undostruct);
2056 compose->modified = FALSE;
2057 compose_set_title(compose);
2059 compose->updating = FALSE;
2060 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2061 SCROLL_TO_CURSOR(compose);
2063 if (compose->deferred_destroy) {
2064 compose_destroy(compose);
2068 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2073 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2075 GtkTextIter start = *iter;
2076 GtkTextIter end_iter;
2077 int start_pos = gtk_text_iter_get_offset(&start);
2079 if (!compose->account->sig_sep)
2082 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2083 start_pos+strlen(compose->account->sig_sep));
2085 /* check sig separator */
2086 str = gtk_text_iter_get_text(&start, &end_iter);
2087 if (!strcmp(str, compose->account->sig_sep)) {
2089 /* check end of line (\n) */
2090 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2091 start_pos+strlen(compose->account->sig_sep));
2092 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2093 start_pos+strlen(compose->account->sig_sep)+1);
2094 tmp = gtk_text_iter_get_text(&start, &end_iter);
2095 if (!strcmp(tmp,"\n")) {
2107 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2109 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2110 Compose *compose = (Compose *)data;
2111 FolderItem *old_item = NULL;
2112 FolderItem *new_item = NULL;
2113 gchar *old_id, *new_id;
2115 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2116 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2119 old_item = hookdata->item;
2120 new_item = hookdata->item2;
2122 old_id = folder_item_get_identifier(old_item);
2123 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2125 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2126 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2127 compose->targetinfo->folder = new_item;
2130 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2131 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->replyinfo->folder = new_item;
2135 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2136 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2137 compose->fwdinfo->folder = new_item;
2145 static void compose_colorize_signature(Compose *compose)
2147 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2149 GtkTextIter end_iter;
2150 gtk_text_buffer_get_start_iter(buffer, &iter);
2151 while (gtk_text_iter_forward_line(&iter))
2152 if (compose_is_sig_separator(compose, buffer, &iter)) {
2153 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2154 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2158 #define BLOCK_WRAP() { \
2159 prev_autowrap = compose->autowrap; \
2160 buffer = gtk_text_view_get_buffer( \
2161 GTK_TEXT_VIEW(compose->text)); \
2162 compose->autowrap = FALSE; \
2164 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2165 G_CALLBACK(compose_changed_cb), \
2167 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2168 G_CALLBACK(text_inserted), \
2171 #define UNBLOCK_WRAP() { \
2172 compose->autowrap = prev_autowrap; \
2173 if (compose->autowrap) { \
2174 gint old = compose->draft_timeout_tag; \
2175 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2176 compose_wrap_all(compose); \
2177 compose->draft_timeout_tag = old; \
2180 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2181 G_CALLBACK(compose_changed_cb), \
2183 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2184 G_CALLBACK(text_inserted), \
2188 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2190 Compose *compose = NULL;
2191 PrefsAccount *account = NULL;
2192 GtkTextView *textview;
2193 GtkTextBuffer *textbuf;
2197 gboolean use_signing = FALSE;
2198 gboolean use_encryption = FALSE;
2199 gchar *privacy_system = NULL;
2200 int priority = PRIORITY_NORMAL;
2201 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2202 gboolean autowrap = prefs_common.autowrap;
2203 gboolean autoindent = prefs_common.auto_indent;
2204 HeaderEntry *manual_headers = NULL;
2206 cm_return_val_if_fail(msginfo != NULL, NULL);
2207 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2209 if (compose_put_existing_to_front(msginfo)) {
2213 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2214 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2215 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2216 gchar *queueheader_buf = NULL;
2219 /* Select Account from queue headers */
2220 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2221 "X-Claws-Account-Id:")) {
2222 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2223 account = account_find_from_id(id);
2224 g_free(queueheader_buf);
2226 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2227 "X-Sylpheed-Account-Id:")) {
2228 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2229 account = account_find_from_id(id);
2230 g_free(queueheader_buf);
2232 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2234 id = atoi(&queueheader_buf[strlen("NAID:")]);
2235 account = account_find_from_id(id);
2236 g_free(queueheader_buf);
2238 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2240 id = atoi(&queueheader_buf[strlen("MAID:")]);
2241 account = account_find_from_id(id);
2242 g_free(queueheader_buf);
2244 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2246 account = account_find_from_address(queueheader_buf, FALSE);
2247 g_free(queueheader_buf);
2249 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2251 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2252 use_signing = param;
2253 g_free(queueheader_buf);
2255 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2256 "X-Sylpheed-Sign:")) {
2257 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2258 use_signing = param;
2259 g_free(queueheader_buf);
2261 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2262 "X-Claws-Encrypt:")) {
2263 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2264 use_encryption = param;
2265 g_free(queueheader_buf);
2267 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2268 "X-Sylpheed-Encrypt:")) {
2269 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2270 use_encryption = param;
2271 g_free(queueheader_buf);
2273 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2274 "X-Claws-Auto-Wrapping:")) {
2275 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2277 g_free(queueheader_buf);
2279 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2280 "X-Claws-Auto-Indent:")) {
2281 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2283 g_free(queueheader_buf);
2285 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2286 "X-Claws-Privacy-System:")) {
2287 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Sylpheed-Privacy-System:")) {
2292 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2293 g_free(queueheader_buf);
2295 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2299 g_free(queueheader_buf);
2301 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2304 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2305 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2306 if (orig_item != NULL) {
2307 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2311 g_free(queueheader_buf);
2313 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2315 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2316 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2317 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2318 if (orig_item != NULL) {
2319 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2323 g_free(queueheader_buf);
2325 /* Get manual headers */
2326 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2327 "X-Claws-Manual-Headers:")) {
2328 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2329 if (listmh && *listmh != '\0') {
2330 debug_print("Got manual headers: %s\n", listmh);
2331 manual_headers = procheader_entries_from_str(listmh);
2334 g_free(queueheader_buf);
2337 account = msginfo->folder->folder->account;
2340 if (!account && prefs_common.reedit_account_autosel) {
2342 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2343 extract_address(from);
2344 account = account_find_from_address(from, FALSE);
2349 account = cur_account;
2351 cm_return_val_if_fail(account != NULL, NULL);
2353 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2355 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2356 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2357 compose->autowrap = autowrap;
2358 compose->replyinfo = replyinfo;
2359 compose->fwdinfo = fwdinfo;
2361 compose->updating = TRUE;
2362 compose->priority = priority;
2364 if (privacy_system != NULL) {
2365 compose->privacy_system = privacy_system;
2366 compose_use_signing(compose, use_signing);
2367 compose_use_encryption(compose, use_encryption);
2368 compose_update_privacy_system_menu_item(compose, FALSE);
2370 compose_activate_privacy_system(compose, account, FALSE);
2372 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2374 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2375 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2377 compose_extract_original_charset(compose);
2379 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2380 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2381 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2382 gchar *queueheader_buf = NULL;
2384 /* Set message save folder */
2385 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2386 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2387 compose_set_save_to(compose, &queueheader_buf[4]);
2388 g_free(queueheader_buf);
2390 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2391 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2393 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2395 g_free(queueheader_buf);
2399 if (compose_parse_header(compose, msginfo) < 0) {
2400 compose->updating = FALSE;
2401 compose_destroy(compose);
2404 compose_reedit_set_entry(compose, msginfo);
2406 textview = GTK_TEXT_VIEW(compose->text);
2407 textbuf = gtk_text_view_get_buffer(textview);
2408 compose_create_tags(textview, compose);
2410 mark = gtk_text_buffer_get_insert(textbuf);
2411 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2413 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2414 G_CALLBACK(compose_changed_cb),
2417 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2418 fp = procmime_get_first_encrypted_text_content(msginfo);
2420 compose_force_encryption(compose, account, TRUE, NULL);
2423 fp = procmime_get_first_text_content(msginfo);
2426 g_warning("Can't get text part");
2430 gchar buf[BUFFSIZE];
2431 gboolean prev_autowrap;
2432 GtkTextBuffer *buffer;
2434 while (fgets(buf, sizeof(buf), fp) != NULL) {
2436 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2442 compose_attach_parts(compose, msginfo);
2444 compose_colorize_signature(compose);
2446 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2447 G_CALLBACK(compose_changed_cb),
2450 if (manual_headers != NULL) {
2451 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2452 procheader_entries_free(manual_headers);
2453 compose->updating = FALSE;
2454 compose_destroy(compose);
2457 procheader_entries_free(manual_headers);
2460 gtk_widget_grab_focus(compose->text);
2462 if (prefs_common.auto_exteditor) {
2463 compose_exec_ext_editor(compose);
2465 compose->modified = FALSE;
2466 compose_set_title(compose);
2468 compose->updating = FALSE;
2469 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2470 SCROLL_TO_CURSOR(compose);
2472 if (compose->deferred_destroy) {
2473 compose_destroy(compose);
2477 compose->sig_str = account_get_signature_str(compose->account);
2479 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2484 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2491 cm_return_val_if_fail(msginfo != NULL, NULL);
2494 account = account_get_reply_account(msginfo,
2495 prefs_common.reply_account_autosel);
2496 cm_return_val_if_fail(account != NULL, NULL);
2498 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2499 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2501 compose->updating = TRUE;
2503 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2504 compose->replyinfo = NULL;
2505 compose->fwdinfo = NULL;
2507 compose_show_first_last_header(compose, TRUE);
2509 gtk_widget_grab_focus(compose->header_last->entry);
2511 filename = procmsg_get_message_file(msginfo);
2513 if (filename == NULL) {
2514 compose->updating = FALSE;
2515 compose_destroy(compose);
2520 compose->redirect_filename = filename;
2522 /* Set save folder */
2523 item = msginfo->folder;
2524 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2525 gchar *folderidentifier;
2527 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2528 folderidentifier = folder_item_get_identifier(item);
2529 compose_set_save_to(compose, folderidentifier);
2530 g_free(folderidentifier);
2533 compose_attach_parts(compose, msginfo);
2535 if (msginfo->subject)
2536 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2538 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2540 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2541 _("The body of the \"Redirect\" template has an error at line %d."));
2542 quote_fmt_reset_vartable();
2543 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2545 compose_colorize_signature(compose);
2548 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2549 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2550 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2554 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2555 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2559 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2560 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2562 if (compose->toolbar->draft_btn)
2563 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2564 if (compose->toolbar->insert_btn)
2565 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2566 if (compose->toolbar->attach_btn)
2567 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2568 if (compose->toolbar->sig_btn)
2569 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2570 if (compose->toolbar->exteditor_btn)
2571 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2572 if (compose->toolbar->linewrap_current_btn)
2573 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2574 if (compose->toolbar->linewrap_all_btn)
2575 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2577 compose->modified = FALSE;
2578 compose_set_title(compose);
2579 compose->updating = FALSE;
2580 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2581 SCROLL_TO_CURSOR(compose);
2583 if (compose->deferred_destroy) {
2584 compose_destroy(compose);
2588 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2593 const GList *compose_get_compose_list(void)
2595 return compose_list;
2598 void compose_entry_append(Compose *compose, const gchar *address,
2599 ComposeEntryType type, ComposePrefType pref_type)
2601 const gchar *header;
2603 gboolean in_quote = FALSE;
2604 if (!address || *address == '\0') return;
2611 header = N_("Bcc:");
2613 case COMPOSE_REPLYTO:
2614 header = N_("Reply-To:");
2616 case COMPOSE_NEWSGROUPS:
2617 header = N_("Newsgroups:");
2619 case COMPOSE_FOLLOWUPTO:
2620 header = N_( "Followup-To:");
2622 case COMPOSE_INREPLYTO:
2623 header = N_( "In-Reply-To:");
2630 header = prefs_common_translated_header_name(header);
2632 cur = begin = (gchar *)address;
2634 /* we separate the line by commas, but not if we're inside a quoted
2636 while (*cur != '\0') {
2638 in_quote = !in_quote;
2639 if (*cur == ',' && !in_quote) {
2640 gchar *tmp = g_strdup(begin);
2642 tmp[cur-begin]='\0';
2645 while (*tmp == ' ' || *tmp == '\t')
2647 compose_add_header_entry(compose, header, tmp, pref_type);
2648 compose_entry_indicate(compose, tmp);
2655 gchar *tmp = g_strdup(begin);
2657 tmp[cur-begin]='\0';
2658 while (*tmp == ' ' || *tmp == '\t')
2660 compose_add_header_entry(compose, header, tmp, pref_type);
2661 compose_entry_indicate(compose, tmp);
2666 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2671 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2672 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2673 if (gtk_entry_get_text(entry) &&
2674 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2675 gtk_widget_modify_base(
2676 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2677 GTK_STATE_NORMAL, &default_header_bgcolor);
2678 gtk_widget_modify_text(
2679 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2680 GTK_STATE_NORMAL, &default_header_color);
2685 void compose_toolbar_cb(gint action, gpointer data)
2687 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2688 Compose *compose = (Compose*)toolbar_item->parent;
2690 cm_return_if_fail(compose != NULL);
2694 compose_send_cb(NULL, compose);
2697 compose_send_later_cb(NULL, compose);
2700 compose_draft(compose, COMPOSE_QUIT_EDITING);
2703 compose_insert_file_cb(NULL, compose);
2706 compose_attach_cb(NULL, compose);
2709 compose_insert_sig(compose, FALSE);
2712 compose_insert_sig(compose, TRUE);
2715 compose_ext_editor_cb(NULL, compose);
2717 case A_LINEWRAP_CURRENT:
2718 compose_beautify_paragraph(compose, NULL, TRUE);
2720 case A_LINEWRAP_ALL:
2721 compose_wrap_all_full(compose, TRUE);
2724 compose_address_cb(NULL, compose);
2727 case A_CHECK_SPELLING:
2728 compose_check_all(NULL, compose);
2731 case A_PRIVACY_SIGN:
2733 case A_PRIVACY_ENCRYPT:
2740 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2745 gchar *subject = NULL;
2749 gchar **attach = NULL;
2750 gchar *inreplyto = NULL;
2751 MailField mfield = NO_FIELD_PRESENT;
2753 /* get mailto parts but skip from */
2754 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2757 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2758 mfield = TO_FIELD_PRESENT;
2761 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2763 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2765 if (!g_utf8_validate (subject, -1, NULL)) {
2766 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2767 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2770 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2772 mfield = SUBJECT_FIELD_PRESENT;
2775 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2776 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2779 gboolean prev_autowrap = compose->autowrap;
2781 compose->autowrap = FALSE;
2783 mark = gtk_text_buffer_get_insert(buffer);
2784 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2786 if (!g_utf8_validate (body, -1, NULL)) {
2787 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2788 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2791 gtk_text_buffer_insert(buffer, &iter, body, -1);
2793 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2795 compose->autowrap = prev_autowrap;
2796 if (compose->autowrap)
2797 compose_wrap_all(compose);
2798 mfield = BODY_FIELD_PRESENT;
2802 gint i = 0, att = 0;
2803 gchar *warn_files = NULL;
2804 while (attach[i] != NULL) {
2805 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2806 if (utf8_filename) {
2807 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2808 gchar *tmp = g_strdup_printf("%s%s\n",
2809 warn_files?warn_files:"",
2815 g_free(utf8_filename);
2817 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2822 alertpanel_notice(ngettext(
2823 "The following file has been attached: \n%s",
2824 "The following files have been attached: \n%s", att), warn_files);
2829 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2842 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2844 static HeaderEntry hentry[] = {
2845 {"Reply-To:", NULL, TRUE },
2846 {"Cc:", NULL, TRUE },
2847 {"References:", NULL, FALSE },
2848 {"Bcc:", NULL, TRUE },
2849 {"Newsgroups:", NULL, TRUE },
2850 {"Followup-To:", NULL, TRUE },
2851 {"List-Post:", NULL, FALSE },
2852 {"X-Priority:", NULL, FALSE },
2853 {NULL, NULL, FALSE }
2870 cm_return_val_if_fail(msginfo != NULL, -1);
2872 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2873 procheader_get_header_fields(fp, hentry);
2876 if (hentry[H_REPLY_TO].body != NULL) {
2877 if (hentry[H_REPLY_TO].body[0] != '\0') {
2879 conv_unmime_header(hentry[H_REPLY_TO].body,
2882 g_free(hentry[H_REPLY_TO].body);
2883 hentry[H_REPLY_TO].body = NULL;
2885 if (hentry[H_CC].body != NULL) {
2886 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2887 g_free(hentry[H_CC].body);
2888 hentry[H_CC].body = NULL;
2890 if (hentry[H_REFERENCES].body != NULL) {
2891 if (compose->mode == COMPOSE_REEDIT)
2892 compose->references = hentry[H_REFERENCES].body;
2894 compose->references = compose_parse_references
2895 (hentry[H_REFERENCES].body, msginfo->msgid);
2896 g_free(hentry[H_REFERENCES].body);
2898 hentry[H_REFERENCES].body = NULL;
2900 if (hentry[H_BCC].body != NULL) {
2901 if (compose->mode == COMPOSE_REEDIT)
2903 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2904 g_free(hentry[H_BCC].body);
2905 hentry[H_BCC].body = NULL;
2907 if (hentry[H_NEWSGROUPS].body != NULL) {
2908 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2909 hentry[H_NEWSGROUPS].body = NULL;
2911 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2912 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2913 compose->followup_to =
2914 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2917 g_free(hentry[H_FOLLOWUP_TO].body);
2918 hentry[H_FOLLOWUP_TO].body = NULL;
2920 if (hentry[H_LIST_POST].body != NULL) {
2921 gchar *to = NULL, *start = NULL;
2923 extract_address(hentry[H_LIST_POST].body);
2924 if (hentry[H_LIST_POST].body[0] != '\0') {
2925 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2927 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2928 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2931 g_free(compose->ml_post);
2932 compose->ml_post = to;
2935 g_free(hentry[H_LIST_POST].body);
2936 hentry[H_LIST_POST].body = NULL;
2939 /* CLAWS - X-Priority */
2940 if (compose->mode == COMPOSE_REEDIT)
2941 if (hentry[H_X_PRIORITY].body != NULL) {
2944 priority = atoi(hentry[H_X_PRIORITY].body);
2945 g_free(hentry[H_X_PRIORITY].body);
2947 hentry[H_X_PRIORITY].body = NULL;
2949 if (priority < PRIORITY_HIGHEST ||
2950 priority > PRIORITY_LOWEST)
2951 priority = PRIORITY_NORMAL;
2953 compose->priority = priority;
2956 if (compose->mode == COMPOSE_REEDIT) {
2957 if (msginfo->inreplyto && *msginfo->inreplyto)
2958 compose->inreplyto = g_strdup(msginfo->inreplyto);
2960 if (msginfo->msgid && *msginfo->msgid &&
2961 compose->folder != NULL &&
2962 compose->folder->stype == F_DRAFT)
2963 compose->msgid = g_strdup(msginfo->msgid);
2965 if (msginfo->msgid && *msginfo->msgid)
2966 compose->inreplyto = g_strdup(msginfo->msgid);
2968 if (!compose->references) {
2969 if (msginfo->msgid && *msginfo->msgid) {
2970 if (msginfo->inreplyto && *msginfo->inreplyto)
2971 compose->references =
2972 g_strdup_printf("<%s>\n\t<%s>",
2976 compose->references =
2977 g_strconcat("<", msginfo->msgid, ">",
2979 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2980 compose->references =
2981 g_strconcat("<", msginfo->inreplyto, ">",
2990 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2995 cm_return_val_if_fail(msginfo != NULL, -1);
2997 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2998 procheader_get_header_fields(fp, entries);
3002 while (he != NULL && he->name != NULL) {
3004 GtkListStore *model = NULL;
3006 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3007 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3008 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3009 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3010 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3017 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3019 GSList *ref_id_list, *cur;
3023 ref_id_list = references_list_append(NULL, ref);
3024 if (!ref_id_list) return NULL;
3025 if (msgid && *msgid)
3026 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3031 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3032 /* "<" + Message-ID + ">" + CR+LF+TAB */
3033 len += strlen((gchar *)cur->data) + 5;
3035 if (len > MAX_REFERENCES_LEN) {
3036 /* remove second message-ID */
3037 if (ref_id_list && ref_id_list->next &&
3038 ref_id_list->next->next) {
3039 g_free(ref_id_list->next->data);
3040 ref_id_list = g_slist_remove
3041 (ref_id_list, ref_id_list->next->data);
3043 slist_free_strings_full(ref_id_list);
3050 new_ref = g_string_new("");
3051 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3052 if (new_ref->len > 0)
3053 g_string_append(new_ref, "\n\t");
3054 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3057 slist_free_strings_full(ref_id_list);
3059 new_ref_str = new_ref->str;
3060 g_string_free(new_ref, FALSE);
3065 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3066 const gchar *fmt, const gchar *qmark,
3067 const gchar *body, gboolean rewrap,
3068 gboolean need_unescape,
3069 const gchar *err_msg)
3071 MsgInfo* dummyinfo = NULL;
3072 gchar *quote_str = NULL;
3074 gboolean prev_autowrap;
3075 const gchar *trimmed_body = body;
3076 gint cursor_pos = -1;
3077 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3078 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3083 SIGNAL_BLOCK(buffer);
3086 dummyinfo = compose_msginfo_new_from_compose(compose);
3087 msginfo = dummyinfo;
3090 if (qmark != NULL) {
3092 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3093 compose->gtkaspell);
3095 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3097 quote_fmt_scan_string(qmark);
3100 buf = quote_fmt_get_buffer();
3103 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3105 Xstrdup_a(quote_str, buf, goto error)
3108 if (fmt && *fmt != '\0') {
3111 while (*trimmed_body == '\n')
3115 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3116 compose->gtkaspell);
3118 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3120 if (need_unescape) {
3123 /* decode \-escape sequences in the internal representation of the quote format */
3124 tmp = g_malloc(strlen(fmt)+1);
3125 pref_get_unescaped_pref(tmp, fmt);
3126 quote_fmt_scan_string(tmp);
3130 quote_fmt_scan_string(fmt);
3134 buf = quote_fmt_get_buffer();
3137 gint line = quote_fmt_get_line();
3138 alertpanel_error(err_msg, line);
3146 prev_autowrap = compose->autowrap;
3147 compose->autowrap = FALSE;
3149 mark = gtk_text_buffer_get_insert(buffer);
3150 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3151 if (g_utf8_validate(buf, -1, NULL)) {
3152 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3154 gchar *tmpout = NULL;
3155 tmpout = conv_codeset_strdup
3156 (buf, conv_get_locale_charset_str_no_utf8(),
3158 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3160 tmpout = g_malloc(strlen(buf)*2+1);
3161 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3163 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3167 cursor_pos = quote_fmt_get_cursor_pos();
3168 if (cursor_pos == -1)
3169 cursor_pos = gtk_text_iter_get_offset(&iter);
3170 compose->set_cursor_pos = cursor_pos;
3172 gtk_text_buffer_get_start_iter(buffer, &iter);
3173 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3174 gtk_text_buffer_place_cursor(buffer, &iter);
3176 compose->autowrap = prev_autowrap;
3177 if (compose->autowrap && rewrap)
3178 compose_wrap_all(compose);
3185 SIGNAL_UNBLOCK(buffer);
3187 procmsg_msginfo_free( &dummyinfo );
3192 /* if ml_post is of type addr@host and from is of type
3193 * addr-anything@host, return TRUE
3195 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3197 gchar *left_ml = NULL;
3198 gchar *right_ml = NULL;
3199 gchar *left_from = NULL;
3200 gchar *right_from = NULL;
3201 gboolean result = FALSE;
3203 if (!ml_post || !from)
3206 left_ml = g_strdup(ml_post);
3207 if (strstr(left_ml, "@")) {
3208 right_ml = strstr(left_ml, "@")+1;
3209 *(strstr(left_ml, "@")) = '\0';
3212 left_from = g_strdup(from);
3213 if (strstr(left_from, "@")) {
3214 right_from = strstr(left_from, "@")+1;
3215 *(strstr(left_from, "@")) = '\0';
3218 if (right_ml && right_from
3219 && !strncmp(left_from, left_ml, strlen(left_ml))
3220 && !strcmp(right_from, right_ml)) {
3229 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3230 gboolean respect_default_to)
3234 if (!folder || !folder->prefs)
3237 if (respect_default_to && folder->prefs->enable_default_to) {
3238 compose_entry_append(compose, folder->prefs->default_to,
3239 COMPOSE_TO, PREF_FOLDER);
3240 compose_entry_indicate(compose, folder->prefs->default_to);
3242 if (folder->prefs->enable_default_cc) {
3243 compose_entry_append(compose, folder->prefs->default_cc,
3244 COMPOSE_CC, PREF_FOLDER);
3245 compose_entry_indicate(compose, folder->prefs->default_cc);
3247 if (folder->prefs->enable_default_bcc) {
3248 compose_entry_append(compose, folder->prefs->default_bcc,
3249 COMPOSE_BCC, PREF_FOLDER);
3250 compose_entry_indicate(compose, folder->prefs->default_bcc);
3252 if (folder->prefs->enable_default_replyto) {
3253 compose_entry_append(compose, folder->prefs->default_replyto,
3254 COMPOSE_REPLYTO, PREF_FOLDER);
3255 compose_entry_indicate(compose, folder->prefs->default_replyto);
3259 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3264 if (!compose || !msginfo)
3267 if (msginfo->subject && *msginfo->subject) {
3268 buf = p = g_strdup(msginfo->subject);
3269 p += subject_get_prefix_length(p);
3270 memmove(buf, p, strlen(p) + 1);
3272 buf2 = g_strdup_printf("Re: %s", buf);
3273 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3278 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3281 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3282 gboolean to_all, gboolean to_ml,
3284 gboolean followup_and_reply_to)
3286 GSList *cc_list = NULL;
3289 gchar *replyto = NULL;
3290 gchar *ac_email = NULL;
3292 gboolean reply_to_ml = FALSE;
3293 gboolean default_reply_to = FALSE;
3295 cm_return_if_fail(compose->account != NULL);
3296 cm_return_if_fail(msginfo != NULL);
3298 reply_to_ml = to_ml && compose->ml_post;
3300 default_reply_to = msginfo->folder &&
3301 msginfo->folder->prefs->enable_default_reply_to;
3303 if (compose->account->protocol != A_NNTP) {
3304 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3306 if (reply_to_ml && !default_reply_to) {
3308 gboolean is_subscr = is_subscription(compose->ml_post,
3311 /* normal answer to ml post with a reply-to */
3312 compose_entry_append(compose,
3314 COMPOSE_TO, PREF_ML);
3315 if (compose->replyto)
3316 compose_entry_append(compose,
3318 COMPOSE_CC, PREF_ML);
3320 /* answer to subscription confirmation */
3321 if (compose->replyto)
3322 compose_entry_append(compose,
3324 COMPOSE_TO, PREF_ML);
3325 else if (msginfo->from)
3326 compose_entry_append(compose,
3328 COMPOSE_TO, PREF_ML);
3331 else if (!(to_all || to_sender) && default_reply_to) {
3332 compose_entry_append(compose,
3333 msginfo->folder->prefs->default_reply_to,
3334 COMPOSE_TO, PREF_FOLDER);
3335 compose_entry_indicate(compose,
3336 msginfo->folder->prefs->default_reply_to);
3342 compose_entry_append(compose, msginfo->from,
3343 COMPOSE_TO, PREF_NONE);
3345 Xstrdup_a(tmp1, msginfo->from, return);
3346 extract_address(tmp1);
3347 compose_entry_append(compose,
3348 (!account_find_from_address(tmp1, FALSE))
3351 COMPOSE_TO, PREF_NONE);
3352 if (compose->replyto)
3353 compose_entry_append(compose,
3355 COMPOSE_CC, PREF_NONE);
3357 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3358 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3359 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3360 if (compose->replyto) {
3361 compose_entry_append(compose,
3363 COMPOSE_TO, PREF_NONE);
3365 compose_entry_append(compose,
3366 msginfo->from ? msginfo->from : "",
3367 COMPOSE_TO, PREF_NONE);
3370 /* replying to own mail, use original recp */
3371 compose_entry_append(compose,
3372 msginfo->to ? msginfo->to : "",
3373 COMPOSE_TO, PREF_NONE);
3374 compose_entry_append(compose,
3375 msginfo->cc ? msginfo->cc : "",
3376 COMPOSE_CC, PREF_NONE);
3381 if (to_sender || (compose->followup_to &&
3382 !strncmp(compose->followup_to, "poster", 6)))
3383 compose_entry_append
3385 (compose->replyto ? compose->replyto :
3386 msginfo->from ? msginfo->from : ""),
3387 COMPOSE_TO, PREF_NONE);
3389 else if (followup_and_reply_to || to_all) {
3390 compose_entry_append
3392 (compose->replyto ? compose->replyto :
3393 msginfo->from ? msginfo->from : ""),
3394 COMPOSE_TO, PREF_NONE);
3396 compose_entry_append
3398 compose->followup_to ? compose->followup_to :
3399 compose->newsgroups ? compose->newsgroups : "",
3400 COMPOSE_NEWSGROUPS, PREF_NONE);
3402 compose_entry_append
3404 msginfo->cc ? msginfo->cc : "",
3405 COMPOSE_CC, PREF_NONE);
3408 compose_entry_append
3410 compose->followup_to ? compose->followup_to :
3411 compose->newsgroups ? compose->newsgroups : "",
3412 COMPOSE_NEWSGROUPS, PREF_NONE);
3414 compose_reply_set_subject(compose, msginfo);
3416 if (to_ml && compose->ml_post) return;
3417 if (!to_all || compose->account->protocol == A_NNTP) return;
3419 if (compose->replyto) {
3420 Xstrdup_a(replyto, compose->replyto, return);
3421 extract_address(replyto);
3423 if (msginfo->from) {
3424 Xstrdup_a(from, msginfo->from, return);
3425 extract_address(from);
3428 if (replyto && from)
3429 cc_list = address_list_append_with_comments(cc_list, from);
3430 if (to_all && msginfo->folder &&
3431 msginfo->folder->prefs->enable_default_reply_to)
3432 cc_list = address_list_append_with_comments(cc_list,
3433 msginfo->folder->prefs->default_reply_to);
3434 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3435 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3437 ac_email = g_utf8_strdown(compose->account->address, -1);
3440 for (cur = cc_list; cur != NULL; cur = cur->next) {
3441 gchar *addr = g_utf8_strdown(cur->data, -1);
3442 extract_address(addr);
3444 if (strcmp(ac_email, addr))
3445 compose_entry_append(compose, (gchar *)cur->data,
3446 COMPOSE_CC, PREF_NONE);
3448 debug_print("Cc address same as compose account's, ignoring\n");
3453 slist_free_strings_full(cc_list);
3459 #define SET_ENTRY(entry, str) \
3462 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3465 #define SET_ADDRESS(type, str) \
3468 compose_entry_append(compose, str, type, PREF_NONE); \
3471 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3473 cm_return_if_fail(msginfo != NULL);
3475 SET_ENTRY(subject_entry, msginfo->subject);
3476 SET_ENTRY(from_name, msginfo->from);
3477 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3478 SET_ADDRESS(COMPOSE_CC, compose->cc);
3479 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3480 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3481 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3482 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3484 compose_update_priority_menu_item(compose);
3485 compose_update_privacy_system_menu_item(compose, FALSE);
3486 compose_show_first_last_header(compose, TRUE);
3492 static void compose_insert_sig(Compose *compose, gboolean replace)
3494 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3495 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3497 GtkTextIter iter, iter_end;
3498 gint cur_pos, ins_pos;
3499 gboolean prev_autowrap;
3500 gboolean found = FALSE;
3501 gboolean exists = FALSE;
3503 cm_return_if_fail(compose->account != NULL);
3507 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3508 G_CALLBACK(compose_changed_cb),
3511 mark = gtk_text_buffer_get_insert(buffer);
3512 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3513 cur_pos = gtk_text_iter_get_offset (&iter);
3516 gtk_text_buffer_get_end_iter(buffer, &iter);
3518 exists = (compose->sig_str != NULL);
3521 GtkTextIter first_iter, start_iter, end_iter;
3523 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3525 if (!exists || compose->sig_str[0] == '\0')
3528 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3529 compose->signature_tag);
3532 /* include previous \n\n */
3533 gtk_text_iter_backward_chars(&first_iter, 1);
3534 start_iter = first_iter;
3535 end_iter = first_iter;
3537 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3538 compose->signature_tag);
3539 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3540 compose->signature_tag);
3542 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3548 g_free(compose->sig_str);
3549 compose->sig_str = account_get_signature_str(compose->account);
3551 cur_pos = gtk_text_iter_get_offset(&iter);
3553 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3554 g_free(compose->sig_str);
3555 compose->sig_str = NULL;
3557 if (compose->sig_inserted == FALSE)
3558 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3559 compose->sig_inserted = TRUE;
3561 cur_pos = gtk_text_iter_get_offset(&iter);
3562 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3564 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3565 gtk_text_iter_forward_chars(&iter, 1);
3566 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3567 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3569 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3570 cur_pos = gtk_text_buffer_get_char_count (buffer);
3573 /* put the cursor where it should be
3574 * either where the quote_fmt says, either where it was */
3575 if (compose->set_cursor_pos < 0)
3576 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3578 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3579 compose->set_cursor_pos);
3581 compose->set_cursor_pos = -1;
3582 gtk_text_buffer_place_cursor(buffer, &iter);
3583 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3584 G_CALLBACK(compose_changed_cb),
3590 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3593 GtkTextBuffer *buffer;
3596 const gchar *cur_encoding;
3597 gchar buf[BUFFSIZE];
3600 gboolean prev_autowrap;
3604 GError *error = NULL;
3610 GString *file_contents = NULL;
3611 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3613 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3615 /* get the size of the file we are about to insert */
3617 f = g_file_new_for_path(file);
3618 fi = g_file_query_info(f, "standard::size",
3619 G_FILE_QUERY_INFO_NONE, NULL, &error);
3621 if (error != NULL) {
3622 g_warning(error->message);
3624 g_error_free(error);
3628 ret = g_stat(file, &file_stat);
3631 gchar *shortfile = g_path_get_basename(file);
3632 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3634 return COMPOSE_INSERT_NO_FILE;
3635 } else if (prefs_common.warn_large_insert == TRUE) {
3637 size = g_file_info_get_size(fi);
3641 size = file_stat.st_size;
3644 /* ask user for confirmation if the file is large */
3645 if (prefs_common.warn_large_insert_size < 0 ||
3646 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3650 msg = g_strdup_printf(_("You are about to insert a file of %s "
3651 "in the message body. Are you sure you want to do that?"),
3652 to_human_readable(size));
3653 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3654 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3655 NULL, ALERT_QUESTION);
3658 /* do we ask for confirmation next time? */
3659 if (aval & G_ALERTDISABLE) {
3660 /* no confirmation next time, disable feature in preferences */
3661 aval &= ~G_ALERTDISABLE;
3662 prefs_common.warn_large_insert = FALSE;
3665 /* abort file insertion if user canceled action */
3666 if (aval != G_ALERTALTERNATE) {
3667 return COMPOSE_INSERT_NO_FILE;
3673 if ((fp = g_fopen(file, "rb")) == NULL) {
3674 FILE_OP_ERROR(file, "fopen");
3675 return COMPOSE_INSERT_READ_ERROR;
3678 prev_autowrap = compose->autowrap;
3679 compose->autowrap = FALSE;
3681 text = GTK_TEXT_VIEW(compose->text);
3682 buffer = gtk_text_view_get_buffer(text);
3683 mark = gtk_text_buffer_get_insert(buffer);
3684 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3686 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3687 G_CALLBACK(text_inserted),
3690 cur_encoding = conv_get_locale_charset_str_no_utf8();
3692 file_contents = g_string_new("");
3693 while (fgets(buf, sizeof(buf), fp) != NULL) {
3696 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3697 str = g_strdup(buf);
3699 codeconv_set_strict(TRUE);
3700 str = conv_codeset_strdup
3701 (buf, cur_encoding, CS_INTERNAL);
3702 codeconv_set_strict(FALSE);
3705 result = COMPOSE_INSERT_INVALID_CHARACTER;
3711 /* strip <CR> if DOS/Windows file,
3712 replace <CR> with <LF> if Macintosh file. */
3715 if (len > 0 && str[len - 1] != '\n') {
3717 if (str[len] == '\r') str[len] = '\n';
3720 file_contents = g_string_append(file_contents, str);
3724 if (result == COMPOSE_INSERT_SUCCESS) {
3725 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3727 compose_changed_cb(NULL, compose);
3728 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3729 G_CALLBACK(text_inserted),
3731 compose->autowrap = prev_autowrap;
3732 if (compose->autowrap)
3733 compose_wrap_all(compose);
3736 g_string_free(file_contents, TRUE);
3742 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3743 const gchar *filename,
3744 const gchar *content_type,
3745 const gchar *charset)
3753 GtkListStore *store;
3755 gboolean has_binary = FALSE;
3757 if (!is_file_exist(file)) {
3758 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3759 gboolean result = FALSE;
3760 if (file_from_uri && is_file_exist(file_from_uri)) {
3761 result = compose_attach_append(
3762 compose, file_from_uri,
3763 filename, content_type,
3766 g_free(file_from_uri);
3769 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3772 if ((size = get_file_size(file)) < 0) {
3773 alertpanel_error("Can't get file size of %s\n", filename);
3777 /* In batch mode, we allow 0-length files to be attached no questions asked */
3778 if (size == 0 && !compose->batch) {
3779 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3780 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3781 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3782 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3785 if (aval != G_ALERTALTERNATE) {
3789 if ((fp = g_fopen(file, "rb")) == NULL) {
3790 alertpanel_error(_("Can't read %s."), filename);
3795 ainfo = g_new0(AttachInfo, 1);
3796 auto_ainfo = g_auto_pointer_new_with_free
3797 (ainfo, (GFreeFunc) compose_attach_info_free);
3798 ainfo->file = g_strdup(file);
3801 ainfo->content_type = g_strdup(content_type);
3802 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3804 MsgFlags flags = {0, 0};
3806 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3807 ainfo->encoding = ENC_7BIT;
3809 ainfo->encoding = ENC_8BIT;
3811 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3812 if (msginfo && msginfo->subject)
3813 name = g_strdup(msginfo->subject);
3815 name = g_path_get_basename(filename ? filename : file);
3817 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3819 procmsg_msginfo_free(&msginfo);
3821 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3822 ainfo->charset = g_strdup(charset);
3823 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3825 ainfo->encoding = ENC_BASE64;
3827 name = g_path_get_basename(filename ? filename : file);
3828 ainfo->name = g_strdup(name);
3832 ainfo->content_type = procmime_get_mime_type(file);
3833 if (!ainfo->content_type) {
3834 ainfo->content_type =
3835 g_strdup("application/octet-stream");
3836 ainfo->encoding = ENC_BASE64;
3837 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3839 procmime_get_encoding_for_text_file(file, &has_binary);
3841 ainfo->encoding = ENC_BASE64;
3842 name = g_path_get_basename(filename ? filename : file);
3843 ainfo->name = g_strdup(name);
3847 if (ainfo->name != NULL
3848 && !strcmp(ainfo->name, ".")) {
3849 g_free(ainfo->name);
3853 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3854 g_free(ainfo->content_type);
3855 ainfo->content_type = g_strdup("application/octet-stream");
3856 g_free(ainfo->charset);
3857 ainfo->charset = NULL;
3860 ainfo->size = (goffset)size;
3861 size_text = to_human_readable((goffset)size);
3863 store = GTK_LIST_STORE(gtk_tree_view_get_model
3864 (GTK_TREE_VIEW(compose->attach_clist)));
3866 gtk_list_store_append(store, &iter);
3867 gtk_list_store_set(store, &iter,
3868 COL_MIMETYPE, ainfo->content_type,
3869 COL_SIZE, size_text,
3870 COL_NAME, ainfo->name,
3871 COL_CHARSET, ainfo->charset,
3873 COL_AUTODATA, auto_ainfo,
3876 g_auto_pointer_free(auto_ainfo);
3877 compose_attach_update_label(compose);
3881 void compose_use_signing(Compose *compose, gboolean use_signing)
3883 compose->use_signing = use_signing;
3884 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3887 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3889 compose->use_encryption = use_encryption;
3890 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3893 #define NEXT_PART_NOT_CHILD(info) \
3895 node = info->node; \
3896 while (node->children) \
3897 node = g_node_last_child(node); \
3898 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3901 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3905 MimeInfo *firsttext = NULL;
3906 MimeInfo *encrypted = NULL;
3909 const gchar *partname = NULL;
3911 mimeinfo = procmime_scan_message(msginfo);
3912 if (!mimeinfo) return;
3914 if (mimeinfo->node->children == NULL) {
3915 procmime_mimeinfo_free_all(&mimeinfo);
3919 /* find first content part */
3920 child = (MimeInfo *) mimeinfo->node->children->data;
3921 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3922 child = (MimeInfo *)child->node->children->data;
3925 if (child->type == MIMETYPE_TEXT) {
3927 debug_print("First text part found\n");
3928 } else if (compose->mode == COMPOSE_REEDIT &&
3929 child->type == MIMETYPE_APPLICATION &&
3930 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3931 encrypted = (MimeInfo *)child->node->parent->data;
3934 child = (MimeInfo *) mimeinfo->node->children->data;
3935 while (child != NULL) {
3938 if (child == encrypted) {
3939 /* skip this part of tree */
3940 NEXT_PART_NOT_CHILD(child);
3944 if (child->type == MIMETYPE_MULTIPART) {
3945 /* get the actual content */
3946 child = procmime_mimeinfo_next(child);
3950 if (child == firsttext) {
3951 child = procmime_mimeinfo_next(child);
3955 outfile = procmime_get_tmp_file_name(child);
3956 if ((err = procmime_get_part(outfile, child)) < 0)
3957 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3959 gchar *content_type;
3961 content_type = procmime_get_content_type_str(child->type, child->subtype);
3963 /* if we meet a pgp signature, we don't attach it, but
3964 * we force signing. */
3965 if ((strcmp(content_type, "application/pgp-signature") &&
3966 strcmp(content_type, "application/pkcs7-signature") &&
3967 strcmp(content_type, "application/x-pkcs7-signature"))
3968 || compose->mode == COMPOSE_REDIRECT) {
3969 partname = procmime_mimeinfo_get_parameter(child, "filename");
3970 if (partname == NULL)
3971 partname = procmime_mimeinfo_get_parameter(child, "name");
3972 if (partname == NULL)
3974 compose_attach_append(compose, outfile,
3975 partname, content_type,
3976 procmime_mimeinfo_get_parameter(child, "charset"));
3978 compose_force_signing(compose, compose->account, NULL);
3980 g_free(content_type);
3983 NEXT_PART_NOT_CHILD(child);
3985 procmime_mimeinfo_free_all(&mimeinfo);
3988 #undef NEXT_PART_NOT_CHILD
3993 WAIT_FOR_INDENT_CHAR,
3994 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3997 /* return indent length, we allow:
3998 indent characters followed by indent characters or spaces/tabs,
3999 alphabets and numbers immediately followed by indent characters,
4000 and the repeating sequences of the above
4001 If quote ends with multiple spaces, only the first one is included. */
4002 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4003 const GtkTextIter *start, gint *len)
4005 GtkTextIter iter = *start;
4009 IndentState state = WAIT_FOR_INDENT_CHAR;
4012 gint alnum_count = 0;
4013 gint space_count = 0;
4016 if (prefs_common.quote_chars == NULL) {
4020 while (!gtk_text_iter_ends_line(&iter)) {
4021 wc = gtk_text_iter_get_char(&iter);
4022 if (g_unichar_iswide(wc))
4024 clen = g_unichar_to_utf8(wc, ch);
4028 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4029 is_space = g_unichar_isspace(wc);
4031 if (state == WAIT_FOR_INDENT_CHAR) {
4032 if (!is_indent && !g_unichar_isalnum(wc))
4035 quote_len += alnum_count + space_count + 1;
4036 alnum_count = space_count = 0;
4037 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4040 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4041 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4045 else if (is_indent) {
4046 quote_len += alnum_count + space_count + 1;
4047 alnum_count = space_count = 0;
4050 state = WAIT_FOR_INDENT_CHAR;
4054 gtk_text_iter_forward_char(&iter);
4057 if (quote_len > 0 && space_count > 0)
4063 if (quote_len > 0) {
4065 gtk_text_iter_forward_chars(&iter, quote_len);
4066 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4072 /* return >0 if the line is itemized */
4073 static int compose_itemized_length(GtkTextBuffer *buffer,
4074 const GtkTextIter *start)
4076 GtkTextIter iter = *start;
4081 if (gtk_text_iter_ends_line(&iter))
4086 wc = gtk_text_iter_get_char(&iter);
4087 if (!g_unichar_isspace(wc))
4089 gtk_text_iter_forward_char(&iter);
4090 if (gtk_text_iter_ends_line(&iter))
4094 clen = g_unichar_to_utf8(wc, ch);
4095 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4097 wc == 0x2022 || /* BULLET */
4098 wc == 0x2023 || /* TRIANGULAR BULLET */
4099 wc == 0x2043 || /* HYPHEN BULLET */
4100 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4101 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4102 wc == 0x2219 || /* BULLET OPERATOR */
4103 wc == 0x25d8 || /* INVERSE BULLET */
4104 wc == 0x25e6 || /* WHITE BULLET */
4105 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4106 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4107 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4108 wc == 0x29be || /* CIRCLED WHITE BULLET */
4109 wc == 0x29bf /* CIRCLED BULLET */
4113 gtk_text_iter_forward_char(&iter);
4114 if (gtk_text_iter_ends_line(&iter))
4116 wc = gtk_text_iter_get_char(&iter);
4117 if (g_unichar_isspace(wc)) {
4123 /* return the string at the start of the itemization */
4124 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4125 const GtkTextIter *start)
4127 GtkTextIter iter = *start;
4130 GString *item_chars = g_string_new("");
4133 if (gtk_text_iter_ends_line(&iter))
4138 wc = gtk_text_iter_get_char(&iter);
4139 if (!g_unichar_isspace(wc))
4141 gtk_text_iter_forward_char(&iter);
4142 if (gtk_text_iter_ends_line(&iter))
4144 g_string_append_unichar(item_chars, wc);
4147 str = item_chars->str;
4148 g_string_free(item_chars, FALSE);
4152 /* return the number of spaces at a line's start */
4153 static int compose_left_offset_length(GtkTextBuffer *buffer,
4154 const GtkTextIter *start)
4156 GtkTextIter iter = *start;
4159 if (gtk_text_iter_ends_line(&iter))
4163 wc = gtk_text_iter_get_char(&iter);
4164 if (!g_unichar_isspace(wc))
4167 gtk_text_iter_forward_char(&iter);
4168 if (gtk_text_iter_ends_line(&iter))
4172 gtk_text_iter_forward_char(&iter);
4173 if (gtk_text_iter_ends_line(&iter))
4178 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4179 const GtkTextIter *start,
4180 GtkTextIter *break_pos,
4184 GtkTextIter iter = *start, line_end = *start;
4185 PangoLogAttr *attrs;
4192 gboolean can_break = FALSE;
4193 gboolean do_break = FALSE;
4194 gboolean was_white = FALSE;
4195 gboolean prev_dont_break = FALSE;
4197 gtk_text_iter_forward_to_line_end(&line_end);
4198 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4199 len = g_utf8_strlen(str, -1);
4203 g_warning("compose_get_line_break_pos: len = 0!");
4207 /* g_print("breaking line: %d: %s (len = %d)\n",
4208 gtk_text_iter_get_line(&iter), str, len); */
4210 attrs = g_new(PangoLogAttr, len + 1);
4212 pango_default_break(str, -1, NULL, attrs, len + 1);
4216 /* skip quote and leading spaces */
4217 for (i = 0; *p != '\0' && i < len; i++) {
4220 wc = g_utf8_get_char(p);
4221 if (i >= quote_len && !g_unichar_isspace(wc))
4223 if (g_unichar_iswide(wc))
4225 else if (*p == '\t')
4229 p = g_utf8_next_char(p);
4232 for (; *p != '\0' && i < len; i++) {
4233 PangoLogAttr *attr = attrs + i;
4237 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4240 was_white = attr->is_white;
4242 /* don't wrap URI */
4243 if ((uri_len = get_uri_len(p)) > 0) {
4245 if (pos > 0 && col > max_col) {
4255 wc = g_utf8_get_char(p);
4256 if (g_unichar_iswide(wc)) {
4258 if (prev_dont_break && can_break && attr->is_line_break)
4260 } else if (*p == '\t')
4264 if (pos > 0 && col > max_col) {
4269 if (*p == '-' || *p == '/')
4270 prev_dont_break = TRUE;
4272 prev_dont_break = FALSE;
4274 p = g_utf8_next_char(p);
4278 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4283 *break_pos = *start;
4284 gtk_text_iter_set_line_offset(break_pos, pos);
4289 static gboolean compose_join_next_line(Compose *compose,
4290 GtkTextBuffer *buffer,
4292 const gchar *quote_str)
4294 GtkTextIter iter_ = *iter, cur, prev, next, end;
4295 PangoLogAttr attrs[3];
4297 gchar *next_quote_str;
4300 gboolean keep_cursor = FALSE;
4302 if (!gtk_text_iter_forward_line(&iter_) ||
4303 gtk_text_iter_ends_line(&iter_)) {
4306 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4308 if ((quote_str || next_quote_str) &&
4309 strcmp2(quote_str, next_quote_str) != 0) {
4310 g_free(next_quote_str);
4313 g_free(next_quote_str);
4316 if (quote_len > 0) {
4317 gtk_text_iter_forward_chars(&end, quote_len);
4318 if (gtk_text_iter_ends_line(&end)) {
4323 /* don't join itemized lines */
4324 if (compose_itemized_length(buffer, &end) > 0) {
4328 /* don't join signature separator */
4329 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4332 /* delete quote str */
4334 gtk_text_buffer_delete(buffer, &iter_, &end);
4336 /* don't join line breaks put by the user */
4338 gtk_text_iter_backward_char(&cur);
4339 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4340 gtk_text_iter_forward_char(&cur);
4344 gtk_text_iter_forward_char(&cur);
4345 /* delete linebreak and extra spaces */
4346 while (gtk_text_iter_backward_char(&cur)) {
4347 wc1 = gtk_text_iter_get_char(&cur);
4348 if (!g_unichar_isspace(wc1))
4353 while (!gtk_text_iter_ends_line(&cur)) {
4354 wc1 = gtk_text_iter_get_char(&cur);
4355 if (!g_unichar_isspace(wc1))
4357 gtk_text_iter_forward_char(&cur);
4360 if (!gtk_text_iter_equal(&prev, &next)) {
4363 mark = gtk_text_buffer_get_insert(buffer);
4364 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4365 if (gtk_text_iter_equal(&prev, &cur))
4367 gtk_text_buffer_delete(buffer, &prev, &next);
4371 /* insert space if required */
4372 gtk_text_iter_backward_char(&prev);
4373 wc1 = gtk_text_iter_get_char(&prev);
4374 wc2 = gtk_text_iter_get_char(&next);
4375 gtk_text_iter_forward_char(&next);
4376 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4377 pango_default_break(str, -1, NULL, attrs, 3);
4378 if (!attrs[1].is_line_break ||
4379 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4380 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4382 gtk_text_iter_backward_char(&iter_);
4383 gtk_text_buffer_place_cursor(buffer, &iter_);
4392 #define ADD_TXT_POS(bp_, ep_, pti_) \
4393 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4394 last = last->next; \
4395 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4396 last->next = NULL; \
4398 g_warning("alloc error scanning URIs"); \
4401 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4403 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4404 GtkTextBuffer *buffer;
4405 GtkTextIter iter, break_pos, end_of_line;
4406 gchar *quote_str = NULL;
4408 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4409 gboolean prev_autowrap = compose->autowrap;
4410 gint startq_offset = -1, noq_offset = -1;
4411 gint uri_start = -1, uri_stop = -1;
4412 gint nouri_start = -1, nouri_stop = -1;
4413 gint num_blocks = 0;
4414 gint quotelevel = -1;
4415 gboolean modified = force;
4416 gboolean removed = FALSE;
4417 gboolean modified_before_remove = FALSE;
4419 gboolean start = TRUE;
4420 gint itemized_len = 0, rem_item_len = 0;
4421 gchar *itemized_chars = NULL;
4422 gboolean item_continuation = FALSE;
4427 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4431 compose->autowrap = FALSE;
4433 buffer = gtk_text_view_get_buffer(text);
4434 undo_wrapping(compose->undostruct, TRUE);
4439 mark = gtk_text_buffer_get_insert(buffer);
4440 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4444 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4445 if (gtk_text_iter_ends_line(&iter)) {
4446 while (gtk_text_iter_ends_line(&iter) &&
4447 gtk_text_iter_forward_line(&iter))
4450 while (gtk_text_iter_backward_line(&iter)) {
4451 if (gtk_text_iter_ends_line(&iter)) {
4452 gtk_text_iter_forward_line(&iter);
4458 /* move to line start */
4459 gtk_text_iter_set_line_offset(&iter, 0);
4462 itemized_len = compose_itemized_length(buffer, &iter);
4464 if (!itemized_len) {
4465 itemized_len = compose_left_offset_length(buffer, &iter);
4466 item_continuation = TRUE;
4470 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4472 /* go until paragraph end (empty line) */
4473 while (start || !gtk_text_iter_ends_line(&iter)) {
4474 gchar *scanpos = NULL;
4475 /* parse table - in order of priority */
4477 const gchar *needle; /* token */
4479 /* token search function */
4480 gchar *(*search) (const gchar *haystack,
4481 const gchar *needle);
4482 /* part parsing function */
4483 gboolean (*parse) (const gchar *start,
4484 const gchar *scanpos,
4488 /* part to URI function */
4489 gchar *(*build_uri) (const gchar *bp,
4493 static struct table parser[] = {
4494 {"http://", strcasestr, get_uri_part, make_uri_string},
4495 {"https://", strcasestr, get_uri_part, make_uri_string},
4496 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4497 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4498 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4499 {"www.", strcasestr, get_uri_part, make_http_string},
4500 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4501 {"@", strcasestr, get_email_part, make_email_string}
4503 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4504 gint last_index = PARSE_ELEMS;
4506 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4510 if (!prev_autowrap && num_blocks == 0) {
4512 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4513 G_CALLBACK(text_inserted),
4516 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4519 uri_start = uri_stop = -1;
4521 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4524 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4525 if (startq_offset == -1)
4526 startq_offset = gtk_text_iter_get_offset(&iter);
4527 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4528 if (quotelevel > 2) {
4529 /* recycle colors */
4530 if (prefs_common.recycle_quote_colors)
4539 if (startq_offset == -1)
4540 noq_offset = gtk_text_iter_get_offset(&iter);
4544 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4547 if (gtk_text_iter_ends_line(&iter)) {
4549 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4550 prefs_common.linewrap_len,
4552 GtkTextIter prev, next, cur;
4553 if (prev_autowrap != FALSE || force) {
4554 compose->automatic_break = TRUE;
4556 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4557 compose->automatic_break = FALSE;
4558 if (itemized_len && compose->autoindent) {
4559 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4560 if (!item_continuation)
4561 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4563 } else if (quote_str && wrap_quote) {
4564 compose->automatic_break = TRUE;
4566 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4567 compose->automatic_break = FALSE;
4568 if (itemized_len && compose->autoindent) {
4569 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4570 if (!item_continuation)
4571 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4575 /* remove trailing spaces */
4577 rem_item_len = itemized_len;
4578 while (compose->autoindent && rem_item_len-- > 0)
4579 gtk_text_iter_backward_char(&cur);
4580 gtk_text_iter_backward_char(&cur);
4583 while (!gtk_text_iter_starts_line(&cur)) {
4586 gtk_text_iter_backward_char(&cur);
4587 wc = gtk_text_iter_get_char(&cur);
4588 if (!g_unichar_isspace(wc))
4592 if (!gtk_text_iter_equal(&prev, &next)) {
4593 gtk_text_buffer_delete(buffer, &prev, &next);
4595 gtk_text_iter_forward_char(&break_pos);
4599 gtk_text_buffer_insert(buffer, &break_pos,
4603 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4605 /* move iter to current line start */
4606 gtk_text_iter_set_line_offset(&iter, 0);
4613 /* move iter to next line start */
4619 if (!prev_autowrap && num_blocks > 0) {
4621 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4622 G_CALLBACK(text_inserted),
4626 while (!gtk_text_iter_ends_line(&end_of_line)) {
4627 gtk_text_iter_forward_char(&end_of_line);
4629 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4631 nouri_start = gtk_text_iter_get_offset(&iter);
4632 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4634 walk_pos = gtk_text_iter_get_offset(&iter);
4635 /* FIXME: this looks phony. scanning for anything in the parse table */
4636 for (n = 0; n < PARSE_ELEMS; n++) {
4639 tmp = parser[n].search(walk, parser[n].needle);
4641 if (scanpos == NULL || tmp < scanpos) {
4650 /* check if URI can be parsed */
4651 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4652 (const gchar **)&ep, FALSE)
4653 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4657 strlen(parser[last_index].needle);
4660 uri_start = walk_pos + (bp - o_walk);
4661 uri_stop = walk_pos + (ep - o_walk);
4665 gtk_text_iter_forward_line(&iter);
4668 if (startq_offset != -1) {
4669 GtkTextIter startquote, endquote;
4670 gtk_text_buffer_get_iter_at_offset(
4671 buffer, &startquote, startq_offset);
4674 switch (quotelevel) {
4676 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4677 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4678 gtk_text_buffer_apply_tag_by_name(
4679 buffer, "quote0", &startquote, &endquote);
4680 gtk_text_buffer_remove_tag_by_name(
4681 buffer, "quote1", &startquote, &endquote);
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "quote2", &startquote, &endquote);
4688 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4689 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4690 gtk_text_buffer_apply_tag_by_name(
4691 buffer, "quote1", &startquote, &endquote);
4692 gtk_text_buffer_remove_tag_by_name(
4693 buffer, "quote0", &startquote, &endquote);
4694 gtk_text_buffer_remove_tag_by_name(
4695 buffer, "quote2", &startquote, &endquote);
4700 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4701 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4702 gtk_text_buffer_apply_tag_by_name(
4703 buffer, "quote2", &startquote, &endquote);
4704 gtk_text_buffer_remove_tag_by_name(
4705 buffer, "quote0", &startquote, &endquote);
4706 gtk_text_buffer_remove_tag_by_name(
4707 buffer, "quote1", &startquote, &endquote);
4713 } else if (noq_offset != -1) {
4714 GtkTextIter startnoquote, endnoquote;
4715 gtk_text_buffer_get_iter_at_offset(
4716 buffer, &startnoquote, noq_offset);
4719 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4720 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4721 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4722 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4723 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4724 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4725 gtk_text_buffer_remove_tag_by_name(
4726 buffer, "quote0", &startnoquote, &endnoquote);
4727 gtk_text_buffer_remove_tag_by_name(
4728 buffer, "quote1", &startnoquote, &endnoquote);
4729 gtk_text_buffer_remove_tag_by_name(
4730 buffer, "quote2", &startnoquote, &endnoquote);
4736 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4737 GtkTextIter nouri_start_iter, nouri_end_iter;
4738 gtk_text_buffer_get_iter_at_offset(
4739 buffer, &nouri_start_iter, nouri_start);
4740 gtk_text_buffer_get_iter_at_offset(
4741 buffer, &nouri_end_iter, nouri_stop);
4742 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4743 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4744 gtk_text_buffer_remove_tag_by_name(
4745 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4746 modified_before_remove = modified;
4751 if (uri_start >= 0 && uri_stop > 0) {
4752 GtkTextIter uri_start_iter, uri_end_iter, back;
4753 gtk_text_buffer_get_iter_at_offset(
4754 buffer, &uri_start_iter, uri_start);
4755 gtk_text_buffer_get_iter_at_offset(
4756 buffer, &uri_end_iter, uri_stop);
4757 back = uri_end_iter;
4758 gtk_text_iter_backward_char(&back);
4759 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4760 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4761 gtk_text_buffer_apply_tag_by_name(
4762 buffer, "link", &uri_start_iter, &uri_end_iter);
4764 if (removed && !modified_before_remove) {
4770 /* debug_print("not modified, out after %d lines\n", lines); */
4774 /* debug_print("modified, out after %d lines\n", lines); */
4776 g_free(itemized_chars);
4779 undo_wrapping(compose->undostruct, FALSE);
4780 compose->autowrap = prev_autowrap;
4785 void compose_action_cb(void *data)
4787 Compose *compose = (Compose *)data;
4788 compose_wrap_all(compose);
4791 static void compose_wrap_all(Compose *compose)
4793 compose_wrap_all_full(compose, FALSE);
4796 static void compose_wrap_all_full(Compose *compose, gboolean force)
4798 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4799 GtkTextBuffer *buffer;
4801 gboolean modified = TRUE;
4803 buffer = gtk_text_view_get_buffer(text);
4805 gtk_text_buffer_get_start_iter(buffer, &iter);
4807 undo_wrapping(compose->undostruct, TRUE);
4809 while (!gtk_text_iter_is_end(&iter) && modified)
4810 modified = compose_beautify_paragraph(compose, &iter, force);
4812 undo_wrapping(compose->undostruct, FALSE);
4816 static void compose_set_title(Compose *compose)
4822 edited = compose->modified ? _(" [Edited]") : "";
4824 subject = gtk_editable_get_chars(
4825 GTK_EDITABLE(compose->subject_entry), 0, -1);
4827 #ifndef GENERIC_UMPC
4828 if (subject && strlen(subject))
4829 str = g_strdup_printf(_("%s - Compose message%s"),
4832 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4834 str = g_strdup(_("Compose message"));
4837 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4843 * compose_current_mail_account:
4845 * Find a current mail account (the currently selected account, or the
4846 * default account, if a news account is currently selected). If a
4847 * mail account cannot be found, display an error message.
4849 * Return value: Mail account, or NULL if not found.
4851 static PrefsAccount *
4852 compose_current_mail_account(void)
4856 if (cur_account && cur_account->protocol != A_NNTP)
4859 ac = account_get_default();
4860 if (!ac || ac->protocol == A_NNTP) {
4861 alertpanel_error(_("Account for sending mail is not specified.\n"
4862 "Please select a mail account before sending."));
4869 #define QUOTE_IF_REQUIRED(out, str) \
4871 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4875 len = strlen(str) + 3; \
4876 if ((__tmp = alloca(len)) == NULL) { \
4877 g_warning("can't allocate memory"); \
4878 g_string_free(header, TRUE); \
4881 g_snprintf(__tmp, len, "\"%s\"", str); \
4886 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4887 g_warning("can't allocate memory"); \
4888 g_string_free(header, TRUE); \
4891 strcpy(__tmp, str); \
4897 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4899 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4903 len = strlen(str) + 3; \
4904 if ((__tmp = alloca(len)) == NULL) { \
4905 g_warning("can't allocate memory"); \
4908 g_snprintf(__tmp, len, "\"%s\"", str); \
4913 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4914 g_warning("can't allocate memory"); \
4917 strcpy(__tmp, str); \
4923 static void compose_select_account(Compose *compose, PrefsAccount *account,
4926 gchar *from = NULL, *header = NULL;
4927 ComposeHeaderEntry *header_entry;
4930 cm_return_if_fail(account != NULL);
4932 compose->account = account;
4933 if (account->name && *account->name) {
4935 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4936 qbuf = escape_internal_quotes(buf, '"');
4937 from = g_strdup_printf("%s <%s>",
4938 qbuf, account->address);
4941 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4943 from = g_strdup_printf("<%s>",
4945 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4950 compose_set_title(compose);
4952 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4953 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4955 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4956 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4957 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4959 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4961 compose_activate_privacy_system(compose, account, FALSE);
4963 if (!init && compose->mode != COMPOSE_REDIRECT) {
4964 undo_block(compose->undostruct);
4965 compose_insert_sig(compose, TRUE);
4966 undo_unblock(compose->undostruct);
4969 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4970 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4971 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4972 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4974 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4975 if (account->protocol == A_NNTP) {
4976 if (!strcmp(header, _("To:")))
4977 combobox_select_by_text(
4978 GTK_COMBO_BOX(header_entry->combo),
4981 if (!strcmp(header, _("Newsgroups:")))
4982 combobox_select_by_text(
4983 GTK_COMBO_BOX(header_entry->combo),
4991 /* use account's dict info if set */
4992 if (compose->gtkaspell) {
4993 if (account->enable_default_dictionary)
4994 gtkaspell_change_dict(compose->gtkaspell,
4995 account->default_dictionary, FALSE);
4996 if (account->enable_default_alt_dictionary)
4997 gtkaspell_change_alt_dict(compose->gtkaspell,
4998 account->default_alt_dictionary);
4999 if (account->enable_default_dictionary
5000 || account->enable_default_alt_dictionary)
5001 compose_spell_menu_changed(compose);
5006 gboolean compose_check_for_valid_recipient(Compose *compose) {
5007 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5008 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5009 gboolean recipient_found = FALSE;
5013 /* free to and newsgroup list */
5014 slist_free_strings_full(compose->to_list);
5015 compose->to_list = NULL;
5017 slist_free_strings_full(compose->newsgroup_list);
5018 compose->newsgroup_list = NULL;
5020 /* search header entries for to and newsgroup entries */
5021 for (list = compose->header_list; list; list = list->next) {
5024 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5025 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5028 if (entry[0] != '\0') {
5029 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5030 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5031 compose->to_list = address_list_append(compose->to_list, entry);
5032 recipient_found = TRUE;
5035 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5036 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5037 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5038 recipient_found = TRUE;
5045 return recipient_found;
5048 static gboolean compose_check_for_set_recipients(Compose *compose)
5050 if (compose->account->set_autocc && compose->account->auto_cc) {
5051 gboolean found_other = FALSE;
5053 /* search header entries for to and newsgroup entries */
5054 for (list = compose->header_list; list; list = list->next) {
5057 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5058 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5061 if (strcmp(entry, compose->account->auto_cc)
5062 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5073 if (compose->batch) {
5074 gtk_widget_show_all(compose->window);
5076 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5077 prefs_common_translated_header_name("Cc"));
5078 aval = alertpanel(_("Send"),
5080 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5082 if (aval != G_ALERTALTERNATE)
5086 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5087 gboolean found_other = FALSE;
5089 /* search header entries for to and newsgroup entries */
5090 for (list = compose->header_list; list; list = list->next) {
5093 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5094 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5097 if (strcmp(entry, compose->account->auto_bcc)
5098 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5110 if (compose->batch) {
5111 gtk_widget_show_all(compose->window);
5113 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5114 prefs_common_translated_header_name("Bcc"));
5115 aval = alertpanel(_("Send"),
5117 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5119 if (aval != G_ALERTALTERNATE)
5126 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5130 if (compose_check_for_valid_recipient(compose) == FALSE) {
5131 if (compose->batch) {
5132 gtk_widget_show_all(compose->window);
5134 alertpanel_error(_("Recipient is not specified."));
5138 if (compose_check_for_set_recipients(compose) == FALSE) {
5142 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5143 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5144 if (*str == '\0' && check_everything == TRUE &&
5145 compose->mode != COMPOSE_REDIRECT) {
5149 message = g_strdup_printf(_("Subject is empty. %s"),
5150 compose->sending?_("Send it anyway?"):
5151 _("Queue it anyway?"));
5153 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5154 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5155 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5157 if (aval & G_ALERTDISABLE) {
5158 aval &= ~G_ALERTDISABLE;
5159 prefs_common.warn_empty_subj = FALSE;
5161 if (aval != G_ALERTALTERNATE)
5166 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5167 && check_everything == TRUE) {
5171 /* count To and Cc recipients */
5172 for (list = compose->header_list; list; list = list->next) {
5176 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5177 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5180 if ((entry[0] != '\0') &&
5181 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5182 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5188 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5192 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5193 compose->sending?_("Send it anyway?"):
5194 _("Queue it anyway?"));
5196 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5197 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5198 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5200 if (aval & G_ALERTDISABLE) {
5201 aval &= ~G_ALERTDISABLE;
5202 prefs_common.warn_sending_many_recipients_num = 0;
5204 if (aval != G_ALERTALTERNATE)
5209 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5215 static void _display_queue_error(ComposeQueueResult val)
5218 case COMPOSE_QUEUE_SUCCESS:
5220 case COMPOSE_QUEUE_ERROR_NO_MSG:
5221 alertpanel_error(_("Could not queue message."));
5223 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5224 alertpanel_error(_("Could not queue message:\n\n%s."),
5227 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5228 alertpanel_error(_("Could not queue message for sending:\n\n"
5229 "Signature failed: %s"),
5230 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5232 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5233 alertpanel_error(_("Could not queue message for sending:\n\n"
5234 "Encryption failed: %s"),
5235 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5237 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5238 alertpanel_error(_("Could not queue message for sending:\n\n"
5239 "Charset conversion failed."));
5241 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5242 alertpanel_error(_("Could not queue message for sending:\n\n"
5243 "Couldn't get recipient encryption key."));
5246 /* unhandled error */
5247 debug_print("oops, unhandled compose_queue() return value %d\n",
5253 gint compose_send(Compose *compose)
5256 FolderItem *folder = NULL;
5257 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5258 gchar *msgpath = NULL;
5259 gboolean discard_window = FALSE;
5260 gchar *errstr = NULL;
5261 gchar *tmsgid = NULL;
5262 MainWindow *mainwin = mainwindow_get_mainwindow();
5263 gboolean queued_removed = FALSE;
5265 if (prefs_common.send_dialog_invisible
5266 || compose->batch == TRUE)
5267 discard_window = TRUE;
5269 compose_allow_user_actions (compose, FALSE);
5270 compose->sending = TRUE;
5272 if (compose_check_entries(compose, TRUE) == FALSE) {
5273 if (compose->batch) {
5274 gtk_widget_show_all(compose->window);
5280 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5282 if (val != COMPOSE_QUEUE_SUCCESS) {
5283 if (compose->batch) {
5284 gtk_widget_show_all(compose->window);
5287 _display_queue_error(val);
5292 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5293 if (discard_window) {
5294 compose->sending = FALSE;
5295 compose_close(compose);
5296 /* No more compose access in the normal codepath
5297 * after this point! */
5302 alertpanel_error(_("The message was queued but could not be "
5303 "sent.\nUse \"Send queued messages\" from "
5304 "the main window to retry."));
5305 if (!discard_window) {
5312 if (msgpath == NULL) {
5313 msgpath = folder_item_fetch_msg(folder, msgnum);
5314 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5317 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5318 claws_unlink(msgpath);
5321 if (!discard_window) {
5323 if (!queued_removed)
5324 folder_item_remove_msg(folder, msgnum);
5325 folder_item_scan(folder);
5327 /* make sure we delete that */
5328 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5330 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5331 folder_item_remove_msg(folder, tmp->msgnum);
5332 procmsg_msginfo_free(&tmp);
5339 if (!queued_removed)
5340 folder_item_remove_msg(folder, msgnum);
5341 folder_item_scan(folder);
5343 /* make sure we delete that */
5344 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5346 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5347 folder_item_remove_msg(folder, tmp->msgnum);
5348 procmsg_msginfo_free(&tmp);
5351 if (!discard_window) {
5352 compose->sending = FALSE;
5353 compose_allow_user_actions (compose, TRUE);
5354 compose_close(compose);
5358 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5359 "the main window to retry."), errstr);
5362 alertpanel_error_log(_("The message was queued but could not be "
5363 "sent.\nUse \"Send queued messages\" from "
5364 "the main window to retry."));
5366 if (!discard_window) {
5375 toolbar_main_set_sensitive(mainwin);
5376 main_window_set_menu_sensitive(mainwin);
5382 compose_allow_user_actions (compose, TRUE);
5383 compose->sending = FALSE;
5384 compose->modified = TRUE;
5385 toolbar_main_set_sensitive(mainwin);
5386 main_window_set_menu_sensitive(mainwin);
5391 static gboolean compose_use_attach(Compose *compose)
5393 GtkTreeModel *model = gtk_tree_view_get_model
5394 (GTK_TREE_VIEW(compose->attach_clist));
5395 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5398 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5401 gchar buf[BUFFSIZE];
5403 gboolean first_to_address;
5404 gboolean first_cc_address;
5406 ComposeHeaderEntry *headerentry;
5407 const gchar *headerentryname;
5408 const gchar *cc_hdr;
5409 const gchar *to_hdr;
5410 gboolean err = FALSE;
5412 debug_print("Writing redirect header\n");
5414 cc_hdr = prefs_common_translated_header_name("Cc:");
5415 to_hdr = prefs_common_translated_header_name("To:");
5417 first_to_address = TRUE;
5418 for (list = compose->header_list; list; list = list->next) {
5419 headerentry = ((ComposeHeaderEntry *)list->data);
5420 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5422 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5423 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5424 Xstrdup_a(str, entstr, return -1);
5426 if (str[0] != '\0') {
5427 compose_convert_header
5428 (compose, buf, sizeof(buf), str,
5429 strlen("Resent-To") + 2, TRUE);
5431 if (first_to_address) {
5432 err |= (fprintf(fp, "Resent-To: ") < 0);
5433 first_to_address = FALSE;
5435 err |= (fprintf(fp, ",") < 0);
5437 err |= (fprintf(fp, "%s", buf) < 0);
5441 if (!first_to_address) {
5442 err |= (fprintf(fp, "\n") < 0);
5445 first_cc_address = TRUE;
5446 for (list = compose->header_list; list; list = list->next) {
5447 headerentry = ((ComposeHeaderEntry *)list->data);
5448 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5450 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5451 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5452 Xstrdup_a(str, strg, return -1);
5454 if (str[0] != '\0') {
5455 compose_convert_header
5456 (compose, buf, sizeof(buf), str,
5457 strlen("Resent-Cc") + 2, TRUE);
5459 if (first_cc_address) {
5460 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5461 first_cc_address = FALSE;
5463 err |= (fprintf(fp, ",") < 0);
5465 err |= (fprintf(fp, "%s", buf) < 0);
5469 if (!first_cc_address) {
5470 err |= (fprintf(fp, "\n") < 0);
5473 return (err ? -1:0);
5476 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5478 gchar date[RFC822_DATE_BUFFSIZE];
5479 gchar buf[BUFFSIZE];
5481 const gchar *entstr;
5482 /* struct utsname utsbuf; */
5483 gboolean err = FALSE;
5485 cm_return_val_if_fail(fp != NULL, -1);
5486 cm_return_val_if_fail(compose->account != NULL, -1);
5487 cm_return_val_if_fail(compose->account->address != NULL, -1);
5490 if (prefs_common.hide_timezone)
5491 get_rfc822_date_hide_tz(date, sizeof(date));
5493 get_rfc822_date(date, sizeof(date));
5494 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5497 if (compose->account->name && *compose->account->name) {
5498 compose_convert_header
5499 (compose, buf, sizeof(buf), compose->account->name,
5500 strlen("From: "), TRUE);
5501 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5502 buf, compose->account->address) < 0);
5504 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5507 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5508 if (*entstr != '\0') {
5509 Xstrdup_a(str, entstr, return -1);
5512 compose_convert_header(compose, buf, sizeof(buf), str,
5513 strlen("Subject: "), FALSE);
5514 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5518 /* Resent-Message-ID */
5519 if (compose->account->gen_msgid) {
5520 gchar *addr = prefs_account_generate_msgid(compose->account);
5521 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5523 g_free(compose->msgid);
5524 compose->msgid = addr;
5526 compose->msgid = NULL;
5529 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5532 /* separator between header and body */
5533 err |= (fputs("\n", fp) == EOF);
5535 return (err ? -1:0);
5538 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5543 gchar rewrite_buf[BUFFSIZE];
5545 gboolean skip = FALSE;
5546 gboolean err = FALSE;
5547 gchar *not_included[]={
5548 "Return-Path:", "Delivered-To:", "Received:",
5549 "Subject:", "X-UIDL:", "AF:",
5550 "NF:", "PS:", "SRH:",
5551 "SFN:", "DSR:", "MID:",
5552 "CFG:", "PT:", "S:",
5553 "RQ:", "SSV:", "NSV:",
5554 "SSH:", "R:", "MAID:",
5555 "NAID:", "RMID:", "FMID:",
5556 "SCF:", "RRCPT:", "NG:",
5557 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5558 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5559 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5560 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5561 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5566 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5567 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5571 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5573 for (i = 0; not_included[i] != NULL; i++) {
5574 if (g_ascii_strncasecmp(buf, not_included[i],
5575 strlen(not_included[i])) == 0) {
5585 if (fputs(buf, fdest) == -1) {
5591 if (!prefs_common.redirect_keep_from) {
5592 if (g_ascii_strncasecmp(buf, "From:",
5593 strlen("From:")) == 0) {
5594 err |= (fputs(" (by way of ", fdest) == EOF);
5595 if (compose->account->name
5596 && *compose->account->name) {
5597 gchar buffer[BUFFSIZE];
5599 compose_convert_header
5600 (compose, buffer, sizeof(buffer),
5601 compose->account->name,
5604 err |= (fprintf(fdest, "%s <%s>",
5606 compose->account->address) < 0);
5608 err |= (fprintf(fdest, "%s",
5609 compose->account->address) < 0);
5610 err |= (fputs(")", fdest) == EOF);
5616 if (fputs("\n", fdest) == -1)
5623 if (compose_redirect_write_headers(compose, fdest))
5626 while ((len = fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5627 if (fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5641 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5643 GtkTextBuffer *buffer;
5644 GtkTextIter start, end, tmp;
5645 gchar *chars, *tmp_enc_file, *content;
5647 const gchar *out_codeset;
5648 EncodingType encoding = ENC_UNKNOWN;
5649 MimeInfo *mimemsg, *mimetext;
5651 const gchar *src_codeset = CS_INTERNAL;
5652 gchar *from_addr = NULL;
5653 gchar *from_name = NULL;
5656 if (action == COMPOSE_WRITE_FOR_SEND) {
5657 attach_parts = TRUE;
5659 /* We're sending the message, generate a Message-ID
5661 if (compose->msgid == NULL &&
5662 compose->account->gen_msgid) {
5663 compose->msgid = prefs_account_generate_msgid(compose->account);
5667 /* create message MimeInfo */
5668 mimemsg = procmime_mimeinfo_new();
5669 mimemsg->type = MIMETYPE_MESSAGE;
5670 mimemsg->subtype = g_strdup("rfc822");
5671 mimemsg->content = MIMECONTENT_MEM;
5672 mimemsg->tmp = TRUE; /* must free content later */
5673 mimemsg->data.mem = compose_get_header(compose);
5675 /* Create text part MimeInfo */
5676 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5677 gtk_text_buffer_get_end_iter(buffer, &end);
5680 /* We make sure that there is a newline at the end. */
5681 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5682 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5683 if (*chars != '\n') {
5684 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5689 /* get all composed text */
5690 gtk_text_buffer_get_start_iter(buffer, &start);
5691 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5693 out_codeset = conv_get_charset_str(compose->out_encoding);
5695 if (!out_codeset && is_ascii_str(chars)) {
5696 out_codeset = CS_US_ASCII;
5697 } else if (prefs_common.outgoing_fallback_to_ascii &&
5698 is_ascii_str(chars)) {
5699 out_codeset = CS_US_ASCII;
5700 encoding = ENC_7BIT;
5704 gchar *test_conv_global_out = NULL;
5705 gchar *test_conv_reply = NULL;
5707 /* automatic mode. be automatic. */
5708 codeconv_set_strict(TRUE);
5710 out_codeset = conv_get_outgoing_charset_str();
5712 debug_print("trying to convert to %s\n", out_codeset);
5713 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5716 if (!test_conv_global_out && compose->orig_charset
5717 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5718 out_codeset = compose->orig_charset;
5719 debug_print("failure; trying to convert to %s\n", out_codeset);
5720 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5723 if (!test_conv_global_out && !test_conv_reply) {
5725 out_codeset = CS_INTERNAL;
5726 debug_print("failure; finally using %s\n", out_codeset);
5728 g_free(test_conv_global_out);
5729 g_free(test_conv_reply);
5730 codeconv_set_strict(FALSE);
5733 if (encoding == ENC_UNKNOWN) {
5734 if (prefs_common.encoding_method == CTE_BASE64)
5735 encoding = ENC_BASE64;
5736 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5737 encoding = ENC_QUOTED_PRINTABLE;
5738 else if (prefs_common.encoding_method == CTE_8BIT)
5739 encoding = ENC_8BIT;
5741 encoding = procmime_get_encoding_for_charset(out_codeset);
5744 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5745 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5747 if (action == COMPOSE_WRITE_FOR_SEND) {
5748 codeconv_set_strict(TRUE);
5749 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5750 codeconv_set_strict(FALSE);
5755 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5756 "to the specified %s charset.\n"
5757 "Send it as %s?"), out_codeset, src_codeset);
5758 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5759 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5763 if (aval != G_ALERTALTERNATE) {
5765 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5768 out_codeset = src_codeset;
5774 out_codeset = src_codeset;
5779 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5780 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5781 strstr(buf, "\nFrom ") != NULL) {
5782 encoding = ENC_QUOTED_PRINTABLE;
5786 mimetext = procmime_mimeinfo_new();
5787 mimetext->content = MIMECONTENT_MEM;
5788 mimetext->tmp = TRUE; /* must free content later */
5789 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5790 * and free the data, which we need later. */
5791 mimetext->data.mem = g_strdup(buf);
5792 mimetext->type = MIMETYPE_TEXT;
5793 mimetext->subtype = g_strdup("plain");
5794 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5795 g_strdup(out_codeset));
5797 /* protect trailing spaces when signing message */
5798 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5799 privacy_system_can_sign(compose->privacy_system)) {
5800 encoding = ENC_QUOTED_PRINTABLE;
5804 debug_print("main text: %Id bytes encoded as %s in %d\n",
5806 debug_print("main text: %zd bytes encoded as %s in %d\n",
5808 strlen(buf), out_codeset, encoding);
5810 /* check for line length limit */
5811 if (action == COMPOSE_WRITE_FOR_SEND &&
5812 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5813 check_line_length(buf, 1000, &line) < 0) {
5816 msg = g_strdup_printf
5817 (_("Line %d exceeds the line length limit (998 bytes).\n"
5818 "The contents of the message might be broken on the way to the delivery.\n"
5820 "Send it anyway?"), line + 1);
5821 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5824 if (aval != G_ALERTALTERNATE) {
5826 return COMPOSE_QUEUE_ERROR_NO_MSG;
5830 if (encoding != ENC_UNKNOWN)
5831 procmime_encode_content(mimetext, encoding);
5833 /* append attachment parts */
5834 if (compose_use_attach(compose) && attach_parts) {
5835 MimeInfo *mimempart;
5836 gchar *boundary = NULL;
5837 mimempart = procmime_mimeinfo_new();
5838 mimempart->content = MIMECONTENT_EMPTY;
5839 mimempart->type = MIMETYPE_MULTIPART;
5840 mimempart->subtype = g_strdup("mixed");
5844 boundary = generate_mime_boundary(NULL);
5845 } while (strstr(buf, boundary) != NULL);
5847 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5850 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5852 g_node_append(mimempart->node, mimetext->node);
5853 g_node_append(mimemsg->node, mimempart->node);
5855 if (compose_add_attachments(compose, mimempart) < 0)
5856 return COMPOSE_QUEUE_ERROR_NO_MSG;
5858 g_node_append(mimemsg->node, mimetext->node);
5862 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5863 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5864 /* extract name and address */
5865 if (strstr(spec, " <") && strstr(spec, ">")) {
5866 from_addr = g_strdup(strrchr(spec, '<')+1);
5867 *(strrchr(from_addr, '>')) = '\0';
5868 from_name = g_strdup(spec);
5869 *(strrchr(from_name, '<')) = '\0';
5876 /* sign message if sending */
5877 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5878 privacy_system_can_sign(compose->privacy_system))
5879 if (!privacy_sign(compose->privacy_system, mimemsg,
5880 compose->account, from_addr)) {
5883 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5888 if (compose->use_encryption) {
5889 if (compose->encdata != NULL &&
5890 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5892 /* First, write an unencrypted copy and save it to outbox, if
5893 * user wants that. */
5894 if (compose->account->save_encrypted_as_clear_text) {
5895 debug_print("saving sent message unencrypted...\n");
5896 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5900 /* fp now points to a file with headers written,
5901 * let's make a copy. */
5903 content = file_read_stream_to_str(fp);
5905 str_write_to_file(content, tmp_enc_file);
5908 /* Now write the unencrypted body. */
5909 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5910 procmime_write_mimeinfo(mimemsg, tmpfp);
5913 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5915 outbox = folder_get_default_outbox();
5917 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5918 claws_unlink(tmp_enc_file);
5920 g_warning("Can't open file '%s'", tmp_enc_file);
5923 g_warning("couldn't get tempfile");
5926 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5927 debug_print("Couldn't encrypt mime structure: %s.\n",
5928 privacy_get_error());
5929 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5934 procmime_write_mimeinfo(mimemsg, fp);
5936 procmime_mimeinfo_free_all(&mimemsg);
5941 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5943 GtkTextBuffer *buffer;
5944 GtkTextIter start, end;
5949 if ((fp = g_fopen(file, "wb")) == NULL) {
5950 FILE_OP_ERROR(file, "fopen");
5954 /* chmod for security */
5955 if (change_file_mode_rw(fp, file) < 0) {
5956 FILE_OP_ERROR(file, "chmod");
5957 g_warning("can't change file mode");
5960 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5961 gtk_text_buffer_get_start_iter(buffer, &start);
5962 gtk_text_buffer_get_end_iter(buffer, &end);
5963 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5965 chars = conv_codeset_strdup
5966 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5975 len = strlen(chars);
5976 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5977 FILE_OP_ERROR(file, "fwrite");
5986 if (fclose(fp) == EOF) {
5987 FILE_OP_ERROR(file, "fclose");
5994 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5997 MsgInfo *msginfo = compose->targetinfo;
5999 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6000 if (!msginfo) return -1;
6002 if (!force && MSG_IS_LOCKED(msginfo->flags))
6005 item = msginfo->folder;
6006 cm_return_val_if_fail(item != NULL, -1);
6008 if (procmsg_msg_exist(msginfo) &&
6009 (folder_has_parent_of_type(item, F_QUEUE) ||
6010 folder_has_parent_of_type(item, F_DRAFT)
6011 || msginfo == compose->autosaved_draft)) {
6012 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6013 g_warning("can't remove the old message");
6016 debug_print("removed reedit target %d\n", msginfo->msgnum);
6023 static void compose_remove_draft(Compose *compose)
6026 MsgInfo *msginfo = compose->targetinfo;
6027 drafts = account_get_special_folder(compose->account, F_DRAFT);
6029 if (procmsg_msg_exist(msginfo)) {
6030 folder_item_remove_msg(drafts, msginfo->msgnum);
6035 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6036 gboolean remove_reedit_target)
6038 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6041 static gboolean compose_warn_encryption(Compose *compose)
6043 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6044 AlertValue val = G_ALERTALTERNATE;
6046 if (warning == NULL)
6049 val = alertpanel_full(_("Encryption warning"), warning,
6050 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6051 TRUE, NULL, ALERT_WARNING);
6052 if (val & G_ALERTDISABLE) {
6053 val &= ~G_ALERTDISABLE;
6054 if (val == G_ALERTALTERNATE)
6055 privacy_inhibit_encrypt_warning(compose->privacy_system,
6059 if (val == G_ALERTALTERNATE) {
6066 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6067 gchar **msgpath, gboolean perform_checks,
6068 gboolean remove_reedit_target)
6075 PrefsAccount *mailac = NULL, *newsac = NULL;
6076 gboolean err = FALSE;
6078 debug_print("queueing message...\n");
6079 cm_return_val_if_fail(compose->account != NULL, -1);
6081 if (compose_check_entries(compose, perform_checks) == FALSE) {
6082 if (compose->batch) {
6083 gtk_widget_show_all(compose->window);
6085 return COMPOSE_QUEUE_ERROR_NO_MSG;
6088 if (!compose->to_list && !compose->newsgroup_list) {
6089 g_warning("can't get recipient list.");
6090 return COMPOSE_QUEUE_ERROR_NO_MSG;
6093 if (compose->to_list) {
6094 if (compose->account->protocol != A_NNTP)
6095 mailac = compose->account;
6096 else if (cur_account && cur_account->protocol != A_NNTP)
6097 mailac = cur_account;
6098 else if (!(mailac = compose_current_mail_account())) {
6099 alertpanel_error(_("No account for sending mails available!"));
6100 return COMPOSE_QUEUE_ERROR_NO_MSG;
6104 if (compose->newsgroup_list) {
6105 if (compose->account->protocol == A_NNTP)
6106 newsac = compose->account;
6108 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6109 return COMPOSE_QUEUE_ERROR_NO_MSG;
6113 /* write queue header */
6114 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6115 G_DIR_SEPARATOR, compose, (guint) rand());
6116 debug_print("queuing to %s\n", tmp);
6117 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
6118 FILE_OP_ERROR(tmp, "fopen");
6120 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6123 if (change_file_mode_rw(fp, tmp) < 0) {
6124 FILE_OP_ERROR(tmp, "chmod");
6125 g_warning("can't change file mode");
6128 /* queueing variables */
6129 err |= (fprintf(fp, "AF:\n") < 0);
6130 err |= (fprintf(fp, "NF:0\n") < 0);
6131 err |= (fprintf(fp, "PS:10\n") < 0);
6132 err |= (fprintf(fp, "SRH:1\n") < 0);
6133 err |= (fprintf(fp, "SFN:\n") < 0);
6134 err |= (fprintf(fp, "DSR:\n") < 0);
6136 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6138 err |= (fprintf(fp, "MID:\n") < 0);
6139 err |= (fprintf(fp, "CFG:\n") < 0);
6140 err |= (fprintf(fp, "PT:0\n") < 0);
6141 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6142 err |= (fprintf(fp, "RQ:\n") < 0);
6144 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6146 err |= (fprintf(fp, "SSV:\n") < 0);
6148 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6150 err |= (fprintf(fp, "NSV:\n") < 0);
6151 err |= (fprintf(fp, "SSH:\n") < 0);
6152 /* write recipient list */
6153 if (compose->to_list) {
6154 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6155 for (cur = compose->to_list->next; cur != NULL;
6157 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6158 err |= (fprintf(fp, "\n") < 0);
6160 /* write newsgroup list */
6161 if (compose->newsgroup_list) {
6162 err |= (fprintf(fp, "NG:") < 0);
6163 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6164 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6165 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6166 err |= (fprintf(fp, "\n") < 0);
6170 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6172 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6175 if (compose->privacy_system != NULL) {
6176 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6177 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6178 if (compose->use_encryption) {
6179 if (!compose_warn_encryption(compose)) {
6183 return COMPOSE_QUEUE_ERROR_NO_MSG;
6185 if (mailac && mailac->encrypt_to_self) {
6186 GSList *tmp_list = g_slist_copy(compose->to_list);
6187 tmp_list = g_slist_append(tmp_list, compose->account->address);
6188 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6189 g_slist_free(tmp_list);
6191 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6193 if (compose->encdata != NULL) {
6194 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6195 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6196 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6197 compose->encdata) < 0);
6198 } /* else we finally dont want to encrypt */
6200 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6201 /* and if encdata was null, it means there's been a problem in
6204 g_warning("failed to write queue message");
6208 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6213 /* Save copy folder */
6214 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6215 gchar *savefolderid;
6217 savefolderid = compose_get_save_to(compose);
6218 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6219 g_free(savefolderid);
6221 /* Save copy folder */
6222 if (compose->return_receipt) {
6223 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6225 /* Message-ID of message replying to */
6226 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6227 gchar *folderid = NULL;
6229 if (compose->replyinfo->folder)
6230 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6231 if (folderid == NULL)
6232 folderid = g_strdup("NULL");
6234 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6237 /* Message-ID of message forwarding to */
6238 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6239 gchar *folderid = NULL;
6241 if (compose->fwdinfo->folder)
6242 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6243 if (folderid == NULL)
6244 folderid = g_strdup("NULL");
6246 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6250 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6251 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6253 /* end of headers */
6254 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6256 if (compose->redirect_filename != NULL) {
6257 if (compose_redirect_write_to_file(compose, fp) < 0) {
6261 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6265 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6273 g_warning("failed to write queue message");
6277 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6279 if (fclose(fp) == EOF) {
6280 FILE_OP_ERROR(tmp, "fclose");
6283 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6286 if (item && *item) {
6289 queue = account_get_special_folder(compose->account, F_QUEUE);
6292 g_warning("can't find queue folder");
6295 return COMPOSE_QUEUE_ERROR_NO_MSG;
6297 folder_item_scan(queue);
6298 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6299 g_warning("can't queue the message");
6302 return COMPOSE_QUEUE_ERROR_NO_MSG;
6305 if (msgpath == NULL) {
6311 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6312 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6314 procmsg_msginfo_change_flags(mi,
6315 compose->targetinfo->flags.perm_flags,
6316 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6319 g_slist_free(mi->tags);
6320 mi->tags = g_slist_copy(compose->targetinfo->tags);
6321 procmsg_msginfo_free(&mi);
6325 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6326 compose_remove_reedit_target(compose, FALSE);
6329 if ((msgnum != NULL) && (item != NULL)) {
6334 return COMPOSE_QUEUE_SUCCESS;
6337 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6340 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6345 GError *error = NULL;
6350 gchar *type, *subtype;
6351 GtkTreeModel *model;
6354 model = gtk_tree_view_get_model(tree_view);
6356 if (!gtk_tree_model_get_iter_first(model, &iter))
6359 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6361 if (!is_file_exist(ainfo->file)) {
6362 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6363 AlertValue val = alertpanel_full(_("Warning"), msg,
6364 _("Cancel sending"), _("Ignore attachment"), NULL,
6365 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6367 if (val == G_ALERTDEFAULT) {
6373 f = g_file_new_for_path(ainfo->file);
6374 fi = g_file_query_info(f, "standard::size",
6375 G_FILE_QUERY_INFO_NONE, NULL, &error);
6376 if (error != NULL) {
6377 g_warning(error->message);
6378 g_error_free(error);
6382 size = g_file_info_get_size(fi);
6386 if (g_stat(ainfo->file, &statbuf) < 0)
6388 size = statbuf.st_size;
6391 mimepart = procmime_mimeinfo_new();
6392 mimepart->content = MIMECONTENT_FILE;
6393 mimepart->data.filename = g_strdup(ainfo->file);
6394 mimepart->tmp = FALSE; /* or we destroy our attachment */
6395 mimepart->offset = 0;
6396 mimepart->length = size;
6398 type = g_strdup(ainfo->content_type);
6400 if (!strchr(type, '/')) {
6402 type = g_strdup("application/octet-stream");
6405 subtype = strchr(type, '/') + 1;
6406 *(subtype - 1) = '\0';
6407 mimepart->type = procmime_get_media_type(type);
6408 mimepart->subtype = g_strdup(subtype);
6411 if (mimepart->type == MIMETYPE_MESSAGE &&
6412 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6413 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6414 } else if (mimepart->type == MIMETYPE_TEXT) {
6415 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6416 /* Text parts with no name come from multipart/alternative
6417 * forwards. Make sure the recipient won't look at the
6418 * original HTML part by mistake. */
6419 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6420 ainfo->name = g_strdup_printf(_("Original %s part"),
6424 g_hash_table_insert(mimepart->typeparameters,
6425 g_strdup("charset"), g_strdup(ainfo->charset));
6427 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6428 if (mimepart->type == MIMETYPE_APPLICATION &&
6429 !strcmp2(mimepart->subtype, "octet-stream"))
6430 g_hash_table_insert(mimepart->typeparameters,
6431 g_strdup("name"), g_strdup(ainfo->name));
6432 g_hash_table_insert(mimepart->dispositionparameters,
6433 g_strdup("filename"), g_strdup(ainfo->name));
6434 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6437 if (mimepart->type == MIMETYPE_MESSAGE
6438 || mimepart->type == MIMETYPE_MULTIPART)
6439 ainfo->encoding = ENC_BINARY;
6440 else if (compose->use_signing) {
6441 if (ainfo->encoding == ENC_7BIT)
6442 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6443 else if (ainfo->encoding == ENC_8BIT)
6444 ainfo->encoding = ENC_BASE64;
6447 procmime_encode_content(mimepart, ainfo->encoding);
6449 g_node_append(parent->node, mimepart->node);
6450 } while (gtk_tree_model_iter_next(model, &iter));
6455 static gchar *compose_quote_list_of_addresses(gchar *str)
6457 GSList *list = NULL, *item = NULL;
6458 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6460 list = address_list_append_with_comments(list, str);
6461 for (item = list; item != NULL; item = item->next) {
6462 gchar *spec = item->data;
6463 gchar *endofname = strstr(spec, " <");
6464 if (endofname != NULL) {
6467 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6468 qqname = escape_internal_quotes(qname, '"');
6470 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6471 gchar *addr = g_strdup(endofname);
6472 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6473 faddr = g_strconcat(name, addr, NULL);
6476 debug_print("new auto-quoted address: '%s'\n", faddr);
6480 result = g_strdup((faddr != NULL)? faddr: spec);
6482 result = g_strconcat(result,
6484 (faddr != NULL)? faddr: spec,
6487 if (faddr != NULL) {
6492 slist_free_strings_full(list);
6497 #define IS_IN_CUSTOM_HEADER(header) \
6498 (compose->account->add_customhdr && \
6499 custom_header_find(compose->account->customhdr_list, header) != NULL)
6501 static const gchar *compose_untranslated_header_name(gchar *header_name)
6503 /* return the untranslated header name, if header_name is a known
6504 header name, in either its translated or untranslated form, with
6505 or without trailing colon. otherwise, returns header_name. */
6506 gchar *translated_header_name;
6507 gchar *translated_header_name_wcolon;
6508 const gchar *untranslated_header_name;
6509 const gchar *untranslated_header_name_wcolon;
6512 cm_return_val_if_fail(header_name != NULL, NULL);
6514 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6515 untranslated_header_name = HEADERS[i].header_name;
6516 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6518 translated_header_name = gettext(untranslated_header_name);
6519 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6521 if (!strcmp(header_name, untranslated_header_name) ||
6522 !strcmp(header_name, translated_header_name)) {
6523 return untranslated_header_name;
6525 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6526 !strcmp(header_name, translated_header_name_wcolon)) {
6527 return untranslated_header_name_wcolon;
6531 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6535 static void compose_add_headerfield_from_headerlist(Compose *compose,
6537 const gchar *fieldname,
6538 const gchar *seperator)
6540 gchar *str, *fieldname_w_colon;
6541 gboolean add_field = FALSE;
6543 ComposeHeaderEntry *headerentry;
6544 const gchar *headerentryname;
6545 const gchar *trans_fieldname;
6548 if (IS_IN_CUSTOM_HEADER(fieldname))
6551 debug_print("Adding %s-fields\n", fieldname);
6553 fieldstr = g_string_sized_new(64);
6555 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6556 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6558 for (list = compose->header_list; list; list = list->next) {
6559 headerentry = ((ComposeHeaderEntry *)list->data);
6560 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6562 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6563 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6565 str = compose_quote_list_of_addresses(ustr);
6567 if (str != NULL && str[0] != '\0') {
6569 g_string_append(fieldstr, seperator);
6570 g_string_append(fieldstr, str);
6579 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6580 compose_convert_header
6581 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6582 strlen(fieldname) + 2, TRUE);
6583 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6587 g_free(fieldname_w_colon);
6588 g_string_free(fieldstr, TRUE);
6593 static gchar *compose_get_manual_headers_info(Compose *compose)
6595 GString *sh_header = g_string_new(" ");
6597 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6599 for (list = compose->header_list; list; list = list->next) {
6600 ComposeHeaderEntry *headerentry;
6603 gchar *headername_wcolon;
6604 const gchar *headername_trans;
6606 gboolean standard_header = FALSE;
6608 headerentry = ((ComposeHeaderEntry *)list->data);
6610 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6612 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6617 if (!strstr(tmp, ":")) {
6618 headername_wcolon = g_strconcat(tmp, ":", NULL);
6619 headername = g_strdup(tmp);
6621 headername_wcolon = g_strdup(tmp);
6622 headername = g_strdup(strtok(tmp, ":"));
6626 string = std_headers;
6627 while (*string != NULL) {
6628 headername_trans = prefs_common_translated_header_name(*string);
6629 if (!strcmp(headername_trans, headername_wcolon))
6630 standard_header = TRUE;
6633 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6634 g_string_append_printf(sh_header, "%s ", headername);
6636 g_free(headername_wcolon);
6638 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6639 return g_string_free(sh_header, FALSE);
6642 static gchar *compose_get_header(Compose *compose)
6644 gchar date[RFC822_DATE_BUFFSIZE];
6645 gchar buf[BUFFSIZE];
6646 const gchar *entry_str;
6650 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6652 gchar *from_name = NULL, *from_address = NULL;
6655 cm_return_val_if_fail(compose->account != NULL, NULL);
6656 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6658 header = g_string_sized_new(64);
6661 if (prefs_common.hide_timezone)
6662 get_rfc822_date_hide_tz(date, sizeof(date));
6664 get_rfc822_date(date, sizeof(date));
6665 g_string_append_printf(header, "Date: %s\n", date);
6669 if (compose->account->name && *compose->account->name) {
6671 QUOTE_IF_REQUIRED(buf, compose->account->name);
6672 tmp = g_strdup_printf("%s <%s>",
6673 buf, compose->account->address);
6675 tmp = g_strdup_printf("%s",
6676 compose->account->address);
6678 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6679 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6681 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6682 from_address = g_strdup(compose->account->address);
6684 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6685 /* extract name and address */
6686 if (strstr(spec, " <") && strstr(spec, ">")) {
6687 from_address = g_strdup(strrchr(spec, '<')+1);
6688 *(strrchr(from_address, '>')) = '\0';
6689 from_name = g_strdup(spec);
6690 *(strrchr(from_name, '<')) = '\0';
6693 from_address = g_strdup(spec);
6700 if (from_name && *from_name) {
6702 compose_convert_header
6703 (compose, buf, sizeof(buf), from_name,
6704 strlen("From: "), TRUE);
6705 QUOTE_IF_REQUIRED(name, buf);
6706 qname = escape_internal_quotes(name, '"');
6708 g_string_append_printf(header, "From: %s <%s>\n",
6709 qname, from_address);
6710 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6711 compose->return_receipt) {
6712 compose_convert_header(compose, buf, sizeof(buf), from_name,
6713 strlen("Disposition-Notification-To: "),
6715 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6720 g_string_append_printf(header, "From: %s\n", from_address);
6721 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6722 compose->return_receipt)
6723 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6727 g_free(from_address);
6730 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6733 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6736 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6740 * If this account is a NNTP account remove Bcc header from
6741 * message body since it otherwise will be publicly shown
6743 if (compose->account->protocol != A_NNTP)
6744 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6747 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6749 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6752 compose_convert_header(compose, buf, sizeof(buf), str,
6753 strlen("Subject: "), FALSE);
6754 g_string_append_printf(header, "Subject: %s\n", buf);
6760 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6761 g_string_append_printf(header, "Message-ID: <%s>\n",
6765 if (compose->remove_references == FALSE) {
6767 if (compose->inreplyto && compose->to_list)
6768 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6771 if (compose->references)
6772 g_string_append_printf(header, "References: %s\n", compose->references);
6776 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6779 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6782 if (compose->account->organization &&
6783 strlen(compose->account->organization) &&
6784 !IS_IN_CUSTOM_HEADER("Organization")) {
6785 compose_convert_header(compose, buf, sizeof(buf),
6786 compose->account->organization,
6787 strlen("Organization: "), FALSE);
6788 g_string_append_printf(header, "Organization: %s\n", buf);
6791 /* Program version and system info */
6792 if (compose->account->gen_xmailer &&
6793 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6794 !compose->newsgroup_list) {
6795 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6797 gtk_major_version, gtk_minor_version, gtk_micro_version,
6800 if (compose->account->gen_xmailer &&
6801 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6802 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6804 gtk_major_version, gtk_minor_version, gtk_micro_version,
6808 /* custom headers */
6809 if (compose->account->add_customhdr) {
6812 for (cur = compose->account->customhdr_list; cur != NULL;
6814 CustomHeader *chdr = (CustomHeader *)cur->data;
6816 if (custom_header_is_allowed(chdr->name)
6817 && chdr->value != NULL
6818 && *(chdr->value) != '\0') {
6819 compose_convert_header
6820 (compose, buf, sizeof(buf),
6822 strlen(chdr->name) + 2, FALSE);
6823 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6828 /* Automatic Faces and X-Faces */
6829 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6830 g_string_append_printf(header, "X-Face: %s\n", buf);
6832 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6833 g_string_append_printf(header, "X-Face: %s\n", buf);
6835 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6836 g_string_append_printf(header, "Face: %s\n", buf);
6838 else if (get_default_face (buf, sizeof(buf)) == 0) {
6839 g_string_append_printf(header, "Face: %s\n", buf);
6843 switch (compose->priority) {
6844 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6845 "X-Priority: 1 (Highest)\n");
6847 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6848 "X-Priority: 2 (High)\n");
6850 case PRIORITY_NORMAL: break;
6851 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6852 "X-Priority: 4 (Low)\n");
6854 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6855 "X-Priority: 5 (Lowest)\n");
6857 default: debug_print("compose: priority unknown : %d\n",
6861 /* get special headers */
6862 for (list = compose->header_list; list; list = list->next) {
6863 ComposeHeaderEntry *headerentry;
6866 gchar *headername_wcolon;
6867 const gchar *headername_trans;
6870 gboolean standard_header = FALSE;
6872 headerentry = ((ComposeHeaderEntry *)list->data);
6874 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6876 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6881 if (!strstr(tmp, ":")) {
6882 headername_wcolon = g_strconcat(tmp, ":", NULL);
6883 headername = g_strdup(tmp);
6885 headername_wcolon = g_strdup(tmp);
6886 headername = g_strdup(strtok(tmp, ":"));
6890 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6891 Xstrdup_a(headervalue, entry_str, return NULL);
6892 subst_char(headervalue, '\r', ' ');
6893 subst_char(headervalue, '\n', ' ');
6894 g_strstrip(headervalue);
6895 if (*headervalue != '\0') {
6896 string = std_headers;
6897 while (*string != NULL && !standard_header) {
6898 headername_trans = prefs_common_translated_header_name(*string);
6899 /* support mixed translated and untranslated headers */
6900 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6901 standard_header = TRUE;
6904 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6905 /* store untranslated header name */
6906 g_string_append_printf(header, "%s %s\n",
6907 compose_untranslated_header_name(headername_wcolon), headervalue);
6911 g_free(headername_wcolon);
6915 g_string_free(header, FALSE);
6920 #undef IS_IN_CUSTOM_HEADER
6922 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6923 gint header_len, gboolean addr_field)
6925 gchar *tmpstr = NULL;
6926 const gchar *out_codeset = NULL;
6928 cm_return_if_fail(src != NULL);
6929 cm_return_if_fail(dest != NULL);
6931 if (len < 1) return;
6933 tmpstr = g_strdup(src);
6935 subst_char(tmpstr, '\n', ' ');
6936 subst_char(tmpstr, '\r', ' ');
6939 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6940 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6941 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6946 codeconv_set_strict(TRUE);
6947 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6948 conv_get_charset_str(compose->out_encoding));
6949 codeconv_set_strict(FALSE);
6951 if (!dest || *dest == '\0') {
6952 gchar *test_conv_global_out = NULL;
6953 gchar *test_conv_reply = NULL;
6955 /* automatic mode. be automatic. */
6956 codeconv_set_strict(TRUE);
6958 out_codeset = conv_get_outgoing_charset_str();
6960 debug_print("trying to convert to %s\n", out_codeset);
6961 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6964 if (!test_conv_global_out && compose->orig_charset
6965 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6966 out_codeset = compose->orig_charset;
6967 debug_print("failure; trying to convert to %s\n", out_codeset);
6968 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6971 if (!test_conv_global_out && !test_conv_reply) {
6973 out_codeset = CS_INTERNAL;
6974 debug_print("finally using %s\n", out_codeset);
6976 g_free(test_conv_global_out);
6977 g_free(test_conv_reply);
6978 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6980 codeconv_set_strict(FALSE);
6985 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6989 cm_return_if_fail(user_data != NULL);
6991 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6992 g_strstrip(address);
6993 if (*address != '\0') {
6994 gchar *name = procheader_get_fromname(address);
6995 extract_address(address);
6996 #ifndef USE_ALT_ADDRBOOK
6997 addressbook_add_contact(name, address, NULL, NULL);
6999 debug_print("%s: %s\n", name, address);
7000 if (addressadd_selection(name, address, NULL, NULL)) {
7001 debug_print( "addressbook_add_contact - added\n" );
7008 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7010 GtkWidget *menuitem;
7013 cm_return_if_fail(menu != NULL);
7014 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7016 menuitem = gtk_separator_menu_item_new();
7017 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7018 gtk_widget_show(menuitem);
7020 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7021 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7023 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7024 g_strstrip(address);
7025 if (*address == '\0') {
7026 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7029 g_signal_connect(G_OBJECT(menuitem), "activate",
7030 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7031 gtk_widget_show(menuitem);
7034 void compose_add_extra_header(gchar *header, GtkListStore *model)
7037 if (strcmp(header, "")) {
7038 COMBOBOX_ADD(model, header, COMPOSE_TO);
7042 void compose_add_extra_header_entries(GtkListStore *model)
7046 gchar buf[BUFFSIZE];
7049 if (extra_headers == NULL) {
7050 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7051 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
7052 debug_print("extra headers file not found\n");
7053 goto extra_headers_done;
7055 while (fgets(buf, BUFFSIZE, exh) != NULL) {
7056 lastc = strlen(buf) - 1; /* remove trailing control chars */
7057 while (lastc >= 0 && buf[lastc] != ':')
7058 buf[lastc--] = '\0';
7059 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7060 buf[lastc] = '\0'; /* remove trailing : for comparison */
7061 if (custom_header_is_allowed(buf)) {
7063 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7066 g_message("disallowed extra header line: %s\n", buf);
7070 g_message("invalid extra header line: %s\n", buf);
7076 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7077 extra_headers = g_slist_reverse(extra_headers);
7079 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7083 static void _ldap_srv_func(gpointer data, gpointer user_data)
7085 LdapServer *server = (LdapServer *)data;
7086 gboolean *enable = (gboolean *)user_data;
7088 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7089 server->searchFlag = *enable;
7093 static void compose_create_header_entry(Compose *compose)
7095 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7102 const gchar *header = NULL;
7103 ComposeHeaderEntry *headerentry;
7104 gboolean standard_header = FALSE;
7105 GtkListStore *model;
7108 headerentry = g_new0(ComposeHeaderEntry, 1);
7110 /* Combo box model */
7111 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7112 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7114 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7116 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7118 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7119 COMPOSE_NEWSGROUPS);
7120 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7122 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7123 COMPOSE_FOLLOWUPTO);
7124 compose_add_extra_header_entries(model);
7127 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7128 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7129 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7130 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7131 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7132 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7133 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7134 G_CALLBACK(compose_grab_focus_cb), compose);
7135 gtk_widget_show(combo);
7137 /* Putting only the combobox child into focus chain of its parent causes
7138 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7139 * This eliminates need to pres Tab twice in order to really get from the
7140 * combobox to next widget. */
7142 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7143 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7146 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7147 compose->header_nextrow, compose->header_nextrow+1,
7148 GTK_SHRINK, GTK_FILL, 0, 0);
7149 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7150 const gchar *last_header_entry = gtk_entry_get_text(
7151 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7153 while (*string != NULL) {
7154 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7155 standard_header = TRUE;
7158 if (standard_header)
7159 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7161 if (!compose->header_last || !standard_header) {
7162 switch(compose->account->protocol) {
7164 header = prefs_common_translated_header_name("Newsgroups:");
7167 header = prefs_common_translated_header_name("To:");
7172 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7174 gtk_editable_set_editable(
7175 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7176 prefs_common.type_any_header);
7178 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7179 G_CALLBACK(compose_grab_focus_cb), compose);
7181 /* Entry field with cleanup button */
7182 button = gtk_button_new();
7183 gtk_button_set_image(GTK_BUTTON(button),
7184 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7185 gtk_widget_show(button);
7186 CLAWS_SET_TIP(button,
7187 _("Delete entry contents"));
7188 entry = gtk_entry_new();
7189 gtk_widget_show(entry);
7190 CLAWS_SET_TIP(entry,
7191 _("Use <tab> to autocomplete from addressbook"));
7192 hbox = gtk_hbox_new (FALSE, 0);
7193 gtk_widget_show(hbox);
7194 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7195 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7196 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7197 compose->header_nextrow, compose->header_nextrow+1,
7198 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7200 g_signal_connect(G_OBJECT(entry), "key-press-event",
7201 G_CALLBACK(compose_headerentry_key_press_event_cb),
7203 g_signal_connect(G_OBJECT(entry), "changed",
7204 G_CALLBACK(compose_headerentry_changed_cb),
7206 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7207 G_CALLBACK(compose_grab_focus_cb), compose);
7209 g_signal_connect(G_OBJECT(button), "clicked",
7210 G_CALLBACK(compose_headerentry_button_clicked_cb),
7214 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7215 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7216 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7217 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7218 G_CALLBACK(compose_header_drag_received_cb),
7220 g_signal_connect(G_OBJECT(entry), "drag-drop",
7221 G_CALLBACK(compose_drag_drop),
7223 g_signal_connect(G_OBJECT(entry), "populate-popup",
7224 G_CALLBACK(compose_entry_popup_extend),
7228 #ifndef PASSWORD_CRYPTO_OLD
7229 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7230 if (pwd_servers != NULL && master_passphrase() == NULL) {
7231 gboolean enable = FALSE;
7232 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7233 /* Temporarily disable password-protected LDAP servers,
7234 * because user did not provide a master passphrase.
7235 * We can safely enable searchFlag on all servers in this list
7236 * later, since addrindex_get_password_protected_ldap_servers()
7237 * includes servers which have it enabled initially. */
7238 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7239 compose->passworded_ldap_servers = pwd_servers;
7241 #endif /* PASSWORD_CRYPTO_OLD */
7242 #endif /* USE_LDAP */
7244 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7246 headerentry->compose = compose;
7247 headerentry->combo = combo;
7248 headerentry->entry = entry;
7249 headerentry->button = button;
7250 headerentry->hbox = hbox;
7251 headerentry->headernum = compose->header_nextrow;
7252 headerentry->type = PREF_NONE;
7254 compose->header_nextrow++;
7255 compose->header_last = headerentry;
7256 compose->header_list =
7257 g_slist_append(compose->header_list,
7261 static void compose_add_header_entry(Compose *compose, const gchar *header,
7262 gchar *text, ComposePrefType pref_type)
7264 ComposeHeaderEntry *last_header = compose->header_last;
7265 gchar *tmp = g_strdup(text), *email;
7266 gboolean replyto_hdr;
7268 replyto_hdr = (!strcasecmp(header,
7269 prefs_common_translated_header_name("Reply-To:")) ||
7271 prefs_common_translated_header_name("Followup-To:")) ||
7273 prefs_common_translated_header_name("In-Reply-To:")));
7275 extract_address(tmp);
7276 email = g_utf8_strdown(tmp, -1);
7278 if (replyto_hdr == FALSE &&
7279 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7281 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7282 header, text, (gint) pref_type);
7288 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7289 gtk_entry_set_text(GTK_ENTRY(
7290 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7292 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7293 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7294 last_header->type = pref_type;
7296 if (replyto_hdr == FALSE)
7297 g_hash_table_insert(compose->email_hashtable, email,
7298 GUINT_TO_POINTER(1));
7305 static void compose_destroy_headerentry(Compose *compose,
7306 ComposeHeaderEntry *headerentry)
7308 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7311 extract_address(text);
7312 email = g_utf8_strdown(text, -1);
7313 g_hash_table_remove(compose->email_hashtable, email);
7317 gtk_widget_destroy(headerentry->combo);
7318 gtk_widget_destroy(headerentry->entry);
7319 gtk_widget_destroy(headerentry->button);
7320 gtk_widget_destroy(headerentry->hbox);
7321 g_free(headerentry);
7324 static void compose_remove_header_entries(Compose *compose)
7327 for (list = compose->header_list; list; list = list->next)
7328 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7330 compose->header_last = NULL;
7331 g_slist_free(compose->header_list);
7332 compose->header_list = NULL;
7333 compose->header_nextrow = 1;
7334 compose_create_header_entry(compose);
7337 static GtkWidget *compose_create_header(Compose *compose)
7339 GtkWidget *from_optmenu_hbox;
7340 GtkWidget *header_table_main;
7341 GtkWidget *header_scrolledwin;
7342 GtkWidget *header_table;
7344 /* parent with account selection and from header */
7345 header_table_main = gtk_table_new(2, 2, FALSE);
7346 gtk_widget_show(header_table_main);
7347 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7349 from_optmenu_hbox = compose_account_option_menu_create(compose);
7350 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7351 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7353 /* child with header labels and entries */
7354 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7355 gtk_widget_show(header_scrolledwin);
7356 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7358 header_table = gtk_table_new(2, 2, FALSE);
7359 gtk_widget_show(header_table);
7360 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7361 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7362 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7363 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7364 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7366 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7367 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7369 compose->header_table = header_table;
7370 compose->header_list = NULL;
7371 compose->header_nextrow = 0;
7373 compose_create_header_entry(compose);
7375 compose->table = NULL;
7377 return header_table_main;
7380 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7382 Compose *compose = (Compose *)data;
7383 GdkEventButton event;
7386 event.time = gtk_get_current_event_time();
7388 return attach_button_pressed(compose->attach_clist, &event, compose);
7391 static GtkWidget *compose_create_attach(Compose *compose)
7393 GtkWidget *attach_scrwin;
7394 GtkWidget *attach_clist;
7396 GtkListStore *store;
7397 GtkCellRenderer *renderer;
7398 GtkTreeViewColumn *column;
7399 GtkTreeSelection *selection;
7401 /* attachment list */
7402 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7403 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7404 GTK_POLICY_AUTOMATIC,
7405 GTK_POLICY_AUTOMATIC);
7406 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7408 store = gtk_list_store_new(N_ATTACH_COLS,
7414 G_TYPE_AUTO_POINTER,
7416 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7417 (GTK_TREE_MODEL(store)));
7418 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7419 g_object_unref(store);
7421 renderer = gtk_cell_renderer_text_new();
7422 column = gtk_tree_view_column_new_with_attributes
7423 (_("Mime type"), renderer, "text",
7424 COL_MIMETYPE, NULL);
7425 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7427 renderer = gtk_cell_renderer_text_new();
7428 column = gtk_tree_view_column_new_with_attributes
7429 (_("Size"), renderer, "text",
7431 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7433 renderer = gtk_cell_renderer_text_new();
7434 column = gtk_tree_view_column_new_with_attributes
7435 (_("Name"), renderer, "text",
7437 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7439 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7440 prefs_common.use_stripes_everywhere);
7441 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7442 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7444 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7445 G_CALLBACK(attach_selected), compose);
7446 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7447 G_CALLBACK(attach_button_pressed), compose);
7448 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7449 G_CALLBACK(popup_attach_button_pressed), compose);
7450 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7451 G_CALLBACK(attach_key_pressed), compose);
7454 gtk_drag_dest_set(attach_clist,
7455 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7456 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7457 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7458 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7459 G_CALLBACK(compose_attach_drag_received_cb),
7461 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7462 G_CALLBACK(compose_drag_drop),
7465 compose->attach_scrwin = attach_scrwin;
7466 compose->attach_clist = attach_clist;
7468 return attach_scrwin;
7471 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7472 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7474 static GtkWidget *compose_create_others(Compose *compose)
7477 GtkWidget *savemsg_checkbtn;
7478 GtkWidget *savemsg_combo;
7479 GtkWidget *savemsg_select;
7482 gchar *folderidentifier;
7484 /* Table for settings */
7485 table = gtk_table_new(3, 1, FALSE);
7486 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7487 gtk_widget_show(table);
7488 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7491 /* Save Message to folder */
7492 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7493 gtk_widget_show(savemsg_checkbtn);
7494 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7495 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7496 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7498 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7499 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7501 savemsg_combo = gtk_combo_box_text_new_with_entry();
7502 compose->savemsg_checkbtn = savemsg_checkbtn;
7503 compose->savemsg_combo = savemsg_combo;
7504 gtk_widget_show(savemsg_combo);
7506 if (prefs_common.compose_save_to_history)
7507 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7508 prefs_common.compose_save_to_history);
7509 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7510 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7511 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7512 G_CALLBACK(compose_grab_focus_cb), compose);
7513 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7514 folderidentifier = folder_item_get_identifier(account_get_special_folder
7515 (compose->account, F_OUTBOX));
7516 compose_set_save_to(compose, folderidentifier);
7517 g_free(folderidentifier);
7520 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7521 gtk_widget_show(savemsg_select);
7522 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7523 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7524 G_CALLBACK(compose_savemsg_select_cb),
7530 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7532 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7533 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7536 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7541 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7542 _("Select folder to save message to"));
7545 path = folder_item_get_identifier(dest);
7547 compose_set_save_to(compose, path);
7551 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7552 GdkAtom clip, GtkTextIter *insert_place);
7555 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7559 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7561 if (event->button == 3) {
7563 GtkTextIter sel_start, sel_end;
7564 gboolean stuff_selected;
7566 /* move the cursor to allow GtkAspell to check the word
7567 * under the mouse */
7568 if (event->x && event->y) {
7569 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7570 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7572 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7575 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7576 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7579 stuff_selected = gtk_text_buffer_get_selection_bounds(
7581 &sel_start, &sel_end);
7583 gtk_text_buffer_place_cursor (buffer, &iter);
7584 /* reselect stuff */
7586 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7587 gtk_text_buffer_select_range(buffer,
7588 &sel_start, &sel_end);
7590 return FALSE; /* pass the event so that the right-click goes through */
7593 if (event->button == 2) {
7598 /* get the middle-click position to paste at the correct place */
7599 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7600 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7602 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7605 entry_paste_clipboard(compose, text,
7606 prefs_common.linewrap_pastes,
7607 GDK_SELECTION_PRIMARY, &iter);
7615 static void compose_spell_menu_changed(void *data)
7617 Compose *compose = (Compose *)data;
7619 GtkWidget *menuitem;
7620 GtkWidget *parent_item;
7621 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7624 if (compose->gtkaspell == NULL)
7627 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7628 "/Menu/Spelling/Options");
7630 /* setting the submenu removes /Spelling/Options from the factory
7631 * so we need to save it */
7633 if (parent_item == NULL) {
7634 parent_item = compose->aspell_options_menu;
7635 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7637 compose->aspell_options_menu = parent_item;
7639 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7641 spell_menu = g_slist_reverse(spell_menu);
7642 for (items = spell_menu;
7643 items; items = items->next) {
7644 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7645 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7646 gtk_widget_show(GTK_WIDGET(menuitem));
7648 g_slist_free(spell_menu);
7650 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7651 gtk_widget_show(parent_item);
7654 static void compose_dict_changed(void *data)
7656 Compose *compose = (Compose *) data;
7658 if(!compose->gtkaspell)
7660 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7663 gtkaspell_highlight_all(compose->gtkaspell);
7664 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7668 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7670 Compose *compose = (Compose *)data;
7671 GdkEventButton event;
7674 event.time = gtk_get_current_event_time();
7678 return text_clicked(compose->text, &event, compose);
7681 static gboolean compose_force_window_origin = TRUE;
7682 static Compose *compose_create(PrefsAccount *account,
7691 GtkWidget *handlebox;
7693 GtkWidget *notebook;
7695 GtkWidget *attach_hbox;
7696 GtkWidget *attach_lab1;
7697 GtkWidget *attach_lab2;
7702 GtkWidget *subject_hbox;
7703 GtkWidget *subject_frame;
7704 GtkWidget *subject_entry;
7708 GtkWidget *edit_vbox;
7709 GtkWidget *ruler_hbox;
7711 GtkWidget *scrolledwin;
7713 GtkTextBuffer *buffer;
7714 GtkClipboard *clipboard;
7716 UndoMain *undostruct;
7718 GtkWidget *popupmenu;
7719 GtkWidget *tmpl_menu;
7720 GtkActionGroup *action_group = NULL;
7723 GtkAspell * gtkaspell = NULL;
7726 static GdkGeometry geometry;
7728 cm_return_val_if_fail(account != NULL, NULL);
7730 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7731 &default_header_bgcolor);
7732 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7733 &default_header_color);
7735 debug_print("Creating compose window...\n");
7736 compose = g_new0(Compose, 1);
7738 compose->batch = batch;
7739 compose->account = account;
7740 compose->folder = folder;
7742 compose->mutex = cm_mutex_new();
7743 compose->set_cursor_pos = -1;
7745 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7747 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7748 gtk_widget_set_size_request(window, prefs_common.compose_width,
7749 prefs_common.compose_height);
7751 if (!geometry.max_width) {
7752 geometry.max_width = gdk_screen_width();
7753 geometry.max_height = gdk_screen_height();
7756 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7757 &geometry, GDK_HINT_MAX_SIZE);
7758 if (!geometry.min_width) {
7759 geometry.min_width = 600;
7760 geometry.min_height = 440;
7762 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7763 &geometry, GDK_HINT_MIN_SIZE);
7765 #ifndef GENERIC_UMPC
7766 if (compose_force_window_origin)
7767 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7768 prefs_common.compose_y);
7770 g_signal_connect(G_OBJECT(window), "delete_event",
7771 G_CALLBACK(compose_delete_cb), compose);
7772 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7773 gtk_widget_realize(window);
7775 gtkut_widget_set_composer_icon(window);
7777 vbox = gtk_vbox_new(FALSE, 0);
7778 gtk_container_add(GTK_CONTAINER(window), vbox);
7780 compose->ui_manager = gtk_ui_manager_new();
7781 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7782 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7783 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7784 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7785 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7786 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7787 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7788 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7789 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7790 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7794 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7795 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7797 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7799 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7800 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7801 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7813 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7814 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7815 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7816 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7819 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7820 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7821 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7823 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7824 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7825 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7827 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7828 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7829 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7830 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7832 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7834 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7835 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7836 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7837 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7838 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7839 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7840 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7841 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7842 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7843 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7844 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7845 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7846 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7847 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7852 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7854 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7882 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7905 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7909 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)
7910 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)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7916 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)
7917 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)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7922 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)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7926 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)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7932 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)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7939 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)
7940 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)
7941 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7949 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7953 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)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7957 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7963 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7971 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7972 gtk_widget_show_all(menubar);
7974 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7975 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7977 if (prefs_common.toolbar_detachable) {
7978 handlebox = gtk_handle_box_new();
7980 handlebox = gtk_hbox_new(FALSE, 0);
7982 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7984 gtk_widget_realize(handlebox);
7985 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7988 vbox2 = gtk_vbox_new(FALSE, 2);
7989 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7990 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7993 notebook = gtk_notebook_new();
7994 gtk_widget_show(notebook);
7996 /* header labels and entries */
7997 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7998 compose_create_header(compose),
7999 gtk_label_new_with_mnemonic(_("Hea_der")));
8000 /* attachment list */
8001 attach_hbox = gtk_hbox_new(FALSE, 0);
8002 gtk_widget_show(attach_hbox);
8004 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8005 gtk_widget_show(attach_lab1);
8006 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8008 attach_lab2 = gtk_label_new("");
8009 gtk_widget_show(attach_lab2);
8010 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8012 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8013 compose_create_attach(compose),
8016 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8017 compose_create_others(compose),
8018 gtk_label_new_with_mnemonic(_("Othe_rs")));
8021 subject_hbox = gtk_hbox_new(FALSE, 0);
8022 gtk_widget_show(subject_hbox);
8024 subject_frame = gtk_frame_new(NULL);
8025 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8026 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8027 gtk_widget_show(subject_frame);
8029 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8030 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8031 gtk_widget_show(subject);
8033 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8034 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8035 gtk_widget_show(label);
8038 subject_entry = claws_spell_entry_new();
8040 subject_entry = gtk_entry_new();
8042 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8043 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8044 G_CALLBACK(compose_grab_focus_cb), compose);
8045 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8046 gtk_widget_show(subject_entry);
8047 compose->subject_entry = subject_entry;
8048 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8050 edit_vbox = gtk_vbox_new(FALSE, 0);
8052 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8055 ruler_hbox = gtk_hbox_new(FALSE, 0);
8056 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8058 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8059 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8060 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8064 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8065 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8066 GTK_POLICY_AUTOMATIC,
8067 GTK_POLICY_AUTOMATIC);
8068 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8070 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8072 text = gtk_text_view_new();
8073 if (prefs_common.show_compose_margin) {
8074 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8075 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8077 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8078 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8079 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8080 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8081 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8083 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8084 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8085 G_CALLBACK(compose_edit_size_alloc),
8087 g_signal_connect(G_OBJECT(buffer), "changed",
8088 G_CALLBACK(compose_changed_cb), compose);
8089 g_signal_connect(G_OBJECT(text), "grab_focus",
8090 G_CALLBACK(compose_grab_focus_cb), compose);
8091 g_signal_connect(G_OBJECT(buffer), "insert_text",
8092 G_CALLBACK(text_inserted), compose);
8093 g_signal_connect(G_OBJECT(text), "button_press_event",
8094 G_CALLBACK(text_clicked), compose);
8095 g_signal_connect(G_OBJECT(text), "popup-menu",
8096 G_CALLBACK(compose_popup_menu), compose);
8097 g_signal_connect(G_OBJECT(subject_entry), "changed",
8098 G_CALLBACK(compose_changed_cb), compose);
8099 g_signal_connect(G_OBJECT(subject_entry), "activate",
8100 G_CALLBACK(compose_subject_entry_activated), compose);
8103 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8104 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8105 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8106 g_signal_connect(G_OBJECT(text), "drag_data_received",
8107 G_CALLBACK(compose_insert_drag_received_cb),
8109 g_signal_connect(G_OBJECT(text), "drag-drop",
8110 G_CALLBACK(compose_drag_drop),
8112 g_signal_connect(G_OBJECT(text), "key-press-event",
8113 G_CALLBACK(completion_set_focus_to_subject),
8115 gtk_widget_show_all(vbox);
8117 /* pane between attach clist and text */
8118 paned = gtk_vpaned_new();
8119 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8120 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8121 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8122 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8123 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8124 G_CALLBACK(compose_notebook_size_alloc), paned);
8126 gtk_widget_show_all(paned);
8129 if (prefs_common.textfont) {
8130 PangoFontDescription *font_desc;
8132 font_desc = pango_font_description_from_string
8133 (prefs_common.textfont);
8135 gtk_widget_modify_font(text, font_desc);
8136 pango_font_description_free(font_desc);
8140 gtk_action_group_add_actions(action_group, compose_popup_entries,
8141 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8149 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8151 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8152 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8153 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8155 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8157 undostruct = undo_init(text);
8158 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8161 address_completion_start(window);
8163 compose->window = window;
8164 compose->vbox = vbox;
8165 compose->menubar = menubar;
8166 compose->handlebox = handlebox;
8168 compose->vbox2 = vbox2;
8170 compose->paned = paned;
8172 compose->attach_label = attach_lab2;
8174 compose->notebook = notebook;
8175 compose->edit_vbox = edit_vbox;
8176 compose->ruler_hbox = ruler_hbox;
8177 compose->ruler = ruler;
8178 compose->scrolledwin = scrolledwin;
8179 compose->text = text;
8181 compose->focused_editable = NULL;
8183 compose->popupmenu = popupmenu;
8185 compose->tmpl_menu = tmpl_menu;
8187 compose->mode = mode;
8188 compose->rmode = mode;
8190 compose->targetinfo = NULL;
8191 compose->replyinfo = NULL;
8192 compose->fwdinfo = NULL;
8194 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8195 g_str_equal, (GDestroyNotify) g_free, NULL);
8197 compose->replyto = NULL;
8199 compose->bcc = NULL;
8200 compose->followup_to = NULL;
8202 compose->ml_post = NULL;
8204 compose->inreplyto = NULL;
8205 compose->references = NULL;
8206 compose->msgid = NULL;
8207 compose->boundary = NULL;
8209 compose->autowrap = prefs_common.autowrap;
8210 compose->autoindent = prefs_common.auto_indent;
8211 compose->use_signing = FALSE;
8212 compose->use_encryption = FALSE;
8213 compose->privacy_system = NULL;
8214 compose->encdata = NULL;
8216 compose->modified = FALSE;
8218 compose->return_receipt = FALSE;
8220 compose->to_list = NULL;
8221 compose->newsgroup_list = NULL;
8223 compose->undostruct = undostruct;
8225 compose->sig_str = NULL;
8227 compose->exteditor_file = NULL;
8228 compose->exteditor_pid = -1;
8229 compose->exteditor_tag = -1;
8230 compose->exteditor_socket = NULL;
8231 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8233 compose->folder_update_callback_id =
8234 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8235 compose_update_folder_hook,
8236 (gpointer) compose);
8239 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8240 if (mode != COMPOSE_REDIRECT) {
8241 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8242 strcmp(prefs_common.dictionary, "")) {
8243 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8244 prefs_common.alt_dictionary,
8245 conv_get_locale_charset_str(),
8246 prefs_common.color[COL_MISSPELLED],
8247 prefs_common.check_while_typing,
8248 prefs_common.recheck_when_changing_dict,
8249 prefs_common.use_alternate,
8250 prefs_common.use_both_dicts,
8251 GTK_TEXT_VIEW(text),
8252 GTK_WINDOW(compose->window),
8253 compose_dict_changed,
8254 compose_spell_menu_changed,
8257 alertpanel_error(_("Spell checker could not "
8259 gtkaspell_checkers_strerror());
8260 gtkaspell_checkers_reset_error();
8262 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8266 compose->gtkaspell = gtkaspell;
8267 compose_spell_menu_changed(compose);
8268 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8271 compose_select_account(compose, account, TRUE);
8273 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8274 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8276 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8277 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8279 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8280 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8282 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8283 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8285 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8286 if (account->protocol != A_NNTP)
8287 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8288 prefs_common_translated_header_name("To:"));
8290 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8291 prefs_common_translated_header_name("Newsgroups:"));
8293 #ifndef USE_ALT_ADDRBOOK
8294 addressbook_set_target_compose(compose);
8296 if (mode != COMPOSE_REDIRECT)
8297 compose_set_template_menu(compose);
8299 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8302 compose_list = g_list_append(compose_list, compose);
8304 if (!prefs_common.show_ruler)
8305 gtk_widget_hide(ruler_hbox);
8307 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8310 compose->priority = PRIORITY_NORMAL;
8311 compose_update_priority_menu_item(compose);
8313 compose_set_out_encoding(compose);
8316 compose_update_actions_menu(compose);
8318 /* Privacy Systems menu */
8319 compose_update_privacy_systems_menu(compose);
8320 compose_activate_privacy_system(compose, account, TRUE);
8322 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8324 gtk_widget_realize(window);
8326 gtk_widget_show(window);
8332 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8337 GtkWidget *optmenubox;
8338 GtkWidget *fromlabel;
8341 GtkWidget *from_name = NULL;
8343 gint num = 0, def_menu = 0;
8345 accounts = account_get_list();
8346 cm_return_val_if_fail(accounts != NULL, NULL);
8348 optmenubox = gtk_event_box_new();
8349 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8350 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8352 hbox = gtk_hbox_new(FALSE, 4);
8353 from_name = gtk_entry_new();
8355 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8356 G_CALLBACK(compose_grab_focus_cb), compose);
8357 g_signal_connect_after(G_OBJECT(from_name), "activate",
8358 G_CALLBACK(from_name_activate_cb), optmenu);
8360 for (; accounts != NULL; accounts = accounts->next, num++) {
8361 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8362 gchar *name, *from = NULL;
8364 if (ac == compose->account) def_menu = num;
8366 name = g_markup_printf_escaped("<i>%s</i>",
8369 if (ac == compose->account) {
8370 if (ac->name && *ac->name) {
8372 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8373 from = g_strdup_printf("%s <%s>",
8375 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8377 from = g_strdup_printf("%s",
8379 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8381 if (cur_account != compose->account) {
8382 gtk_widget_modify_base(
8383 GTK_WIDGET(from_name),
8384 GTK_STATE_NORMAL, &default_header_bgcolor);
8385 gtk_widget_modify_text(
8386 GTK_WIDGET(from_name),
8387 GTK_STATE_NORMAL, &default_header_color);
8390 COMBOBOX_ADD(menu, name, ac->account_id);
8395 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8397 g_signal_connect(G_OBJECT(optmenu), "changed",
8398 G_CALLBACK(account_activated),
8400 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8401 G_CALLBACK(compose_entry_popup_extend),
8404 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8405 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8407 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8408 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8409 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8411 /* Putting only the GtkEntry into focus chain of parent hbox causes
8412 * the account selector combobox next to it to be unreachable when
8413 * navigating widgets in GtkTable with up/down arrow keys.
8414 * Note: gtk_widget_set_can_focus() was not enough. */
8416 l = g_list_prepend(l, from_name);
8417 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8420 CLAWS_SET_TIP(optmenubox,
8421 _("Account to use for this email"));
8422 CLAWS_SET_TIP(from_name,
8423 _("Sender address to be used"));
8425 compose->account_combo = optmenu;
8426 compose->from_name = from_name;
8431 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8433 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8434 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8435 Compose *compose = (Compose *) data;
8437 compose->priority = value;
8441 static void compose_reply_change_mode(Compose *compose,
8444 gboolean was_modified = compose->modified;
8446 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8448 cm_return_if_fail(compose->replyinfo != NULL);
8450 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8452 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8454 if (action == COMPOSE_REPLY_TO_ALL)
8456 if (action == COMPOSE_REPLY_TO_SENDER)
8458 if (action == COMPOSE_REPLY_TO_LIST)
8461 compose_remove_header_entries(compose);
8462 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8463 if (compose->account->set_autocc && compose->account->auto_cc)
8464 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8466 if (compose->account->set_autobcc && compose->account->auto_bcc)
8467 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8469 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8470 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8471 compose_show_first_last_header(compose, TRUE);
8472 compose->modified = was_modified;
8473 compose_set_title(compose);
8476 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8478 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8479 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8480 Compose *compose = (Compose *) data;
8483 compose_reply_change_mode(compose, value);
8486 static void compose_update_priority_menu_item(Compose * compose)
8488 GtkWidget *menuitem = NULL;
8489 switch (compose->priority) {
8490 case PRIORITY_HIGHEST:
8491 menuitem = gtk_ui_manager_get_widget
8492 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8495 menuitem = gtk_ui_manager_get_widget
8496 (compose->ui_manager, "/Menu/Options/Priority/High");
8498 case PRIORITY_NORMAL:
8499 menuitem = gtk_ui_manager_get_widget
8500 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8503 menuitem = gtk_ui_manager_get_widget
8504 (compose->ui_manager, "/Menu/Options/Priority/Low");
8506 case PRIORITY_LOWEST:
8507 menuitem = gtk_ui_manager_get_widget
8508 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8511 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8514 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8516 Compose *compose = (Compose *) data;
8518 gboolean can_sign = FALSE, can_encrypt = FALSE;
8520 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8522 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8525 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8526 g_free(compose->privacy_system);
8527 compose->privacy_system = NULL;
8528 g_free(compose->encdata);
8529 compose->encdata = NULL;
8530 if (systemid != NULL) {
8531 compose->privacy_system = g_strdup(systemid);
8533 can_sign = privacy_system_can_sign(systemid);
8534 can_encrypt = privacy_system_can_encrypt(systemid);
8537 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8541 if (compose->toolbar->privacy_sign_btn != NULL) {
8542 gtk_widget_set_sensitive(
8543 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8545 gtk_toggle_tool_button_set_active(
8546 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8547 can_sign ? compose->use_signing : FALSE);
8549 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8550 gtk_widget_set_sensitive(
8551 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8553 gtk_toggle_tool_button_set_active(
8554 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8555 can_encrypt ? compose->use_encryption : FALSE);
8559 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8561 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8562 GtkWidget *menuitem = NULL;
8563 GList *children, *amenu;
8564 gboolean can_sign = FALSE, can_encrypt = FALSE;
8565 gboolean found = FALSE;
8567 if (compose->privacy_system != NULL) {
8569 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8570 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8571 cm_return_if_fail(menuitem != NULL);
8573 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8576 while (amenu != NULL) {
8577 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8578 if (systemid != NULL) {
8579 if (strcmp(systemid, compose->privacy_system) == 0 &&
8580 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8581 menuitem = GTK_WIDGET(amenu->data);
8583 can_sign = privacy_system_can_sign(systemid);
8584 can_encrypt = privacy_system_can_encrypt(systemid);
8588 } else if (strlen(compose->privacy_system) == 0 &&
8589 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8590 menuitem = GTK_WIDGET(amenu->data);
8593 can_encrypt = FALSE;
8598 amenu = amenu->next;
8600 g_list_free(children);
8601 if (menuitem != NULL)
8602 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8604 if (warn && !found && strlen(compose->privacy_system)) {
8605 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8606 "will not be able to sign or encrypt this message."),
8607 compose->privacy_system);
8611 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8612 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8613 if (compose->toolbar->privacy_sign_btn != NULL) {
8614 gtk_widget_set_sensitive(
8615 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8618 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8619 gtk_widget_set_sensitive(
8620 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8625 static void compose_set_out_encoding(Compose *compose)
8627 CharSet out_encoding;
8628 const gchar *branch = NULL;
8629 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8631 switch(out_encoding) {
8632 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8633 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8634 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8635 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8636 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8637 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8638 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8639 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8640 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8641 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8642 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8643 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8644 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8645 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8646 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8647 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8648 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8649 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8650 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8651 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8652 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8653 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8654 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8655 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8656 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8657 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8658 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8659 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8660 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8661 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8662 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8663 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8664 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8665 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8667 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8670 static void compose_set_template_menu(Compose *compose)
8672 GSList *tmpl_list, *cur;
8676 tmpl_list = template_get_config();
8678 menu = gtk_menu_new();
8680 gtk_menu_set_accel_group (GTK_MENU (menu),
8681 gtk_ui_manager_get_accel_group(compose->ui_manager));
8682 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8683 Template *tmpl = (Template *)cur->data;
8684 gchar *accel_path = NULL;
8685 item = gtk_menu_item_new_with_label(tmpl->name);
8686 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8687 g_signal_connect(G_OBJECT(item), "activate",
8688 G_CALLBACK(compose_template_activate_cb),
8690 g_object_set_data(G_OBJECT(item), "template", tmpl);
8691 gtk_widget_show(item);
8692 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8693 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8697 gtk_widget_show(menu);
8698 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8701 void compose_update_actions_menu(Compose *compose)
8703 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8706 static void compose_update_privacy_systems_menu(Compose *compose)
8708 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8709 GSList *systems, *cur;
8711 GtkWidget *system_none;
8713 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8714 GtkWidget *privacy_menu = gtk_menu_new();
8716 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8717 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8719 g_signal_connect(G_OBJECT(system_none), "activate",
8720 G_CALLBACK(compose_set_privacy_system_cb), compose);
8722 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8723 gtk_widget_show(system_none);
8725 systems = privacy_get_system_ids();
8726 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8727 gchar *systemid = cur->data;
8729 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8730 widget = gtk_radio_menu_item_new_with_label(group,
8731 privacy_system_get_name(systemid));
8732 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8733 g_strdup(systemid), g_free);
8734 g_signal_connect(G_OBJECT(widget), "activate",
8735 G_CALLBACK(compose_set_privacy_system_cb), compose);
8737 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8738 gtk_widget_show(widget);
8741 g_slist_free(systems);
8742 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8743 gtk_widget_show_all(privacy_menu);
8744 gtk_widget_show_all(privacy_menuitem);
8747 void compose_reflect_prefs_all(void)
8752 for (cur = compose_list; cur != NULL; cur = cur->next) {
8753 compose = (Compose *)cur->data;
8754 compose_set_template_menu(compose);
8758 void compose_reflect_prefs_pixmap_theme(void)
8763 for (cur = compose_list; cur != NULL; cur = cur->next) {
8764 compose = (Compose *)cur->data;
8765 toolbar_update(TOOLBAR_COMPOSE, compose);
8769 static const gchar *compose_quote_char_from_context(Compose *compose)
8771 const gchar *qmark = NULL;
8773 cm_return_val_if_fail(compose != NULL, NULL);
8775 switch (compose->mode) {
8776 /* use forward-specific quote char */
8777 case COMPOSE_FORWARD:
8778 case COMPOSE_FORWARD_AS_ATTACH:
8779 case COMPOSE_FORWARD_INLINE:
8780 if (compose->folder && compose->folder->prefs &&
8781 compose->folder->prefs->forward_with_format)
8782 qmark = compose->folder->prefs->forward_quotemark;
8783 else if (compose->account->forward_with_format)
8784 qmark = compose->account->forward_quotemark;
8786 qmark = prefs_common.fw_quotemark;
8789 /* use reply-specific quote char in all other modes */
8791 if (compose->folder && compose->folder->prefs &&
8792 compose->folder->prefs->reply_with_format)
8793 qmark = compose->folder->prefs->reply_quotemark;
8794 else if (compose->account->reply_with_format)
8795 qmark = compose->account->reply_quotemark;
8797 qmark = prefs_common.quotemark;
8801 if (qmark == NULL || *qmark == '\0')
8807 static void compose_template_apply(Compose *compose, Template *tmpl,
8811 GtkTextBuffer *buffer;
8815 gchar *parsed_str = NULL;
8816 gint cursor_pos = 0;
8817 const gchar *err_msg = _("The body of the template has an error at line %d.");
8820 /* process the body */
8822 text = GTK_TEXT_VIEW(compose->text);
8823 buffer = gtk_text_view_get_buffer(text);
8826 qmark = compose_quote_char_from_context(compose);
8828 if (compose->replyinfo != NULL) {
8831 gtk_text_buffer_set_text(buffer, "", -1);
8832 mark = gtk_text_buffer_get_insert(buffer);
8833 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8835 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8836 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8838 } else if (compose->fwdinfo != NULL) {
8841 gtk_text_buffer_set_text(buffer, "", -1);
8842 mark = gtk_text_buffer_get_insert(buffer);
8843 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8845 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8846 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8849 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8851 GtkTextIter start, end;
8854 gtk_text_buffer_get_start_iter(buffer, &start);
8855 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8856 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8858 /* clear the buffer now */
8860 gtk_text_buffer_set_text(buffer, "", -1);
8862 parsed_str = compose_quote_fmt(compose, dummyinfo,
8863 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8864 procmsg_msginfo_free( &dummyinfo );
8870 gtk_text_buffer_set_text(buffer, "", -1);
8871 mark = gtk_text_buffer_get_insert(buffer);
8872 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8875 if (replace && parsed_str && compose->account->auto_sig)
8876 compose_insert_sig(compose, FALSE);
8878 if (replace && parsed_str) {
8879 gtk_text_buffer_get_start_iter(buffer, &iter);
8880 gtk_text_buffer_place_cursor(buffer, &iter);
8884 cursor_pos = quote_fmt_get_cursor_pos();
8885 compose->set_cursor_pos = cursor_pos;
8886 if (cursor_pos == -1)
8888 gtk_text_buffer_get_start_iter(buffer, &iter);
8889 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8890 gtk_text_buffer_place_cursor(buffer, &iter);
8893 /* process the other fields */
8895 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8896 compose_template_apply_fields(compose, tmpl);
8897 quote_fmt_reset_vartable();
8898 quote_fmtlex_destroy();
8900 compose_changed_cb(NULL, compose);
8903 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8904 gtkaspell_highlight_all(compose->gtkaspell);
8908 static void compose_template_apply_fields_error(const gchar *header)
8913 tr = g_strdup(C_("'%s' stands for a header name",
8914 "Template '%s' format error."));
8915 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8916 alertpanel_error("%s", text);
8922 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8924 MsgInfo* dummyinfo = NULL;
8925 MsgInfo *msginfo = NULL;
8928 if (compose->replyinfo != NULL)
8929 msginfo = compose->replyinfo;
8930 else if (compose->fwdinfo != NULL)
8931 msginfo = compose->fwdinfo;
8933 dummyinfo = compose_msginfo_new_from_compose(compose);
8934 msginfo = dummyinfo;
8937 if (tmpl->from && *tmpl->from != '\0') {
8939 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8940 compose->gtkaspell);
8942 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8944 quote_fmt_scan_string(tmpl->from);
8947 buf = quote_fmt_get_buffer();
8949 compose_template_apply_fields_error("From");
8951 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8954 quote_fmt_reset_vartable();
8955 quote_fmtlex_destroy();
8958 if (tmpl->to && *tmpl->to != '\0') {
8960 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8961 compose->gtkaspell);
8963 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8965 quote_fmt_scan_string(tmpl->to);
8968 buf = quote_fmt_get_buffer();
8970 compose_template_apply_fields_error("To");
8972 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8975 quote_fmt_reset_vartable();
8976 quote_fmtlex_destroy();
8979 if (tmpl->cc && *tmpl->cc != '\0') {
8981 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8982 compose->gtkaspell);
8984 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8986 quote_fmt_scan_string(tmpl->cc);
8989 buf = quote_fmt_get_buffer();
8991 compose_template_apply_fields_error("Cc");
8993 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8996 quote_fmt_reset_vartable();
8997 quote_fmtlex_destroy();
9000 if (tmpl->bcc && *tmpl->bcc != '\0') {
9002 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9003 compose->gtkaspell);
9005 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9007 quote_fmt_scan_string(tmpl->bcc);
9010 buf = quote_fmt_get_buffer();
9012 compose_template_apply_fields_error("Bcc");
9014 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9017 quote_fmt_reset_vartable();
9018 quote_fmtlex_destroy();
9021 if (tmpl->replyto && *tmpl->replyto != '\0') {
9023 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9024 compose->gtkaspell);
9026 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9028 quote_fmt_scan_string(tmpl->replyto);
9031 buf = quote_fmt_get_buffer();
9033 compose_template_apply_fields_error("Reply-To");
9035 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9038 quote_fmt_reset_vartable();
9039 quote_fmtlex_destroy();
9042 /* process the subject */
9043 if (tmpl->subject && *tmpl->subject != '\0') {
9045 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9046 compose->gtkaspell);
9048 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9050 quote_fmt_scan_string(tmpl->subject);
9053 buf = quote_fmt_get_buffer();
9055 compose_template_apply_fields_error("Subject");
9057 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9060 quote_fmt_reset_vartable();
9061 quote_fmtlex_destroy();
9064 procmsg_msginfo_free( &dummyinfo );
9067 static void compose_destroy(Compose *compose)
9069 GtkAllocation allocation;
9070 GtkTextBuffer *buffer;
9071 GtkClipboard *clipboard;
9073 compose_list = g_list_remove(compose_list, compose);
9076 gboolean enable = TRUE;
9077 g_slist_foreach(compose->passworded_ldap_servers,
9078 _ldap_srv_func, &enable);
9079 g_slist_free(compose->passworded_ldap_servers);
9082 if (compose->updating) {
9083 debug_print("danger, not destroying anything now\n");
9084 compose->deferred_destroy = TRUE;
9088 /* NOTE: address_completion_end() does nothing with the window
9089 * however this may change. */
9090 address_completion_end(compose->window);
9092 slist_free_strings_full(compose->to_list);
9093 slist_free_strings_full(compose->newsgroup_list);
9094 slist_free_strings_full(compose->header_list);
9096 slist_free_strings_full(extra_headers);
9097 extra_headers = NULL;
9099 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9101 g_hash_table_destroy(compose->email_hashtable);
9103 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9104 compose->folder_update_callback_id);
9106 procmsg_msginfo_free(&(compose->targetinfo));
9107 procmsg_msginfo_free(&(compose->replyinfo));
9108 procmsg_msginfo_free(&(compose->fwdinfo));
9110 g_free(compose->replyto);
9111 g_free(compose->cc);
9112 g_free(compose->bcc);
9113 g_free(compose->newsgroups);
9114 g_free(compose->followup_to);
9116 g_free(compose->ml_post);
9118 g_free(compose->inreplyto);
9119 g_free(compose->references);
9120 g_free(compose->msgid);
9121 g_free(compose->boundary);
9123 g_free(compose->redirect_filename);
9124 if (compose->undostruct)
9125 undo_destroy(compose->undostruct);
9127 g_free(compose->sig_str);
9129 g_free(compose->exteditor_file);
9131 g_free(compose->orig_charset);
9133 g_free(compose->privacy_system);
9134 g_free(compose->encdata);
9136 #ifndef USE_ALT_ADDRBOOK
9137 if (addressbook_get_target_compose() == compose)
9138 addressbook_set_target_compose(NULL);
9141 if (compose->gtkaspell) {
9142 gtkaspell_delete(compose->gtkaspell);
9143 compose->gtkaspell = NULL;
9147 if (!compose->batch) {
9148 gtk_widget_get_allocation(compose->window, &allocation);
9149 prefs_common.compose_width = allocation.width;
9150 prefs_common.compose_height = allocation.height;
9153 if (!gtk_widget_get_parent(compose->paned))
9154 gtk_widget_destroy(compose->paned);
9155 gtk_widget_destroy(compose->popupmenu);
9157 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9158 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9159 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9161 gtk_widget_destroy(compose->window);
9162 toolbar_destroy(compose->toolbar);
9163 g_free(compose->toolbar);
9164 cm_mutex_free(compose->mutex);
9168 static void compose_attach_info_free(AttachInfo *ainfo)
9170 g_free(ainfo->file);
9171 g_free(ainfo->content_type);
9172 g_free(ainfo->name);
9173 g_free(ainfo->charset);
9177 static void compose_attach_update_label(Compose *compose)
9182 GtkTreeModel *model;
9186 if (compose == NULL)
9189 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9190 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9191 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9195 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9196 total_size = ainfo->size;
9197 while(gtk_tree_model_iter_next(model, &iter)) {
9198 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9199 total_size += ainfo->size;
9202 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9203 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9207 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9209 Compose *compose = (Compose *)data;
9210 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9211 GtkTreeSelection *selection;
9213 GtkTreeModel *model;
9215 selection = gtk_tree_view_get_selection(tree_view);
9216 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9217 cm_return_if_fail(sel);
9219 for (cur = sel; cur != NULL; cur = cur->next) {
9220 GtkTreePath *path = cur->data;
9221 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9224 gtk_tree_path_free(path);
9227 for (cur = sel; cur != NULL; cur = cur->next) {
9228 GtkTreeRowReference *ref = cur->data;
9229 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9232 if (gtk_tree_model_get_iter(model, &iter, path))
9233 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9235 gtk_tree_path_free(path);
9236 gtk_tree_row_reference_free(ref);
9240 compose_attach_update_label(compose);
9243 static struct _AttachProperty
9246 GtkWidget *mimetype_entry;
9247 GtkWidget *encoding_optmenu;
9248 GtkWidget *path_entry;
9249 GtkWidget *filename_entry;
9251 GtkWidget *cancel_btn;
9254 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9256 gtk_tree_path_free((GtkTreePath *)ptr);
9259 static void compose_attach_property(GtkAction *action, gpointer data)
9261 Compose *compose = (Compose *)data;
9262 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9264 GtkComboBox *optmenu;
9265 GtkTreeSelection *selection;
9267 GtkTreeModel *model;
9270 static gboolean cancelled;
9272 /* only if one selected */
9273 selection = gtk_tree_view_get_selection(tree_view);
9274 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9277 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9278 cm_return_if_fail(sel);
9280 path = (GtkTreePath *) sel->data;
9281 gtk_tree_model_get_iter(model, &iter, path);
9282 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9285 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9291 if (!attach_prop.window)
9292 compose_attach_property_create(&cancelled);
9293 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9294 gtk_widget_grab_focus(attach_prop.ok_btn);
9295 gtk_widget_show(attach_prop.window);
9296 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9297 GTK_WINDOW(compose->window));
9299 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9300 if (ainfo->encoding == ENC_UNKNOWN)
9301 combobox_select_by_data(optmenu, ENC_BASE64);
9303 combobox_select_by_data(optmenu, ainfo->encoding);
9305 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9306 ainfo->content_type ? ainfo->content_type : "");
9307 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9308 ainfo->file ? ainfo->file : "");
9309 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9310 ainfo->name ? ainfo->name : "");
9313 const gchar *entry_text;
9315 gchar *cnttype = NULL;
9322 gtk_widget_hide(attach_prop.window);
9323 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9328 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9329 if (*entry_text != '\0') {
9332 text = g_strstrip(g_strdup(entry_text));
9333 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9334 cnttype = g_strdup(text);
9337 alertpanel_error(_("Invalid MIME type."));
9343 ainfo->encoding = combobox_get_active_data(optmenu);
9345 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9346 if (*entry_text != '\0') {
9347 if (is_file_exist(entry_text) &&
9348 (size = get_file_size(entry_text)) > 0)
9349 file = g_strdup(entry_text);
9352 (_("File doesn't exist or is empty."));
9358 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9359 if (*entry_text != '\0') {
9360 g_free(ainfo->name);
9361 ainfo->name = g_strdup(entry_text);
9365 g_free(ainfo->content_type);
9366 ainfo->content_type = cnttype;
9369 g_free(ainfo->file);
9373 ainfo->size = (goffset)size;
9375 /* update tree store */
9376 text = to_human_readable(ainfo->size);
9377 gtk_tree_model_get_iter(model, &iter, path);
9378 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9379 COL_MIMETYPE, ainfo->content_type,
9381 COL_NAME, ainfo->name,
9382 COL_CHARSET, ainfo->charset,
9388 gtk_tree_path_free(path);
9391 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9393 label = gtk_label_new(str); \
9394 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9395 GTK_FILL, 0, 0, 0); \
9396 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9398 entry = gtk_entry_new(); \
9399 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9400 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9403 static void compose_attach_property_create(gboolean *cancelled)
9409 GtkWidget *mimetype_entry;
9412 GtkListStore *optmenu_menu;
9413 GtkWidget *path_entry;
9414 GtkWidget *filename_entry;
9417 GtkWidget *cancel_btn;
9418 GList *mime_type_list, *strlist;
9421 debug_print("Creating attach_property window...\n");
9423 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9424 gtk_widget_set_size_request(window, 480, -1);
9425 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9426 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9427 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9428 g_signal_connect(G_OBJECT(window), "delete_event",
9429 G_CALLBACK(attach_property_delete_event),
9431 g_signal_connect(G_OBJECT(window), "key_press_event",
9432 G_CALLBACK(attach_property_key_pressed),
9435 vbox = gtk_vbox_new(FALSE, 8);
9436 gtk_container_add(GTK_CONTAINER(window), vbox);
9438 table = gtk_table_new(4, 2, FALSE);
9439 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9440 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9441 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9443 label = gtk_label_new(_("MIME type"));
9444 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9446 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9447 mimetype_entry = gtk_combo_box_text_new_with_entry();
9448 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9449 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9451 /* stuff with list */
9452 mime_type_list = procmime_get_mime_type_list();
9454 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9455 MimeType *type = (MimeType *) mime_type_list->data;
9458 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9460 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9463 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9464 (GCompareFunc)strcmp2);
9467 for (mime_type_list = strlist; mime_type_list != NULL;
9468 mime_type_list = mime_type_list->next) {
9469 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9470 g_free(mime_type_list->data);
9472 g_list_free(strlist);
9473 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9474 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9476 label = gtk_label_new(_("Encoding"));
9477 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9479 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9481 hbox = gtk_hbox_new(FALSE, 0);
9482 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9483 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9485 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9486 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9488 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9489 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9490 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9491 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9492 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9494 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9496 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9497 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9499 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9500 &ok_btn, GTK_STOCK_OK,
9502 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9503 gtk_widget_grab_default(ok_btn);
9505 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9506 G_CALLBACK(attach_property_ok),
9508 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9509 G_CALLBACK(attach_property_cancel),
9512 gtk_widget_show_all(vbox);
9514 attach_prop.window = window;
9515 attach_prop.mimetype_entry = mimetype_entry;
9516 attach_prop.encoding_optmenu = optmenu;
9517 attach_prop.path_entry = path_entry;
9518 attach_prop.filename_entry = filename_entry;
9519 attach_prop.ok_btn = ok_btn;
9520 attach_prop.cancel_btn = cancel_btn;
9523 #undef SET_LABEL_AND_ENTRY
9525 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9531 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9537 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9538 gboolean *cancelled)
9546 static gboolean attach_property_key_pressed(GtkWidget *widget,
9548 gboolean *cancelled)
9550 if (event && event->keyval == GDK_KEY_Escape) {
9554 if (event && event->keyval == GDK_KEY_Return) {
9562 static void compose_exec_ext_editor(Compose *compose)
9567 GdkNativeWindow socket_wid = 0;
9571 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9572 G_DIR_SEPARATOR, compose);
9574 if (compose_get_ext_editor_uses_socket()) {
9575 /* Only allow one socket */
9576 if (compose->exteditor_socket != NULL) {
9577 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9578 /* Move the focus off of the socket */
9579 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9584 /* Create the receiving GtkSocket */
9585 socket = gtk_socket_new ();
9586 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9587 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9589 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9590 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9591 /* Realize the socket so that we can use its ID */
9592 gtk_widget_realize(socket);
9593 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9594 compose->exteditor_socket = socket;
9597 if (pipe(pipe_fds) < 0) {
9603 if ((pid = fork()) < 0) {
9610 /* close the write side of the pipe */
9613 compose->exteditor_file = g_strdup(tmp);
9614 compose->exteditor_pid = pid;
9616 compose_set_ext_editor_sensitive(compose, FALSE);
9619 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9621 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9623 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9627 } else { /* process-monitoring process */
9633 /* close the read side of the pipe */
9636 if (compose_write_body_to_file(compose, tmp) < 0) {
9637 fd_write_all(pipe_fds[1], "2\n", 2);
9641 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9643 fd_write_all(pipe_fds[1], "1\n", 2);
9647 /* wait until editor is terminated */
9648 waitpid(pid_ed, NULL, 0);
9650 fd_write_all(pipe_fds[1], "0\n", 2);
9657 #endif /* G_OS_UNIX */
9660 static gboolean compose_can_autosave(Compose *compose)
9662 if (compose->privacy_system && compose->use_encryption)
9663 return prefs_common.autosave && prefs_common.autosave_encrypted;
9665 return prefs_common.autosave;
9669 static gboolean compose_get_ext_editor_cmd_valid()
9671 gboolean has_s = FALSE;
9672 gboolean has_w = FALSE;
9673 const gchar *p = prefs_common_get_ext_editor_cmd();
9676 while ((p = strchr(p, '%'))) {
9682 } else if (*p == 'w') {
9693 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9700 cm_return_val_if_fail(file != NULL, -1);
9702 if ((pid = fork()) < 0) {
9707 if (pid != 0) return pid;
9709 /* grandchild process */
9711 if (setpgid(0, getppid()))
9714 if (compose_get_ext_editor_cmd_valid()) {
9715 if (compose_get_ext_editor_uses_socket()) {
9716 p = g_strdup(prefs_common_get_ext_editor_cmd());
9717 s = strstr(p, "%w");
9719 if (strstr(p, "%s") < s)
9720 buf = g_strdup_printf(p, file, socket_wid);
9722 buf = g_strdup_printf(p, socket_wid, file);
9725 buf = g_strdup_printf(prefs_common_get_ext_editor_cmd(), file);
9728 if (prefs_common_get_ext_editor_cmd())
9729 g_warning("External editor command-line is invalid: '%s'",
9730 prefs_common_get_ext_editor_cmd());
9731 buf = g_strdup_printf(DEFAULT_EDITOR_CMD, file);
9734 cmdline = strsplit_with_quote(buf, " ", 0);
9736 execvp(cmdline[0], cmdline);
9739 g_strfreev(cmdline);
9744 static gboolean compose_ext_editor_kill(Compose *compose)
9746 pid_t pgid = compose->exteditor_pid * -1;
9749 ret = kill(pgid, 0);
9751 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9755 msg = g_strdup_printf
9756 (_("The external editor is still working.\n"
9757 "Force terminating the process?\n"
9758 "process group id: %d"), -pgid);
9759 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9760 NULL, ALERTFOCUS_FIRST, FALSE, NULL,
9765 if (val == G_ALERTALTERNATE) {
9766 g_source_remove(compose->exteditor_tag);
9767 g_io_channel_shutdown(compose->exteditor_ch,
9769 g_io_channel_unref(compose->exteditor_ch);
9771 if (kill(pgid, SIGTERM) < 0) perror("kill");
9772 waitpid(compose->exteditor_pid, NULL, 0);
9774 g_warning("Terminated process group id: %d. "
9775 "Temporary file: %s", -pgid, compose->exteditor_file);
9777 compose_set_ext_editor_sensitive(compose, TRUE);
9779 g_free(compose->exteditor_file);
9780 compose->exteditor_file = NULL;
9781 compose->exteditor_pid = -1;
9782 compose->exteditor_ch = NULL;
9783 compose->exteditor_tag = -1;
9791 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9795 Compose *compose = (Compose *)data;
9798 debug_print("Compose: input from monitoring process\n");
9800 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9805 g_io_channel_shutdown(source, FALSE, NULL);
9806 g_io_channel_unref(source);
9808 waitpid(compose->exteditor_pid, NULL, 0);
9810 if (buf[0] == '0') { /* success */
9811 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9812 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9813 GtkTextIter start, end;
9816 gtk_text_buffer_set_text(buffer, "", -1);
9817 compose_insert_file(compose, compose->exteditor_file);
9818 compose_changed_cb(NULL, compose);
9820 /* Check if we should save the draft or not */
9821 if (compose_can_autosave(compose))
9822 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9824 if (claws_unlink(compose->exteditor_file) < 0)
9825 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9827 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9828 gtk_text_buffer_get_start_iter(buffer, &start);
9829 gtk_text_buffer_get_end_iter(buffer, &end);
9830 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9831 if (chars && strlen(chars) > 0)
9832 compose->modified = TRUE;
9834 } else if (buf[0] == '1') { /* failed */
9835 g_warning("Couldn't exec external editor");
9836 if (claws_unlink(compose->exteditor_file) < 0)
9837 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9838 } else if (buf[0] == '2') {
9839 g_warning("Couldn't write to file");
9840 } else if (buf[0] == '3') {
9841 g_warning("Pipe read failed");
9844 compose_set_ext_editor_sensitive(compose, TRUE);
9846 g_free(compose->exteditor_file);
9847 compose->exteditor_file = NULL;
9848 compose->exteditor_pid = -1;
9849 compose->exteditor_ch = NULL;
9850 compose->exteditor_tag = -1;
9851 if (compose->exteditor_socket) {
9852 gtk_widget_destroy(compose->exteditor_socket);
9853 compose->exteditor_socket = NULL;
9860 static char *ext_editor_menu_entries[] = {
9861 "Menu/Message/Send",
9862 "Menu/Message/SendLater",
9863 "Menu/Message/InsertFile",
9864 "Menu/Message/InsertSig",
9865 "Menu/Message/ReplaceSig",
9866 "Menu/Message/Save",
9867 "Menu/Message/Print",
9872 "Menu/Tools/ShowRuler",
9873 "Menu/Tools/Actions",
9878 static void compose_set_ext_editor_sensitive(Compose *compose,
9883 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9884 cm_menu_set_sensitive_full(compose->ui_manager,
9885 ext_editor_menu_entries[i], sensitive);
9888 if (compose_get_ext_editor_uses_socket()) {
9890 if (compose->exteditor_socket)
9891 gtk_widget_hide(compose->exteditor_socket);
9892 gtk_widget_show(compose->scrolledwin);
9893 if (prefs_common.show_ruler)
9894 gtk_widget_show(compose->ruler_hbox);
9895 /* Fix the focus, as it doesn't go anywhere when the
9896 * socket is hidden or destroyed */
9897 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9899 g_assert (compose->exteditor_socket != NULL);
9900 /* Fix the focus, as it doesn't go anywhere when the
9901 * edit box is hidden */
9902 if (gtk_widget_is_focus(compose->text))
9903 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9904 gtk_widget_hide(compose->scrolledwin);
9905 gtk_widget_hide(compose->ruler_hbox);
9906 gtk_widget_show(compose->exteditor_socket);
9909 gtk_widget_set_sensitive(compose->text, sensitive);
9911 if (compose->toolbar->send_btn)
9912 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9913 if (compose->toolbar->sendl_btn)
9914 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9915 if (compose->toolbar->draft_btn)
9916 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9917 if (compose->toolbar->insert_btn)
9918 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9919 if (compose->toolbar->sig_btn)
9920 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9921 if (compose->toolbar->exteditor_btn)
9922 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9923 if (compose->toolbar->linewrap_current_btn)
9924 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9925 if (compose->toolbar->linewrap_all_btn)
9926 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9929 static gboolean compose_get_ext_editor_uses_socket()
9931 return (prefs_common_get_ext_editor_cmd() &&
9932 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9935 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9937 compose->exteditor_socket = NULL;
9938 /* returning FALSE allows destruction of the socket */
9941 #endif /* G_OS_UNIX */
9944 * compose_undo_state_changed:
9946 * Change the sensivity of the menuentries undo and redo
9948 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9949 gint redo_state, gpointer data)
9951 Compose *compose = (Compose *)data;
9953 switch (undo_state) {
9954 case UNDO_STATE_TRUE:
9955 if (!undostruct->undo_state) {
9956 undostruct->undo_state = TRUE;
9957 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9960 case UNDO_STATE_FALSE:
9961 if (undostruct->undo_state) {
9962 undostruct->undo_state = FALSE;
9963 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9966 case UNDO_STATE_UNCHANGED:
9968 case UNDO_STATE_REFRESH:
9969 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9972 g_warning("Undo state not recognized");
9976 switch (redo_state) {
9977 case UNDO_STATE_TRUE:
9978 if (!undostruct->redo_state) {
9979 undostruct->redo_state = TRUE;
9980 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9983 case UNDO_STATE_FALSE:
9984 if (undostruct->redo_state) {
9985 undostruct->redo_state = FALSE;
9986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9989 case UNDO_STATE_UNCHANGED:
9991 case UNDO_STATE_REFRESH:
9992 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9995 g_warning("Redo state not recognized");
10000 /* callback functions */
10002 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10003 GtkAllocation *allocation,
10006 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10009 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10010 * includes "non-client" (windows-izm) in calculation, so this calculation
10011 * may not be accurate.
10013 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10014 GtkAllocation *allocation,
10015 GtkSHRuler *shruler)
10017 if (prefs_common.show_ruler) {
10018 gint char_width = 0, char_height = 0;
10019 gint line_width_in_chars;
10021 gtkut_get_font_size(GTK_WIDGET(widget),
10022 &char_width, &char_height);
10023 line_width_in_chars =
10024 (allocation->width - allocation->x) / char_width;
10026 /* got the maximum */
10027 gtk_shruler_set_range(GTK_SHRULER(shruler),
10028 0.0, line_width_in_chars, 0);
10037 ComposePrefType type;
10038 gboolean entry_marked;
10039 } HeaderEntryState;
10041 static void account_activated(GtkComboBox *optmenu, gpointer data)
10043 Compose *compose = (Compose *)data;
10046 gchar *folderidentifier;
10047 gint account_id = 0;
10048 GtkTreeModel *menu;
10050 GSList *list, *saved_list = NULL;
10051 HeaderEntryState *state;
10053 /* Get ID of active account in the combo box */
10054 menu = gtk_combo_box_get_model(optmenu);
10055 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10056 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10058 ac = account_find_from_id(account_id);
10059 cm_return_if_fail(ac != NULL);
10061 if (ac != compose->account) {
10062 compose_select_account(compose, ac, FALSE);
10064 for (list = compose->header_list; list; list = list->next) {
10065 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10067 if (hentry->type == PREF_ACCOUNT || !list->next) {
10068 compose_destroy_headerentry(compose, hentry);
10071 state = g_malloc0(sizeof(HeaderEntryState));
10072 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10073 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10074 state->entry = gtk_editable_get_chars(
10075 GTK_EDITABLE(hentry->entry), 0, -1);
10076 state->type = hentry->type;
10078 saved_list = g_slist_append(saved_list, state);
10079 compose_destroy_headerentry(compose, hentry);
10082 compose->header_last = NULL;
10083 g_slist_free(compose->header_list);
10084 compose->header_list = NULL;
10085 compose->header_nextrow = 1;
10086 compose_create_header_entry(compose);
10088 if (ac->set_autocc && ac->auto_cc)
10089 compose_entry_append(compose, ac->auto_cc,
10090 COMPOSE_CC, PREF_ACCOUNT);
10091 if (ac->set_autobcc && ac->auto_bcc)
10092 compose_entry_append(compose, ac->auto_bcc,
10093 COMPOSE_BCC, PREF_ACCOUNT);
10094 if (ac->set_autoreplyto && ac->auto_replyto)
10095 compose_entry_append(compose, ac->auto_replyto,
10096 COMPOSE_REPLYTO, PREF_ACCOUNT);
10098 for (list = saved_list; list; list = list->next) {
10099 state = (HeaderEntryState *) list->data;
10101 compose_add_header_entry(compose, state->header,
10102 state->entry, state->type);
10104 g_free(state->header);
10105 g_free(state->entry);
10108 g_slist_free(saved_list);
10110 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10111 (ac->protocol == A_NNTP) ?
10112 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10115 /* Set message save folder */
10116 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10117 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
10119 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
10120 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
10122 compose_set_save_to(compose, NULL);
10123 if (account_get_special_folder(compose->account, F_OUTBOX)) {
10124 folderidentifier = folder_item_get_identifier(account_get_special_folder
10125 (compose->account, F_OUTBOX));
10126 compose_set_save_to(compose, folderidentifier);
10127 g_free(folderidentifier);
10131 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10132 GtkTreeViewColumn *column, Compose *compose)
10134 compose_attach_property(NULL, compose);
10137 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10140 Compose *compose = (Compose *)data;
10141 GtkTreeSelection *attach_selection;
10142 gint attach_nr_selected;
10145 if (!event) return FALSE;
10147 if (event->button == 3) {
10148 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10149 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10151 /* If no rows, or just one row is selected, right-click should
10152 * open menu relevant to the row being right-clicked on. We
10153 * achieve that by selecting the clicked row first. If more
10154 * than one row is selected, we shouldn't modify the selection,
10155 * as user may want to remove selected rows (attachments). */
10156 if (attach_nr_selected < 2) {
10157 gtk_tree_selection_unselect_all(attach_selection);
10158 attach_nr_selected = 0;
10159 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10160 event->x, event->y, &path, NULL, NULL, NULL);
10161 if (path != NULL) {
10162 gtk_tree_selection_select_path(attach_selection, path);
10163 gtk_tree_path_free(path);
10164 attach_nr_selected++;
10168 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10169 /* Properties menu item makes no sense with more than one row
10170 * selected, the properties dialog can only edit one attachment. */
10171 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10173 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10174 NULL, NULL, event->button, event->time);
10181 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10184 Compose *compose = (Compose *)data;
10186 if (!event) return FALSE;
10188 switch (event->keyval) {
10189 case GDK_KEY_Delete:
10190 compose_attach_remove_selected(NULL, compose);
10196 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10198 toolbar_comp_set_sensitive(compose, allow);
10199 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10200 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10202 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10205 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10206 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10208 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10212 static void compose_send_cb(GtkAction *action, gpointer data)
10214 Compose *compose = (Compose *)data;
10217 if (compose->exteditor_tag != -1) {
10218 debug_print("ignoring send: external editor still open\n");
10222 if (prefs_common.work_offline &&
10223 !inc_offline_should_override(TRUE,
10224 _("Claws Mail needs network access in order "
10225 "to send this email.")))
10228 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10229 g_source_remove(compose->draft_timeout_tag);
10230 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10233 compose_send(compose);
10236 static void compose_send_later_cb(GtkAction *action, gpointer data)
10238 Compose *compose = (Compose *)data;
10239 ComposeQueueResult val;
10242 compose_allow_user_actions(compose, FALSE);
10243 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10244 compose_allow_user_actions(compose, TRUE);
10247 if (val == COMPOSE_QUEUE_SUCCESS) {
10248 compose_close(compose);
10250 _display_queue_error(val);
10253 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10256 #define DRAFTED_AT_EXIT "drafted_at_exit"
10257 static void compose_register_draft(MsgInfo *info)
10259 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10260 DRAFTED_AT_EXIT, NULL);
10261 FILE *fp = g_fopen(filepath, "ab");
10264 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10272 gboolean compose_draft (gpointer data, guint action)
10274 Compose *compose = (Compose *)data;
10279 MsgFlags flag = {0, 0};
10280 static gboolean lock = FALSE;
10281 MsgInfo *newmsginfo;
10283 gboolean target_locked = FALSE;
10284 gboolean err = FALSE;
10286 if (lock) return FALSE;
10288 if (compose->sending)
10291 draft = account_get_special_folder(compose->account, F_DRAFT);
10292 cm_return_val_if_fail(draft != NULL, FALSE);
10294 if (!g_mutex_trylock(compose->mutex)) {
10295 /* we don't want to lock the mutex once it's available,
10296 * because as the only other part of compose.c locking
10297 * it is compose_close - which means once unlocked,
10298 * the compose struct will be freed */
10299 debug_print("couldn't lock mutex, probably sending\n");
10305 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10306 G_DIR_SEPARATOR, compose);
10307 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10308 FILE_OP_ERROR(tmp, "fopen");
10312 /* chmod for security */
10313 if (change_file_mode_rw(fp, tmp) < 0) {
10314 FILE_OP_ERROR(tmp, "chmod");
10315 g_warning("can't change file mode");
10318 /* Save draft infos */
10319 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10320 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10322 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10323 gchar *savefolderid;
10325 savefolderid = compose_get_save_to(compose);
10326 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10327 g_free(savefolderid);
10329 if (compose->return_receipt) {
10330 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10332 if (compose->privacy_system) {
10333 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10334 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10335 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10338 /* Message-ID of message replying to */
10339 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10340 gchar *folderid = NULL;
10342 if (compose->replyinfo->folder)
10343 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10344 if (folderid == NULL)
10345 folderid = g_strdup("NULL");
10347 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10350 /* Message-ID of message forwarding to */
10351 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10352 gchar *folderid = NULL;
10354 if (compose->fwdinfo->folder)
10355 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10356 if (folderid == NULL)
10357 folderid = g_strdup("NULL");
10359 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10363 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10364 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10366 sheaders = compose_get_manual_headers_info(compose);
10367 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10370 /* end of headers */
10371 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10378 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10382 if (fclose(fp) == EOF) {
10386 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10387 if (compose->targetinfo) {
10388 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10390 flag.perm_flags |= MSG_LOCKED;
10392 flag.tmp_flags = MSG_DRAFT;
10394 folder_item_scan(draft);
10395 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10396 MsgInfo *tmpinfo = NULL;
10397 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10398 if (compose->msgid) {
10399 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10402 msgnum = tmpinfo->msgnum;
10403 procmsg_msginfo_free(&tmpinfo);
10404 debug_print("got draft msgnum %d from scanning\n", msgnum);
10406 debug_print("didn't get draft msgnum after scanning\n");
10409 debug_print("got draft msgnum %d from adding\n", msgnum);
10415 if (action != COMPOSE_AUTO_SAVE) {
10416 if (action != COMPOSE_DRAFT_FOR_EXIT)
10417 alertpanel_error(_("Could not save draft."));
10420 gtkut_window_popup(compose->window);
10421 val = alertpanel_full(_("Could not save draft"),
10422 _("Could not save draft.\n"
10423 "Do you want to cancel exit or discard this email?"),
10424 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10425 FALSE, NULL, ALERT_QUESTION);
10426 if (val == G_ALERTALTERNATE) {
10428 g_mutex_unlock(compose->mutex); /* must be done before closing */
10429 compose_close(compose);
10433 g_mutex_unlock(compose->mutex); /* must be done before closing */
10442 if (compose->mode == COMPOSE_REEDIT) {
10443 compose_remove_reedit_target(compose, TRUE);
10446 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10449 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10451 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10453 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10454 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10455 procmsg_msginfo_set_flags(newmsginfo, 0,
10456 MSG_HAS_ATTACHMENT);
10458 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10459 compose_register_draft(newmsginfo);
10461 procmsg_msginfo_free(&newmsginfo);
10464 folder_item_scan(draft);
10466 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10468 g_mutex_unlock(compose->mutex); /* must be done before closing */
10469 compose_close(compose);
10476 GError *error = NULL;
10481 goffset size, mtime;
10483 path = folder_item_fetch_msg(draft, msgnum);
10484 if (path == NULL) {
10485 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10489 f = g_file_new_for_path(path);
10490 fi = g_file_query_info(f, "standard::size,time::modified",
10491 G_FILE_QUERY_INFO_NONE, NULL, &error);
10492 if (error != NULL) {
10493 debug_print("couldn't query file info for '%s': %s\n",
10494 path, error->message);
10495 g_error_free(error);
10500 size = g_file_info_get_size(fi);
10501 g_file_info_get_modification_time(fi, &tv);
10503 g_object_unref(fi);
10506 if (g_stat(path, &s) < 0) {
10507 FILE_OP_ERROR(path, "stat");
10512 mtime = s.st_mtime;
10516 procmsg_msginfo_free(&(compose->targetinfo));
10517 compose->targetinfo = procmsg_msginfo_new();
10518 compose->targetinfo->msgnum = msgnum;
10519 compose->targetinfo->size = size;
10520 compose->targetinfo->mtime = mtime;
10521 compose->targetinfo->folder = draft;
10523 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10524 compose->mode = COMPOSE_REEDIT;
10526 if (action == COMPOSE_AUTO_SAVE) {
10527 compose->autosaved_draft = compose->targetinfo;
10529 compose->modified = FALSE;
10530 compose_set_title(compose);
10534 g_mutex_unlock(compose->mutex);
10538 void compose_clear_exit_drafts(void)
10540 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10541 DRAFTED_AT_EXIT, NULL);
10542 if (is_file_exist(filepath))
10543 claws_unlink(filepath);
10548 void compose_reopen_exit_drafts(void)
10550 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10551 DRAFTED_AT_EXIT, NULL);
10552 FILE *fp = g_fopen(filepath, "rb");
10556 while (fgets(buf, sizeof(buf), fp)) {
10557 gchar **parts = g_strsplit(buf, "\t", 2);
10558 const gchar *folder = parts[0];
10559 int msgnum = parts[1] ? atoi(parts[1]):-1;
10561 if (folder && *folder && msgnum > -1) {
10562 FolderItem *item = folder_find_item_from_identifier(folder);
10563 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10565 compose_reedit(info, FALSE);
10572 compose_clear_exit_drafts();
10575 static void compose_save_cb(GtkAction *action, gpointer data)
10577 Compose *compose = (Compose *)data;
10578 compose_draft(compose, COMPOSE_KEEP_EDITING);
10579 compose->rmode = COMPOSE_REEDIT;
10582 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10584 if (compose && file_list) {
10587 for ( tmp = file_list; tmp; tmp = tmp->next) {
10588 gchar *file = (gchar *) tmp->data;
10589 gchar *utf8_filename = conv_filename_to_utf8(file);
10590 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10591 compose_changed_cb(NULL, compose);
10596 g_free(utf8_filename);
10601 static void compose_attach_cb(GtkAction *action, gpointer data)
10603 Compose *compose = (Compose *)data;
10606 if (compose->redirect_filename != NULL)
10609 /* Set focus_window properly, in case we were called via popup menu,
10610 * which unsets it (via focus_out_event callback on compose window). */
10611 manage_window_focus_in(compose->window, NULL, NULL);
10613 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10616 compose_attach_from_list(compose, file_list, TRUE);
10617 g_list_free(file_list);
10621 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10623 Compose *compose = (Compose *)data;
10625 gint files_inserted = 0;
10627 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10632 for ( tmp = file_list; tmp; tmp = tmp->next) {
10633 gchar *file = (gchar *) tmp->data;
10634 gchar *filedup = g_strdup(file);
10635 gchar *shortfile = g_path_get_basename(filedup);
10636 ComposeInsertResult res;
10637 /* insert the file if the file is short or if the user confirmed that
10638 he/she wants to insert the large file */
10639 res = compose_insert_file(compose, file);
10640 if (res == COMPOSE_INSERT_READ_ERROR) {
10641 alertpanel_error(_("File '%s' could not be read."), shortfile);
10642 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10643 alertpanel_error(_("File '%s' contained invalid characters\n"
10644 "for the current encoding, insertion may be incorrect."),
10646 } else if (res == COMPOSE_INSERT_SUCCESS)
10653 g_list_free(file_list);
10657 if (files_inserted > 0 && compose->gtkaspell &&
10658 compose->gtkaspell->check_while_typing)
10659 gtkaspell_highlight_all(compose->gtkaspell);
10663 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10665 Compose *compose = (Compose *)data;
10667 compose_insert_sig(compose, FALSE);
10670 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10672 Compose *compose = (Compose *)data;
10674 compose_insert_sig(compose, TRUE);
10677 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10681 Compose *compose = (Compose *)data;
10683 gtkut_widget_get_uposition(widget, &x, &y);
10684 if (!compose->batch) {
10685 prefs_common.compose_x = x;
10686 prefs_common.compose_y = y;
10688 if (compose->sending || compose->updating)
10690 compose_close_cb(NULL, compose);
10694 void compose_close_toolbar(Compose *compose)
10696 compose_close_cb(NULL, compose);
10699 static void compose_close_cb(GtkAction *action, gpointer data)
10701 Compose *compose = (Compose *)data;
10705 if (compose->exteditor_tag != -1) {
10706 if (!compose_ext_editor_kill(compose))
10711 if (compose->modified) {
10712 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10713 if (!g_mutex_trylock(compose->mutex)) {
10714 /* we don't want to lock the mutex once it's available,
10715 * because as the only other part of compose.c locking
10716 * it is compose_close - which means once unlocked,
10717 * the compose struct will be freed */
10718 debug_print("couldn't lock mutex, probably sending\n");
10722 val = alertpanel(_("Discard message"),
10723 _("This message has been modified. Discard it?"),
10724 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10727 val = alertpanel(_("Save changes"),
10728 _("This message has been modified. Save the latest changes?"),
10729 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10730 ALERTFOCUS_SECOND);
10732 g_mutex_unlock(compose->mutex);
10734 case G_ALERTDEFAULT:
10735 if (compose_can_autosave(compose) && !reedit)
10736 compose_remove_draft(compose);
10738 case G_ALERTALTERNATE:
10739 compose_draft(data, COMPOSE_QUIT_EDITING);
10746 compose_close(compose);
10749 static void compose_print_cb(GtkAction *action, gpointer data)
10751 Compose *compose = (Compose *) data;
10753 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10754 if (compose->targetinfo)
10755 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10758 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10760 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10761 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10762 Compose *compose = (Compose *) data;
10765 compose->out_encoding = (CharSet)value;
10768 static void compose_address_cb(GtkAction *action, gpointer data)
10770 Compose *compose = (Compose *)data;
10772 #ifndef USE_ALT_ADDRBOOK
10773 addressbook_open(compose);
10775 GError* error = NULL;
10776 addressbook_connect_signals(compose);
10777 addressbook_dbus_open(TRUE, &error);
10779 g_warning("%s", error->message);
10780 g_error_free(error);
10785 static void about_show_cb(GtkAction *action, gpointer data)
10790 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10792 Compose *compose = (Compose *)data;
10797 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10798 cm_return_if_fail(tmpl != NULL);
10800 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10802 val = alertpanel(_("Apply template"), msg,
10803 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10806 if (val == G_ALERTDEFAULT)
10807 compose_template_apply(compose, tmpl, TRUE);
10808 else if (val == G_ALERTALTERNATE)
10809 compose_template_apply(compose, tmpl, FALSE);
10812 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10814 Compose *compose = (Compose *)data;
10817 if (compose->exteditor_tag != -1) {
10818 debug_print("ignoring open external editor: external editor still open\n");
10822 compose_exec_ext_editor(compose);
10825 static void compose_undo_cb(GtkAction *action, gpointer data)
10827 Compose *compose = (Compose *)data;
10828 gboolean prev_autowrap = compose->autowrap;
10830 compose->autowrap = FALSE;
10831 undo_undo(compose->undostruct);
10832 compose->autowrap = prev_autowrap;
10835 static void compose_redo_cb(GtkAction *action, gpointer data)
10837 Compose *compose = (Compose *)data;
10838 gboolean prev_autowrap = compose->autowrap;
10840 compose->autowrap = FALSE;
10841 undo_redo(compose->undostruct);
10842 compose->autowrap = prev_autowrap;
10845 static void entry_cut_clipboard(GtkWidget *entry)
10847 if (GTK_IS_EDITABLE(entry))
10848 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10849 else if (GTK_IS_TEXT_VIEW(entry))
10850 gtk_text_buffer_cut_clipboard(
10851 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10852 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10856 static void entry_copy_clipboard(GtkWidget *entry)
10858 if (GTK_IS_EDITABLE(entry))
10859 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10860 else if (GTK_IS_TEXT_VIEW(entry))
10861 gtk_text_buffer_copy_clipboard(
10862 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10863 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10866 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10867 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10869 if (GTK_IS_TEXT_VIEW(entry)) {
10870 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10871 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10872 GtkTextIter start_iter, end_iter;
10874 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10876 if (contents == NULL)
10879 /* we shouldn't delete the selection when middle-click-pasting, or we
10880 * can't mid-click-paste our own selection */
10881 if (clip != GDK_SELECTION_PRIMARY) {
10882 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10883 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10886 if (insert_place == NULL) {
10887 /* if insert_place isn't specified, insert at the cursor.
10888 * used for Ctrl-V pasting */
10889 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10890 start = gtk_text_iter_get_offset(&start_iter);
10891 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10893 /* if insert_place is specified, paste here.
10894 * used for mid-click-pasting */
10895 start = gtk_text_iter_get_offset(insert_place);
10896 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10897 if (prefs_common.primary_paste_unselects)
10898 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10902 /* paste unwrapped: mark the paste so it's not wrapped later */
10903 end = start + strlen(contents);
10904 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10905 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10906 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10907 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10908 /* rewrap paragraph now (after a mid-click-paste) */
10909 mark_start = gtk_text_buffer_get_insert(buffer);
10910 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10911 gtk_text_iter_backward_char(&start_iter);
10912 compose_beautify_paragraph(compose, &start_iter, TRUE);
10914 } else if (GTK_IS_EDITABLE(entry))
10915 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10917 compose->modified = TRUE;
10920 static void entry_allsel(GtkWidget *entry)
10922 if (GTK_IS_EDITABLE(entry))
10923 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10924 else if (GTK_IS_TEXT_VIEW(entry)) {
10925 GtkTextIter startiter, enditer;
10926 GtkTextBuffer *textbuf;
10928 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10929 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10930 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10932 gtk_text_buffer_move_mark_by_name(textbuf,
10933 "selection_bound", &startiter);
10934 gtk_text_buffer_move_mark_by_name(textbuf,
10935 "insert", &enditer);
10939 static void compose_cut_cb(GtkAction *action, gpointer data)
10941 Compose *compose = (Compose *)data;
10942 if (compose->focused_editable
10943 #ifndef GENERIC_UMPC
10944 && gtk_widget_has_focus(compose->focused_editable)
10947 entry_cut_clipboard(compose->focused_editable);
10950 static void compose_copy_cb(GtkAction *action, gpointer data)
10952 Compose *compose = (Compose *)data;
10953 if (compose->focused_editable
10954 #ifndef GENERIC_UMPC
10955 && gtk_widget_has_focus(compose->focused_editable)
10958 entry_copy_clipboard(compose->focused_editable);
10961 static void compose_paste_cb(GtkAction *action, gpointer data)
10963 Compose *compose = (Compose *)data;
10964 gint prev_autowrap;
10965 GtkTextBuffer *buffer;
10967 if (compose->focused_editable
10968 #ifndef GENERIC_UMPC
10969 && gtk_widget_has_focus(compose->focused_editable)
10972 entry_paste_clipboard(compose, compose->focused_editable,
10973 prefs_common.linewrap_pastes,
10974 GDK_SELECTION_CLIPBOARD, NULL);
10979 #ifndef GENERIC_UMPC
10980 gtk_widget_has_focus(compose->text) &&
10982 compose->gtkaspell &&
10983 compose->gtkaspell->check_while_typing)
10984 gtkaspell_highlight_all(compose->gtkaspell);
10988 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10990 Compose *compose = (Compose *)data;
10991 gint wrap_quote = prefs_common.linewrap_quote;
10992 if (compose->focused_editable
10993 #ifndef GENERIC_UMPC
10994 && gtk_widget_has_focus(compose->focused_editable)
10997 /* let text_insert() (called directly or at a later time
10998 * after the gtk_editable_paste_clipboard) know that
10999 * text is to be inserted as a quotation. implemented
11000 * by using a simple refcount... */
11001 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11002 G_OBJECT(compose->focused_editable),
11003 "paste_as_quotation"));
11004 g_object_set_data(G_OBJECT(compose->focused_editable),
11005 "paste_as_quotation",
11006 GINT_TO_POINTER(paste_as_quotation + 1));
11007 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11008 entry_paste_clipboard(compose, compose->focused_editable,
11009 prefs_common.linewrap_pastes,
11010 GDK_SELECTION_CLIPBOARD, NULL);
11011 prefs_common.linewrap_quote = wrap_quote;
11015 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11017 Compose *compose = (Compose *)data;
11018 gint prev_autowrap;
11019 GtkTextBuffer *buffer;
11021 if (compose->focused_editable
11022 #ifndef GENERIC_UMPC
11023 && gtk_widget_has_focus(compose->focused_editable)
11026 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11027 GDK_SELECTION_CLIPBOARD, NULL);
11032 #ifndef GENERIC_UMPC
11033 gtk_widget_has_focus(compose->text) &&
11035 compose->gtkaspell &&
11036 compose->gtkaspell->check_while_typing)
11037 gtkaspell_highlight_all(compose->gtkaspell);
11041 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11043 Compose *compose = (Compose *)data;
11044 gint prev_autowrap;
11045 GtkTextBuffer *buffer;
11047 if (compose->focused_editable
11048 #ifndef GENERIC_UMPC
11049 && gtk_widget_has_focus(compose->focused_editable)
11052 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11053 GDK_SELECTION_CLIPBOARD, NULL);
11058 #ifndef GENERIC_UMPC
11059 gtk_widget_has_focus(compose->text) &&
11061 compose->gtkaspell &&
11062 compose->gtkaspell->check_while_typing)
11063 gtkaspell_highlight_all(compose->gtkaspell);
11067 static void compose_allsel_cb(GtkAction *action, gpointer data)
11069 Compose *compose = (Compose *)data;
11070 if (compose->focused_editable
11071 #ifndef GENERIC_UMPC
11072 && gtk_widget_has_focus(compose->focused_editable)
11075 entry_allsel(compose->focused_editable);
11078 static void textview_move_beginning_of_line (GtkTextView *text)
11080 GtkTextBuffer *buffer;
11084 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11086 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11087 mark = gtk_text_buffer_get_insert(buffer);
11088 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11089 gtk_text_iter_set_line_offset(&ins, 0);
11090 gtk_text_buffer_place_cursor(buffer, &ins);
11093 static void textview_move_forward_character (GtkTextView *text)
11095 GtkTextBuffer *buffer;
11099 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11101 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11102 mark = gtk_text_buffer_get_insert(buffer);
11103 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11104 if (gtk_text_iter_forward_cursor_position(&ins))
11105 gtk_text_buffer_place_cursor(buffer, &ins);
11108 static void textview_move_backward_character (GtkTextView *text)
11110 GtkTextBuffer *buffer;
11114 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11116 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11117 mark = gtk_text_buffer_get_insert(buffer);
11118 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11119 if (gtk_text_iter_backward_cursor_position(&ins))
11120 gtk_text_buffer_place_cursor(buffer, &ins);
11123 static void textview_move_forward_word (GtkTextView *text)
11125 GtkTextBuffer *buffer;
11130 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11132 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11133 mark = gtk_text_buffer_get_insert(buffer);
11134 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11135 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11136 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11137 gtk_text_iter_backward_word_start(&ins);
11138 gtk_text_buffer_place_cursor(buffer, &ins);
11142 static void textview_move_backward_word (GtkTextView *text)
11144 GtkTextBuffer *buffer;
11148 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11150 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11151 mark = gtk_text_buffer_get_insert(buffer);
11152 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11153 if (gtk_text_iter_backward_word_starts(&ins, 1))
11154 gtk_text_buffer_place_cursor(buffer, &ins);
11157 static void textview_move_end_of_line (GtkTextView *text)
11159 GtkTextBuffer *buffer;
11163 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11165 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11166 mark = gtk_text_buffer_get_insert(buffer);
11167 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11168 if (gtk_text_iter_forward_to_line_end(&ins))
11169 gtk_text_buffer_place_cursor(buffer, &ins);
11172 static void textview_move_next_line (GtkTextView *text)
11174 GtkTextBuffer *buffer;
11179 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11181 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11182 mark = gtk_text_buffer_get_insert(buffer);
11183 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11184 offset = gtk_text_iter_get_line_offset(&ins);
11185 if (gtk_text_iter_forward_line(&ins)) {
11186 gtk_text_iter_set_line_offset(&ins, offset);
11187 gtk_text_buffer_place_cursor(buffer, &ins);
11191 static void textview_move_previous_line (GtkTextView *text)
11193 GtkTextBuffer *buffer;
11198 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11200 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11201 mark = gtk_text_buffer_get_insert(buffer);
11202 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11203 offset = gtk_text_iter_get_line_offset(&ins);
11204 if (gtk_text_iter_backward_line(&ins)) {
11205 gtk_text_iter_set_line_offset(&ins, offset);
11206 gtk_text_buffer_place_cursor(buffer, &ins);
11210 static void textview_delete_forward_character (GtkTextView *text)
11212 GtkTextBuffer *buffer;
11214 GtkTextIter ins, end_iter;
11216 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11218 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11219 mark = gtk_text_buffer_get_insert(buffer);
11220 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11222 if (gtk_text_iter_forward_char(&end_iter)) {
11223 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11227 static void textview_delete_backward_character (GtkTextView *text)
11229 GtkTextBuffer *buffer;
11231 GtkTextIter ins, end_iter;
11233 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11235 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11236 mark = gtk_text_buffer_get_insert(buffer);
11237 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11239 if (gtk_text_iter_backward_char(&end_iter)) {
11240 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11244 static void textview_delete_forward_word (GtkTextView *text)
11246 GtkTextBuffer *buffer;
11248 GtkTextIter ins, end_iter;
11250 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11252 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11253 mark = gtk_text_buffer_get_insert(buffer);
11254 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11256 if (gtk_text_iter_forward_word_end(&end_iter)) {
11257 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11261 static void textview_delete_backward_word (GtkTextView *text)
11263 GtkTextBuffer *buffer;
11265 GtkTextIter ins, end_iter;
11267 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11269 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11270 mark = gtk_text_buffer_get_insert(buffer);
11271 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11273 if (gtk_text_iter_backward_word_start(&end_iter)) {
11274 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11278 static void textview_delete_line (GtkTextView *text)
11280 GtkTextBuffer *buffer;
11282 GtkTextIter ins, start_iter, end_iter;
11284 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11286 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11287 mark = gtk_text_buffer_get_insert(buffer);
11288 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11291 gtk_text_iter_set_line_offset(&start_iter, 0);
11294 if (gtk_text_iter_ends_line(&end_iter)){
11295 if (!gtk_text_iter_forward_char(&end_iter))
11296 gtk_text_iter_backward_char(&start_iter);
11299 gtk_text_iter_forward_to_line_end(&end_iter);
11300 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11303 static void textview_delete_to_line_end (GtkTextView *text)
11305 GtkTextBuffer *buffer;
11307 GtkTextIter ins, end_iter;
11309 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11311 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11312 mark = gtk_text_buffer_get_insert(buffer);
11313 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11315 if (gtk_text_iter_ends_line(&end_iter))
11316 gtk_text_iter_forward_char(&end_iter);
11318 gtk_text_iter_forward_to_line_end(&end_iter);
11319 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11322 #define DO_ACTION(name, act) { \
11323 if(!strcmp(name, a_name)) { \
11327 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11329 const gchar *a_name = gtk_action_get_name(action);
11330 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11331 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11332 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11333 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11334 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11335 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11336 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11337 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11338 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11339 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11340 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11341 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11342 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11343 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11344 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11347 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11349 Compose *compose = (Compose *)data;
11350 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11351 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11353 action = compose_call_advanced_action_from_path(gaction);
11356 void (*do_action) (GtkTextView *text);
11357 } action_table[] = {
11358 {textview_move_beginning_of_line},
11359 {textview_move_forward_character},
11360 {textview_move_backward_character},
11361 {textview_move_forward_word},
11362 {textview_move_backward_word},
11363 {textview_move_end_of_line},
11364 {textview_move_next_line},
11365 {textview_move_previous_line},
11366 {textview_delete_forward_character},
11367 {textview_delete_backward_character},
11368 {textview_delete_forward_word},
11369 {textview_delete_backward_word},
11370 {textview_delete_line},
11371 {textview_delete_to_line_end}
11374 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11376 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11377 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11378 if (action_table[action].do_action)
11379 action_table[action].do_action(text);
11381 g_warning("Not implemented yet.");
11385 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11387 GtkAllocation allocation;
11391 if (GTK_IS_EDITABLE(widget)) {
11392 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11393 gtk_editable_set_position(GTK_EDITABLE(widget),
11396 if ((parent = gtk_widget_get_parent(widget))
11397 && (parent = gtk_widget_get_parent(parent))
11398 && (parent = gtk_widget_get_parent(parent))) {
11399 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11400 gtk_widget_get_allocation(widget, &allocation);
11401 gint y = allocation.y;
11402 gint height = allocation.height;
11403 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11404 (GTK_SCROLLED_WINDOW(parent));
11406 gfloat value = gtk_adjustment_get_value(shown);
11407 gfloat upper = gtk_adjustment_get_upper(shown);
11408 gfloat page_size = gtk_adjustment_get_page_size(shown);
11409 if (y < (int)value) {
11410 gtk_adjustment_set_value(shown, y - 1);
11412 if ((y + height) > ((int)value + (int)page_size)) {
11413 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11414 gtk_adjustment_set_value(shown,
11415 y + height - (int)page_size - 1);
11417 gtk_adjustment_set_value(shown,
11418 (int)upper - (int)page_size - 1);
11425 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11426 compose->focused_editable = widget;
11428 #ifdef GENERIC_UMPC
11429 if (GTK_IS_TEXT_VIEW(widget)
11430 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11431 g_object_ref(compose->notebook);
11432 g_object_ref(compose->edit_vbox);
11433 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11434 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11435 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11436 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11437 g_object_unref(compose->notebook);
11438 g_object_unref(compose->edit_vbox);
11439 g_signal_handlers_block_by_func(G_OBJECT(widget),
11440 G_CALLBACK(compose_grab_focus_cb),
11442 gtk_widget_grab_focus(widget);
11443 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11444 G_CALLBACK(compose_grab_focus_cb),
11446 } else if (!GTK_IS_TEXT_VIEW(widget)
11447 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11448 g_object_ref(compose->notebook);
11449 g_object_ref(compose->edit_vbox);
11450 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11451 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11452 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11453 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11454 g_object_unref(compose->notebook);
11455 g_object_unref(compose->edit_vbox);
11456 g_signal_handlers_block_by_func(G_OBJECT(widget),
11457 G_CALLBACK(compose_grab_focus_cb),
11459 gtk_widget_grab_focus(widget);
11460 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11461 G_CALLBACK(compose_grab_focus_cb),
11467 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11469 compose->modified = TRUE;
11470 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11471 #ifndef GENERIC_UMPC
11472 compose_set_title(compose);
11476 static void compose_wrap_cb(GtkAction *action, gpointer data)
11478 Compose *compose = (Compose *)data;
11479 compose_beautify_paragraph(compose, NULL, TRUE);
11482 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11484 Compose *compose = (Compose *)data;
11485 compose_wrap_all_full(compose, TRUE);
11488 static void compose_find_cb(GtkAction *action, gpointer data)
11490 Compose *compose = (Compose *)data;
11492 message_search_compose(compose);
11495 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11498 Compose *compose = (Compose *)data;
11499 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11500 if (compose->autowrap)
11501 compose_wrap_all_full(compose, TRUE);
11502 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11505 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11508 Compose *compose = (Compose *)data;
11509 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11512 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11514 Compose *compose = (Compose *)data;
11516 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11517 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11520 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11522 Compose *compose = (Compose *)data;
11524 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11525 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11528 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11530 g_free(compose->privacy_system);
11531 g_free(compose->encdata);
11533 compose->privacy_system = g_strdup(account->default_privacy_system);
11534 compose_update_privacy_system_menu_item(compose, warn);
11537 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11539 if (folder_item != NULL) {
11540 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT) {
11541 compose_use_signing(compose,
11542 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11544 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT) {
11545 compose_use_encryption(compose,
11546 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11551 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11553 Compose *compose = (Compose *)data;
11555 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11556 gtk_widget_show(compose->ruler_hbox);
11557 prefs_common.show_ruler = TRUE;
11559 gtk_widget_hide(compose->ruler_hbox);
11560 gtk_widget_queue_resize(compose->edit_vbox);
11561 prefs_common.show_ruler = FALSE;
11565 static void compose_attach_drag_received_cb (GtkWidget *widget,
11566 GdkDragContext *context,
11569 GtkSelectionData *data,
11572 gpointer user_data)
11574 Compose *compose = (Compose *)user_data;
11578 type = gtk_selection_data_get_data_type(data);
11579 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11580 && gtk_drag_get_source_widget(context) !=
11581 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11582 list = uri_list_extract_filenames(
11583 (const gchar *)gtk_selection_data_get_data(data));
11584 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11585 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11586 compose_attach_append
11587 (compose, (const gchar *)tmp->data,
11588 utf8_filename, NULL, NULL);
11589 g_free(utf8_filename);
11592 compose_changed_cb(NULL, compose);
11593 list_free_strings_full(list);
11594 } else if (gtk_drag_get_source_widget(context)
11595 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11596 /* comes from our summaryview */
11597 SummaryView * summaryview = NULL;
11598 GSList * list = NULL, *cur = NULL;
11600 if (mainwindow_get_mainwindow())
11601 summaryview = mainwindow_get_mainwindow()->summaryview;
11604 list = summary_get_selected_msg_list(summaryview);
11606 for (cur = list; cur; cur = cur->next) {
11607 MsgInfo *msginfo = (MsgInfo *)cur->data;
11608 gchar *file = NULL;
11610 file = procmsg_get_message_file_full(msginfo,
11613 compose_attach_append(compose, (const gchar *)file,
11614 (const gchar *)file, "message/rfc822", NULL);
11618 g_slist_free(list);
11622 static gboolean compose_drag_drop(GtkWidget *widget,
11623 GdkDragContext *drag_context,
11625 guint time, gpointer user_data)
11627 /* not handling this signal makes compose_insert_drag_received_cb
11632 static gboolean completion_set_focus_to_subject
11633 (GtkWidget *widget,
11634 GdkEventKey *event,
11637 cm_return_val_if_fail(compose != NULL, FALSE);
11639 /* make backtab move to subject field */
11640 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11641 gtk_widget_grab_focus(compose->subject_entry);
11647 static void compose_insert_drag_received_cb (GtkWidget *widget,
11648 GdkDragContext *drag_context,
11651 GtkSelectionData *data,
11654 gpointer user_data)
11656 Compose *compose = (Compose *)user_data;
11662 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11664 type = gtk_selection_data_get_data_type(data);
11665 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11666 AlertValue val = G_ALERTDEFAULT;
11667 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11669 list = uri_list_extract_filenames(ddata);
11670 num_files = g_list_length(list);
11671 if (list == NULL && strstr(ddata, "://")) {
11672 /* Assume a list of no files, and data has ://, is a remote link */
11673 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11674 gchar *tmpfile = get_tmp_file();
11675 str_write_to_file(tmpdata, tmpfile);
11677 compose_insert_file(compose, tmpfile);
11678 claws_unlink(tmpfile);
11680 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11681 compose_beautify_paragraph(compose, NULL, TRUE);
11684 switch (prefs_common.compose_dnd_mode) {
11685 case COMPOSE_DND_ASK:
11686 msg = g_strdup_printf(
11688 "Do you want to insert the contents of the file "
11689 "into the message body, or attach it to the email?",
11690 "Do you want to insert the contents of the %d files "
11691 "into the message body, or attach them to the email?",
11694 val = alertpanel_full(_("Insert or attach?"), msg,
11695 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11697 TRUE, NULL, ALERT_QUESTION);
11700 case COMPOSE_DND_INSERT:
11701 val = G_ALERTALTERNATE;
11703 case COMPOSE_DND_ATTACH:
11704 val = G_ALERTOTHER;
11707 /* unexpected case */
11708 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11711 if (val & G_ALERTDISABLE) {
11712 val &= ~G_ALERTDISABLE;
11713 /* remember what action to perform by default, only if we don't click Cancel */
11714 if (val == G_ALERTALTERNATE)
11715 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11716 else if (val == G_ALERTOTHER)
11717 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11720 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11721 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11722 list_free_strings_full(list);
11724 } else if (val == G_ALERTOTHER) {
11725 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11726 list_free_strings_full(list);
11730 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11731 compose_insert_file(compose, (const gchar *)tmp->data);
11733 list_free_strings_full(list);
11734 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11739 static void compose_header_drag_received_cb (GtkWidget *widget,
11740 GdkDragContext *drag_context,
11743 GtkSelectionData *data,
11746 gpointer user_data)
11748 GtkEditable *entry = (GtkEditable *)user_data;
11749 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11751 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11754 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11755 gchar *decoded=g_new(gchar, strlen(email));
11758 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11759 gtk_editable_delete_text(entry, 0, -1);
11760 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11761 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11765 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11768 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11770 Compose *compose = (Compose *)data;
11772 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11773 compose->return_receipt = TRUE;
11775 compose->return_receipt = FALSE;
11778 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11780 Compose *compose = (Compose *)data;
11782 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11783 compose->remove_references = TRUE;
11785 compose->remove_references = FALSE;
11788 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11789 ComposeHeaderEntry *headerentry)
11791 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11792 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11793 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11797 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11798 GdkEventKey *event,
11799 ComposeHeaderEntry *headerentry)
11801 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11802 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11803 !(event->state & GDK_MODIFIER_MASK) &&
11804 (event->keyval == GDK_KEY_BackSpace) &&
11805 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11806 gtk_container_remove
11807 (GTK_CONTAINER(headerentry->compose->header_table),
11808 headerentry->combo);
11809 gtk_container_remove
11810 (GTK_CONTAINER(headerentry->compose->header_table),
11811 headerentry->entry);
11812 headerentry->compose->header_list =
11813 g_slist_remove(headerentry->compose->header_list,
11815 g_free(headerentry);
11816 } else if (event->keyval == GDK_KEY_Tab) {
11817 if (headerentry->compose->header_last == headerentry) {
11818 /* Override default next focus, and give it to subject_entry
11819 * instead of notebook tabs
11821 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11822 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11829 static gboolean scroll_postpone(gpointer data)
11831 Compose *compose = (Compose *)data;
11833 if (compose->batch)
11836 GTK_EVENTS_FLUSH();
11837 compose_show_first_last_header(compose, FALSE);
11841 static void compose_headerentry_changed_cb(GtkWidget *entry,
11842 ComposeHeaderEntry *headerentry)
11844 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11845 compose_create_header_entry(headerentry->compose);
11846 g_signal_handlers_disconnect_matched
11847 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11848 0, 0, NULL, NULL, headerentry);
11850 if (!headerentry->compose->batch)
11851 g_timeout_add(0, scroll_postpone, headerentry->compose);
11855 static gboolean compose_defer_auto_save_draft(Compose *compose)
11857 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11858 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11862 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11864 GtkAdjustment *vadj;
11866 cm_return_if_fail(compose);
11871 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11872 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11873 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11874 gtk_widget_get_parent(compose->header_table)));
11875 gtk_adjustment_set_value(vadj, (show_first ?
11876 gtk_adjustment_get_lower(vadj) :
11877 (gtk_adjustment_get_upper(vadj) -
11878 gtk_adjustment_get_page_size(vadj))));
11879 gtk_adjustment_changed(vadj);
11882 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11883 const gchar *text, gint len, Compose *compose)
11885 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11886 (G_OBJECT(compose->text), "paste_as_quotation"));
11889 cm_return_if_fail(text != NULL);
11891 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11892 G_CALLBACK(text_inserted),
11894 if (paste_as_quotation) {
11896 const gchar *qmark;
11898 GtkTextIter start_iter;
11901 len = strlen(text);
11903 new_text = g_strndup(text, len);
11905 qmark = compose_quote_char_from_context(compose);
11907 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11908 gtk_text_buffer_place_cursor(buffer, iter);
11910 pos = gtk_text_iter_get_offset(iter);
11912 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11913 _("Quote format error at line %d."));
11914 quote_fmt_reset_vartable();
11916 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11917 GINT_TO_POINTER(paste_as_quotation - 1));
11919 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11920 gtk_text_buffer_place_cursor(buffer, iter);
11921 gtk_text_buffer_delete_mark(buffer, mark);
11923 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11924 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11925 compose_beautify_paragraph(compose, &start_iter, FALSE);
11926 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11927 gtk_text_buffer_delete_mark(buffer, mark);
11929 if (strcmp(text, "\n") || compose->automatic_break
11930 || gtk_text_iter_starts_line(iter)) {
11931 GtkTextIter before_ins;
11932 gtk_text_buffer_insert(buffer, iter, text, len);
11933 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11934 before_ins = *iter;
11935 gtk_text_iter_backward_chars(&before_ins, len);
11936 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11939 /* check if the preceding is just whitespace or quote */
11940 GtkTextIter start_line;
11941 gchar *tmp = NULL, *quote = NULL;
11942 gint quote_len = 0, is_normal = 0;
11943 start_line = *iter;
11944 gtk_text_iter_set_line_offset(&start_line, 0);
11945 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11948 if (*tmp == '\0') {
11951 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11959 gtk_text_buffer_insert(buffer, iter, text, len);
11961 gtk_text_buffer_insert_with_tags_by_name(buffer,
11962 iter, text, len, "no_join", NULL);
11967 if (!paste_as_quotation) {
11968 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11969 compose_beautify_paragraph(compose, iter, FALSE);
11970 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11971 gtk_text_buffer_delete_mark(buffer, mark);
11974 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11975 G_CALLBACK(text_inserted),
11977 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11979 if (compose_can_autosave(compose) &&
11980 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11981 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11982 compose->draft_timeout_tag = g_timeout_add
11983 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11987 static void compose_check_all(GtkAction *action, gpointer data)
11989 Compose *compose = (Compose *)data;
11990 if (!compose->gtkaspell)
11993 if (gtk_widget_has_focus(compose->subject_entry))
11994 claws_spell_entry_check_all(
11995 CLAWS_SPELL_ENTRY(compose->subject_entry));
11997 gtkaspell_check_all(compose->gtkaspell);
12000 static void compose_highlight_all(GtkAction *action, gpointer data)
12002 Compose *compose = (Compose *)data;
12003 if (compose->gtkaspell) {
12004 claws_spell_entry_recheck_all(
12005 CLAWS_SPELL_ENTRY(compose->subject_entry));
12006 gtkaspell_highlight_all(compose->gtkaspell);
12010 static void compose_check_backwards(GtkAction *action, gpointer data)
12012 Compose *compose = (Compose *)data;
12013 if (!compose->gtkaspell) {
12014 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12018 if (gtk_widget_has_focus(compose->subject_entry))
12019 claws_spell_entry_check_backwards(
12020 CLAWS_SPELL_ENTRY(compose->subject_entry));
12022 gtkaspell_check_backwards(compose->gtkaspell);
12025 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12027 Compose *compose = (Compose *)data;
12028 if (!compose->gtkaspell) {
12029 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12033 if (gtk_widget_has_focus(compose->subject_entry))
12034 claws_spell_entry_check_forwards_go(
12035 CLAWS_SPELL_ENTRY(compose->subject_entry));
12037 gtkaspell_check_forwards_go(compose->gtkaspell);
12042 *\brief Guess originating forward account from MsgInfo and several
12043 * "common preference" settings. Return NULL if no guess.
12045 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12047 PrefsAccount *account = NULL;
12049 cm_return_val_if_fail(msginfo, NULL);
12050 cm_return_val_if_fail(msginfo->folder, NULL);
12051 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12053 if (msginfo->folder->prefs->enable_default_account)
12054 account = account_find_from_id(msginfo->folder->prefs->default_account);
12056 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12058 Xstrdup_a(to, msginfo->to, return NULL);
12059 extract_address(to);
12060 account = account_find_from_address(to, FALSE);
12063 if (!account && prefs_common.forward_account_autosel) {
12065 if (!procheader_get_header_from_msginfo
12066 (msginfo, &cc, "Cc:")) {
12067 gchar *buf = cc + strlen("Cc:");
12068 extract_address(buf);
12069 account = account_find_from_address(buf, FALSE);
12074 if (!account && prefs_common.forward_account_autosel) {
12075 gchar *deliveredto = NULL;
12076 if (!procheader_get_header_from_msginfo
12077 (msginfo, &deliveredto, "Delivered-To:")) {
12078 gchar *buf = deliveredto + strlen("Delivered-To:");
12079 extract_address(buf);
12080 account = account_find_from_address(buf, FALSE);
12081 g_free(deliveredto);
12086 account = msginfo->folder->folder->account;
12091 gboolean compose_close(Compose *compose)
12095 cm_return_val_if_fail(compose, FALSE);
12097 if (!g_mutex_trylock(compose->mutex)) {
12098 /* we have to wait for the (possibly deferred by auto-save)
12099 * drafting to be done, before destroying the compose under
12101 debug_print("waiting for drafting to finish...\n");
12102 compose_allow_user_actions(compose, FALSE);
12103 if (compose->close_timeout_tag == 0) {
12104 compose->close_timeout_tag =
12105 g_timeout_add (500, (GSourceFunc) compose_close,
12111 if (compose->draft_timeout_tag >= 0) {
12112 g_source_remove(compose->draft_timeout_tag);
12113 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12116 gtkut_widget_get_uposition(compose->window, &x, &y);
12117 if (!compose->batch) {
12118 prefs_common.compose_x = x;
12119 prefs_common.compose_y = y;
12121 g_mutex_unlock(compose->mutex);
12122 compose_destroy(compose);
12127 * Add entry field for each address in list.
12128 * \param compose E-Mail composition object.
12129 * \param listAddress List of (formatted) E-Mail addresses.
12131 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12134 node = listAddress;
12136 addr = ( gchar * ) node->data;
12137 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12138 node = g_list_next( node );
12142 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12143 guint action, gboolean opening_multiple)
12145 gchar *body = NULL;
12146 GSList *new_msglist = NULL;
12147 MsgInfo *tmp_msginfo = NULL;
12148 gboolean originally_enc = FALSE;
12149 gboolean originally_sig = FALSE;
12150 Compose *compose = NULL;
12151 gchar *s_system = NULL;
12153 cm_return_if_fail(msgview != NULL);
12155 cm_return_if_fail(msginfo_list != NULL);
12157 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
12158 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12159 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12161 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12162 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12163 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12164 orig_msginfo, mimeinfo);
12165 if (tmp_msginfo != NULL) {
12166 new_msglist = g_slist_append(NULL, tmp_msginfo);
12168 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12169 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12170 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12172 tmp_msginfo->folder = orig_msginfo->folder;
12173 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12174 if (orig_msginfo->tags) {
12175 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12176 tmp_msginfo->folder->tags_dirty = TRUE;
12182 if (!opening_multiple)
12183 body = messageview_get_selection(msgview);
12186 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12187 procmsg_msginfo_free(&tmp_msginfo);
12188 g_slist_free(new_msglist);
12190 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12192 if (compose && originally_enc) {
12193 compose_force_encryption(compose, compose->account, FALSE, s_system);
12196 if (compose && originally_sig && compose->account->default_sign_reply) {
12197 compose_force_signing(compose, compose->account, s_system);
12201 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12204 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12207 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12208 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12209 GSList *cur = msginfo_list;
12210 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12211 "messages. Opening the windows "
12212 "could take some time. Do you "
12213 "want to continue?"),
12214 g_slist_length(msginfo_list));
12215 if (g_slist_length(msginfo_list) > 9
12216 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12217 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12222 /* We'll open multiple compose windows */
12223 /* let the WM place the next windows */
12224 compose_force_window_origin = FALSE;
12225 for (; cur; cur = cur->next) {
12227 tmplist.data = cur->data;
12228 tmplist.next = NULL;
12229 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12231 compose_force_window_origin = TRUE;
12233 /* forwarding multiple mails as attachments is done via a
12234 * single compose window */
12235 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12239 void compose_check_for_email_account(Compose *compose)
12241 PrefsAccount *ac = NULL, *curr = NULL;
12247 if (compose->account && compose->account->protocol == A_NNTP) {
12248 ac = account_get_cur_account();
12249 if (ac->protocol == A_NNTP) {
12250 list = account_get_list();
12252 for( ; list != NULL ; list = g_list_next(list)) {
12253 curr = (PrefsAccount *) list->data;
12254 if (curr->protocol != A_NNTP) {
12260 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12265 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12266 const gchar *address)
12268 GSList *msginfo_list = NULL;
12269 gchar *body = messageview_get_selection(msgview);
12272 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12274 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12275 compose_check_for_email_account(compose);
12276 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12277 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12278 compose_reply_set_subject(compose, msginfo);
12281 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12284 void compose_set_position(Compose *compose, gint pos)
12286 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12288 gtkut_text_view_set_position(text, pos);
12291 gboolean compose_search_string(Compose *compose,
12292 const gchar *str, gboolean case_sens)
12294 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12296 return gtkut_text_view_search_string(text, str, case_sens);
12299 gboolean compose_search_string_backward(Compose *compose,
12300 const gchar *str, gboolean case_sens)
12302 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12304 return gtkut_text_view_search_string_backward(text, str, case_sens);
12307 /* allocate a msginfo structure and populate its data from a compose data structure */
12308 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12310 MsgInfo *newmsginfo;
12312 gchar date[RFC822_DATE_BUFFSIZE];
12314 cm_return_val_if_fail( compose != NULL, NULL );
12316 newmsginfo = procmsg_msginfo_new();
12319 get_rfc822_date(date, sizeof(date));
12320 newmsginfo->date = g_strdup(date);
12323 if (compose->from_name) {
12324 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12325 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12329 if (compose->subject_entry)
12330 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12332 /* to, cc, reply-to, newsgroups */
12333 for (list = compose->header_list; list; list = list->next) {
12334 gchar *header = gtk_editable_get_chars(
12336 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12337 gchar *entry = gtk_editable_get_chars(
12338 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12340 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12341 if ( newmsginfo->to == NULL ) {
12342 newmsginfo->to = g_strdup(entry);
12343 } else if (entry && *entry) {
12344 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12345 g_free(newmsginfo->to);
12346 newmsginfo->to = tmp;
12349 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12350 if ( newmsginfo->cc == NULL ) {
12351 newmsginfo->cc = g_strdup(entry);
12352 } else if (entry && *entry) {
12353 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12354 g_free(newmsginfo->cc);
12355 newmsginfo->cc = tmp;
12358 if ( strcasecmp(header,
12359 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12360 if ( newmsginfo->newsgroups == NULL ) {
12361 newmsginfo->newsgroups = g_strdup(entry);
12362 } else if (entry && *entry) {
12363 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12364 g_free(newmsginfo->newsgroups);
12365 newmsginfo->newsgroups = tmp;
12373 /* other data is unset */
12379 /* update compose's dictionaries from folder dict settings */
12380 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12381 FolderItem *folder_item)
12383 cm_return_if_fail(compose != NULL);
12385 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12386 FolderItemPrefs *prefs = folder_item->prefs;
12388 if (prefs->enable_default_dictionary)
12389 gtkaspell_change_dict(compose->gtkaspell,
12390 prefs->default_dictionary, FALSE);
12391 if (folder_item->prefs->enable_default_alt_dictionary)
12392 gtkaspell_change_alt_dict(compose->gtkaspell,
12393 prefs->default_alt_dictionary);
12394 if (prefs->enable_default_dictionary
12395 || prefs->enable_default_alt_dictionary)
12396 compose_spell_menu_changed(compose);
12401 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12403 Compose *compose = (Compose *)data;
12405 cm_return_if_fail(compose != NULL);
12407 gtk_widget_grab_focus(compose->text);
12410 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12412 gtk_combo_box_popup(GTK_COMBO_BOX(data));