2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2021 the Claws Mail team and Hiroyuki Yamamoto
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>
45 # include <sys/wait.h>
49 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
56 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
63 #include "mainwindow.h"
65 #ifndef USE_ALT_ADDRBOOK
66 #include "addressbook.h"
68 #include "addressbook-dbus.h"
69 #include "addressadd.h"
71 #include "folderview.h"
74 #include "stock_pixmap.h"
75 #include "send_message.h"
78 #include "customheader.h"
79 #include "prefs_common.h"
80 #include "prefs_account.h"
84 #include "procheader.h"
86 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
112 #include "file-utils.h"
115 #include "password.h"
116 #include "ldapserver.h"
130 #define N_ATTACH_COLS (N_COL_COLUMNS)
134 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
135 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
136 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
143 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
144 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
148 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
149 } ComposeCallAdvancedAction;
153 PRIORITY_HIGHEST = 1,
162 COMPOSE_INSERT_SUCCESS,
163 COMPOSE_INSERT_READ_ERROR,
164 COMPOSE_INSERT_INVALID_CHARACTER,
165 COMPOSE_INSERT_NO_FILE
166 } ComposeInsertResult;
170 COMPOSE_WRITE_FOR_SEND,
171 COMPOSE_WRITE_FOR_STORE
176 COMPOSE_QUOTE_FORCED,
183 SUBJECT_FIELD_PRESENT,
188 #define B64_LINE_SIZE 57
189 #define B64_BUFFSIZE 77
191 #define MAX_REFERENCES_LEN 999
193 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
194 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
196 #define COMPOSE_PRIVACY_WARNING() { \
197 alertpanel_error(_("You have opted to sign and/or encrypt this " \
198 "message but have not selected a privacy system.\n\n" \
199 "Signing and encrypting have been disabled for this " \
204 #define INVALID_PID INVALID_HANDLE_VALUE
206 #define INVALID_PID -1
209 static GdkColor default_header_bgcolor = {
216 static GdkColor default_header_color = {
223 static GList *compose_list = NULL;
224 static GSList *extra_headers = NULL;
226 static Compose *compose_generic_new (PrefsAccount *account,
230 GList *listAddress );
232 static Compose *compose_create (PrefsAccount *account,
237 static void compose_entry_indicate (Compose *compose,
238 const gchar *address);
239 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
240 ComposeQuoteMode quote_mode,
244 static Compose *compose_forward_multiple (PrefsAccount *account,
245 GSList *msginfo_list);
246 static Compose *compose_reply (MsgInfo *msginfo,
247 ComposeQuoteMode quote_mode,
252 static Compose *compose_reply_mode (ComposeMode mode,
253 GSList *msginfo_list,
255 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
256 static void compose_update_privacy_systems_menu(Compose *compose);
258 static GtkWidget *compose_account_option_menu_create
260 static void compose_set_out_encoding (Compose *compose);
261 static void compose_set_template_menu (Compose *compose);
262 static void compose_destroy (Compose *compose);
264 static MailField compose_entries_set (Compose *compose,
266 ComposeEntryType to_type);
267 static gint compose_parse_header (Compose *compose,
269 static gint compose_parse_manual_headers (Compose *compose,
271 HeaderEntry *entries);
272 static gchar *compose_parse_references (const gchar *ref,
275 static gchar *compose_quote_fmt (Compose *compose,
281 gboolean need_unescape,
282 const gchar *err_msg);
284 static void compose_reply_set_entry (Compose *compose,
290 followup_and_reply_to);
291 static void compose_reedit_set_entry (Compose *compose,
294 static void compose_insert_sig (Compose *compose,
296 static ComposeInsertResult compose_insert_file (Compose *compose,
299 static gboolean compose_attach_append (Compose *compose,
302 const gchar *content_type,
303 const gchar *charset);
304 static void compose_attach_parts (Compose *compose,
307 static gboolean compose_beautify_paragraph (Compose *compose,
308 GtkTextIter *par_iter,
310 static void compose_wrap_all (Compose *compose);
311 static void compose_wrap_all_full (Compose *compose,
314 static void compose_set_title (Compose *compose);
315 static void compose_select_account (Compose *compose,
316 PrefsAccount *account,
319 static PrefsAccount *compose_current_mail_account(void);
320 /* static gint compose_send (Compose *compose); */
321 static gboolean compose_check_for_valid_recipient
323 static gboolean compose_check_entries (Compose *compose,
324 gboolean check_everything);
325 static gint compose_write_to_file (Compose *compose,
328 gboolean attach_parts);
329 static gint compose_write_body_to_file (Compose *compose,
331 static gint compose_remove_reedit_target (Compose *compose,
333 static void compose_remove_draft (Compose *compose);
334 static ComposeQueueResult compose_queue_sub (Compose *compose,
338 gboolean perform_checks,
339 gboolean remove_reedit_target);
340 static int compose_add_attachments (Compose *compose,
342 static gchar *compose_get_header (Compose *compose);
343 static gchar *compose_get_manual_headers_info (Compose *compose);
345 static void compose_convert_header (Compose *compose,
350 gboolean addr_field);
352 static void compose_attach_info_free (AttachInfo *ainfo);
353 static void compose_attach_remove_selected (GtkAction *action,
356 static void compose_template_apply (Compose *compose,
359 static void compose_attach_property (GtkAction *action,
361 static void compose_attach_property_create (gboolean *cancelled);
362 static void attach_property_ok (GtkWidget *widget,
363 gboolean *cancelled);
364 static void attach_property_cancel (GtkWidget *widget,
365 gboolean *cancelled);
366 static gint attach_property_delete_event (GtkWidget *widget,
368 gboolean *cancelled);
369 static gboolean attach_property_key_pressed (GtkWidget *widget,
371 gboolean *cancelled);
373 static void compose_exec_ext_editor (Compose *compose);
374 static gboolean compose_ext_editor_kill (Compose *compose);
375 static void compose_ext_editor_closed_cb (GPid pid,
378 static void compose_set_ext_editor_sensitive (Compose *compose,
380 static gboolean compose_get_ext_editor_cmd_valid();
381 static gboolean compose_get_ext_editor_uses_socket();
383 static gboolean compose_ext_editor_plug_removed_cb
386 #endif /* G_OS_WIN32 */
388 static void compose_undo_state_changed (UndoMain *undostruct,
393 static void compose_create_header_entry (Compose *compose);
394 static void compose_add_header_entry (Compose *compose, const gchar *header,
395 gchar *text, ComposePrefType pref_type);
396 static void compose_remove_header_entries(Compose *compose);
398 static void compose_update_priority_menu_item(Compose * compose);
400 static void compose_spell_menu_changed (void *data);
401 static void compose_dict_changed (void *data);
403 static void compose_add_field_list ( Compose *compose,
404 GList *listAddress );
406 /* callback functions */
408 static void compose_notebook_size_alloc (GtkNotebook *notebook,
409 GtkAllocation *allocation,
411 static gboolean compose_edit_size_alloc (GtkEditable *widget,
412 GtkAllocation *allocation,
413 GtkSHRuler *shruler);
414 static void account_activated (GtkComboBox *optmenu,
416 static void attach_selected (GtkTreeView *tree_view,
417 GtkTreePath *tree_path,
418 GtkTreeViewColumn *column,
420 static gboolean attach_button_pressed (GtkWidget *widget,
421 GdkEventButton *event,
423 static gboolean attach_key_pressed (GtkWidget *widget,
426 static void compose_send_cb (GtkAction *action, gpointer data);
427 static void compose_send_later_cb (GtkAction *action, gpointer data);
429 static void compose_save_cb (GtkAction *action,
432 static void compose_attach_cb (GtkAction *action,
434 static void compose_insert_file_cb (GtkAction *action,
436 static void compose_insert_sig_cb (GtkAction *action,
438 static void compose_replace_sig_cb (GtkAction *action,
441 static void compose_close_cb (GtkAction *action,
443 static void compose_print_cb (GtkAction *action,
446 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
448 static void compose_address_cb (GtkAction *action,
450 static void about_show_cb (GtkAction *action,
452 static void compose_template_activate_cb(GtkWidget *widget,
455 static void compose_ext_editor_cb (GtkAction *action,
458 static gint compose_delete_cb (GtkWidget *widget,
462 static void compose_undo_cb (GtkAction *action,
464 static void compose_redo_cb (GtkAction *action,
466 static void compose_cut_cb (GtkAction *action,
468 static void compose_copy_cb (GtkAction *action,
470 static void compose_paste_cb (GtkAction *action,
472 static void compose_paste_as_quote_cb (GtkAction *action,
474 static void compose_paste_no_wrap_cb (GtkAction *action,
476 static void compose_paste_wrap_cb (GtkAction *action,
478 static void compose_allsel_cb (GtkAction *action,
481 static void compose_advanced_action_cb (GtkAction *action,
484 static void compose_grab_focus_cb (GtkWidget *widget,
487 static void compose_changed_cb (GtkTextBuffer *textbuf,
490 static void compose_wrap_cb (GtkAction *action,
492 static void compose_wrap_all_cb (GtkAction *action,
494 static void compose_find_cb (GtkAction *action,
496 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
498 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
501 static void compose_toggle_ruler_cb (GtkToggleAction *action,
503 static void compose_toggle_sign_cb (GtkToggleAction *action,
505 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
507 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
508 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
509 static void compose_activate_privacy_system (Compose *compose,
510 PrefsAccount *account,
512 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
513 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
515 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
517 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
518 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
519 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
521 static void compose_attach_drag_received_cb (GtkWidget *widget,
522 GdkDragContext *drag_context,
525 GtkSelectionData *data,
529 static void compose_insert_drag_received_cb (GtkWidget *widget,
530 GdkDragContext *drag_context,
533 GtkSelectionData *data,
537 static void compose_header_drag_received_cb (GtkWidget *widget,
538 GdkDragContext *drag_context,
541 GtkSelectionData *data,
546 static gboolean compose_drag_drop (GtkWidget *widget,
547 GdkDragContext *drag_context,
549 guint time, gpointer user_data);
550 static gboolean completion_set_focus_to_subject
555 static void text_inserted (GtkTextBuffer *buffer,
560 static Compose *compose_generic_reply(MsgInfo *msginfo,
561 ComposeQuoteMode quote_mode,
565 gboolean followup_and_reply_to,
568 static void compose_headerentry_changed_cb (GtkWidget *entry,
569 ComposeHeaderEntry *headerentry);
570 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
572 ComposeHeaderEntry *headerentry);
573 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
574 ComposeHeaderEntry *headerentry);
576 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
578 static void compose_allow_user_actions (Compose *compose, gboolean allow);
580 static void compose_nothing_cb (GtkAction *action, gpointer data)
586 static void compose_check_all (GtkAction *action, gpointer data);
587 static void compose_highlight_all (GtkAction *action, gpointer data);
588 static void compose_check_backwards (GtkAction *action, gpointer data);
589 static void compose_check_forwards_go (GtkAction *action, gpointer data);
592 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
594 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
597 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
598 FolderItem *folder_item);
600 static void compose_attach_update_label(Compose *compose);
601 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
602 gboolean respect_default_to);
603 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
604 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
606 static GtkActionEntry compose_popup_entries[] =
608 {"Compose", NULL, "Compose", NULL, NULL, NULL },
609 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
610 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
611 {"Compose/---", NULL, "---", NULL, NULL, NULL },
612 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
615 static GtkActionEntry compose_entries[] =
617 {"Menu", NULL, "Menu", NULL, NULL, NULL },
619 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
620 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
622 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
624 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
625 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
626 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
628 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
629 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
630 {"Message/---", NULL, "---", NULL, NULL, NULL },
632 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
633 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
634 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
635 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
636 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
637 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
638 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
639 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
640 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
641 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
644 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
645 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
646 {"Edit/---", NULL, "---", NULL, NULL, NULL },
648 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
649 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
650 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
652 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
653 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
654 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
655 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
657 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
659 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
660 {"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*/
661 {"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*/
662 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
663 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
664 {"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*/
665 {"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*/
666 {"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*/
667 {"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*/
668 {"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*/
669 {"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*/
670 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
671 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
672 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
673 {"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*/
675 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
676 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
678 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
679 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
680 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
681 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
682 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
685 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
686 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
687 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
688 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
690 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
691 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
695 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
696 {"Options/---", NULL, "---", NULL, NULL, NULL },
697 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
698 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
700 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
701 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
703 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
704 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
705 #define ENC_ACTION(cs_char,c_char,string) \
706 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
708 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
709 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
710 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
711 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
712 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
713 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
714 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
715 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
716 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
719 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
721 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
722 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
723 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
724 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
727 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
730 static GtkToggleActionEntry compose_toggle_entries[] =
732 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
733 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
734 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
735 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
736 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
737 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
738 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
741 static GtkRadioActionEntry compose_radio_rm_entries[] =
743 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
744 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
745 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
746 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
749 static GtkRadioActionEntry compose_radio_prio_entries[] =
751 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
752 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
753 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
754 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
755 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
758 static GtkRadioActionEntry compose_radio_enc_entries[] =
760 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
787 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
788 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
789 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
790 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
791 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
792 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
795 static GtkTargetEntry compose_mime_types[] =
797 {"text/uri-list", 0, 0},
798 {"UTF8_STRING", 0, 0},
802 static gboolean compose_put_existing_to_front(MsgInfo *info)
804 const GList *compose_list = compose_get_compose_list();
805 const GList *elem = NULL;
808 for (elem = compose_list; elem != NULL && elem->data != NULL;
810 Compose *c = (Compose*)elem->data;
812 if (!c->targetinfo || !c->targetinfo->msgid ||
816 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
817 gtkut_window_popup(c->window);
825 static GdkColor quote_color1 =
826 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
827 static GdkColor quote_color2 =
828 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
829 static GdkColor quote_color3 =
830 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
832 static GdkColor quote_bgcolor1 =
833 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
834 static GdkColor quote_bgcolor2 =
835 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
836 static GdkColor quote_bgcolor3 =
837 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
839 static GdkColor signature_color = {
846 static GdkColor uri_color = {
853 static void compose_create_tags(GtkTextView *text, Compose *compose)
855 GtkTextBuffer *buffer;
856 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
858 buffer = gtk_text_view_get_buffer(text);
860 if (prefs_common.enable_color) {
861 /* grab the quote colors, converting from an int to a GdkColor */
862 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1],
864 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2],
866 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3],
868 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL1_BG],
870 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL2_BG],
872 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QUOTE_LEVEL3_BG],
874 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_SIGNATURE],
876 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_URI],
879 signature_color = quote_color1 = quote_color2 = quote_color3 =
880 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
883 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
884 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
885 "foreground-gdk", "e_color1,
886 "paragraph-background-gdk", "e_bgcolor1,
888 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
889 "foreground-gdk", "e_color2,
890 "paragraph-background-gdk", "e_bgcolor2,
892 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
893 "foreground-gdk", "e_color3,
894 "paragraph-background-gdk", "e_bgcolor3,
897 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
898 "foreground-gdk", "e_color1,
900 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
901 "foreground-gdk", "e_color2,
903 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
904 "foreground-gdk", "e_color3,
908 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
909 "foreground-gdk", &signature_color,
912 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
913 "foreground-gdk", &uri_color,
915 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
916 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
919 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
922 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
925 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
927 return compose_generic_new(account, mailto, item, NULL, NULL);
930 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
932 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
935 #define SCROLL_TO_CURSOR(compose) { \
936 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
937 gtk_text_view_get_buffer( \
938 GTK_TEXT_VIEW(compose->text))); \
939 gtk_text_view_scroll_mark_onscreen( \
940 GTK_TEXT_VIEW(compose->text), \
944 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
947 if (folderidentifier) {
948 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
949 prefs_common.compose_save_to_history = add_history(
950 prefs_common.compose_save_to_history, folderidentifier);
951 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
952 prefs_common.compose_save_to_history);
955 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
956 if (folderidentifier)
957 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
959 gtk_entry_set_text(GTK_ENTRY(entry), "");
962 static gchar *compose_get_save_to(Compose *compose)
965 gchar *result = NULL;
966 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
967 result = gtk_editable_get_chars(entry, 0, -1);
970 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
971 prefs_common.compose_save_to_history = add_history(
972 prefs_common.compose_save_to_history, result);
973 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
974 prefs_common.compose_save_to_history);
979 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
980 GList *attach_files, GList *listAddress )
983 GtkTextView *textview;
984 GtkTextBuffer *textbuf;
986 const gchar *subject_format = NULL;
987 const gchar *body_format = NULL;
988 gchar *mailto_from = NULL;
989 PrefsAccount *mailto_account = NULL;
990 MsgInfo* dummyinfo = NULL;
991 gint cursor_pos = -1;
992 MailField mfield = NO_FIELD_PRESENT;
996 /* check if mailto defines a from */
997 if (mailto && *mailto != '\0') {
998 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
999 /* mailto defines a from, check if we can get account prefs from it,
1000 if not, the account prefs will be guessed using other ways, but we'll keep
1003 mailto_account = account_find_from_address(mailto_from, TRUE);
1004 if (mailto_account == NULL) {
1006 Xstrdup_a(tmp_from, mailto_from, return NULL);
1007 extract_address(tmp_from);
1008 mailto_account = account_find_from_address(tmp_from, TRUE);
1012 account = mailto_account;
1015 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1016 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1017 account = account_find_from_id(item->prefs->default_account);
1019 /* if no account prefs set, fallback to the current one */
1020 if (!account) account = cur_account;
1021 cm_return_val_if_fail(account != NULL, NULL);
1023 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1024 compose_apply_folder_privacy_settings(compose, item);
1026 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1027 (account->default_encrypt || account->default_sign))
1028 COMPOSE_PRIVACY_WARNING();
1030 /* override from name if mailto asked for it */
1032 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1033 g_free(mailto_from);
1035 /* override from name according to folder properties */
1036 if (item && item->prefs &&
1037 item->prefs->compose_with_format &&
1038 item->prefs->compose_override_from_format &&
1039 *item->prefs->compose_override_from_format != '\0') {
1044 dummyinfo = compose_msginfo_new_from_compose(compose);
1046 /* decode \-escape sequences in the internal representation of the quote format */
1047 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1048 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1051 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1052 compose->gtkaspell);
1054 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1056 quote_fmt_scan_string(tmp);
1059 buf = quote_fmt_get_buffer();
1061 alertpanel_error(_("New message From format error."));
1063 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1064 quote_fmt_reset_vartable();
1065 quote_fmtlex_destroy();
1070 compose->replyinfo = NULL;
1071 compose->fwdinfo = NULL;
1073 textview = GTK_TEXT_VIEW(compose->text);
1074 textbuf = gtk_text_view_get_buffer(textview);
1075 compose_create_tags(textview, compose);
1077 undo_block(compose->undostruct);
1079 compose_set_dictionaries_from_folder_prefs(compose, item);
1082 if (account->auto_sig)
1083 compose_insert_sig(compose, FALSE);
1084 gtk_text_buffer_get_start_iter(textbuf, &iter);
1085 gtk_text_buffer_place_cursor(textbuf, &iter);
1087 if (account->protocol != A_NNTP) {
1088 if (mailto && *mailto != '\0') {
1089 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1092 compose_set_folder_prefs(compose, item, TRUE);
1094 if (item && item->ret_rcpt) {
1095 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1098 if (mailto && *mailto != '\0') {
1099 if (!strchr(mailto, '@'))
1100 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1102 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1103 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1104 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1105 mfield = TO_FIELD_PRESENT;
1108 * CLAWS: just don't allow return receipt request, even if the user
1109 * may want to send an email. simple but foolproof.
1111 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1113 compose_add_field_list( compose, listAddress );
1115 if (item && item->prefs && item->prefs->compose_with_format) {
1116 subject_format = item->prefs->compose_subject_format;
1117 body_format = item->prefs->compose_body_format;
1118 } else if (account->compose_with_format) {
1119 subject_format = account->compose_subject_format;
1120 body_format = account->compose_body_format;
1121 } else if (prefs_common.compose_with_format) {
1122 subject_format = prefs_common.compose_subject_format;
1123 body_format = prefs_common.compose_body_format;
1126 if (subject_format || body_format) {
1129 && *subject_format != '\0' )
1131 gchar *subject = NULL;
1136 dummyinfo = compose_msginfo_new_from_compose(compose);
1138 /* decode \-escape sequences in the internal representation of the quote format */
1139 tmp = g_malloc(strlen(subject_format)+1);
1140 pref_get_unescaped_pref(tmp, subject_format);
1142 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1144 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1145 compose->gtkaspell);
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1149 quote_fmt_scan_string(tmp);
1152 buf = quote_fmt_get_buffer();
1154 alertpanel_error(_("New message subject format error."));
1156 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1157 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1158 quote_fmt_reset_vartable();
1159 quote_fmtlex_destroy();
1163 mfield = SUBJECT_FIELD_PRESENT;
1167 && *body_format != '\0' )
1170 GtkTextBuffer *buffer;
1171 GtkTextIter start, end;
1175 dummyinfo = compose_msginfo_new_from_compose(compose);
1177 text = GTK_TEXT_VIEW(compose->text);
1178 buffer = gtk_text_view_get_buffer(text);
1179 gtk_text_buffer_get_start_iter(buffer, &start);
1180 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1181 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1183 compose_quote_fmt(compose, dummyinfo,
1185 NULL, tmp, FALSE, TRUE,
1186 _("The body of the \"New message\" template has an error at line %d."));
1187 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1188 quote_fmt_reset_vartable();
1192 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1193 gtkaspell_highlight_all(compose->gtkaspell);
1195 mfield = BODY_FIELD_PRESENT;
1199 procmsg_msginfo_free( &dummyinfo );
1205 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1206 ainfo = (AttachInfo *) curr->data;
1208 compose_insert_file(compose, ainfo->file);
1210 compose_attach_append(compose, ainfo->file, ainfo->file,
1211 ainfo->content_type, ainfo->charset);
1215 compose_show_first_last_header(compose, TRUE);
1217 /* Set save folder */
1218 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1219 gchar *folderidentifier;
1221 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1222 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1223 folderidentifier = folder_item_get_identifier(item);
1224 compose_set_save_to(compose, folderidentifier);
1225 g_free(folderidentifier);
1228 /* Place cursor according to provided input (mfield) */
1230 case NO_FIELD_PRESENT:
1231 if (compose->header_last)
1232 gtk_widget_grab_focus(compose->header_last->entry);
1234 case TO_FIELD_PRESENT:
1235 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1237 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1240 gtk_widget_grab_focus(compose->subject_entry);
1242 case SUBJECT_FIELD_PRESENT:
1243 textview = GTK_TEXT_VIEW(compose->text);
1246 textbuf = gtk_text_view_get_buffer(textview);
1249 mark = gtk_text_buffer_get_insert(textbuf);
1250 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1251 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1253 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1254 * only defers where it comes to the variable body
1255 * is not null. If no body is present compose->text
1256 * will be null in which case you cannot place the
1257 * cursor inside the component so. An empty component
1258 * is therefore created before placing the cursor
1260 case BODY_FIELD_PRESENT:
1261 cursor_pos = quote_fmt_get_cursor_pos();
1262 if (cursor_pos == -1)
1263 gtk_widget_grab_focus(compose->header_last->entry);
1265 gtk_widget_grab_focus(compose->text);
1269 undo_unblock(compose->undostruct);
1271 if (prefs_common.auto_exteditor)
1272 compose_exec_ext_editor(compose);
1274 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1276 SCROLL_TO_CURSOR(compose);
1278 compose->modified = FALSE;
1279 compose_set_title(compose);
1281 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1286 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1287 gboolean override_pref, const gchar *system)
1289 const gchar *privacy = NULL;
1291 cm_return_if_fail(compose != NULL);
1292 cm_return_if_fail(account != NULL);
1294 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1295 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1298 if (account->default_privacy_system && strlen(account->default_privacy_system))
1299 privacy = account->default_privacy_system;
1303 GSList *privacy_avail = privacy_get_system_ids();
1304 if (privacy_avail && g_slist_length(privacy_avail)) {
1305 privacy = (gchar *)(privacy_avail->data);
1307 g_slist_free_full(privacy_avail, g_free);
1309 if (privacy != NULL) {
1311 g_free(compose->privacy_system);
1312 compose->privacy_system = NULL;
1313 g_free(compose->encdata);
1314 compose->encdata = NULL;
1316 if (compose->privacy_system == NULL)
1317 compose->privacy_system = g_strdup(privacy);
1318 else if (*(compose->privacy_system) == '\0') {
1319 g_free(compose->privacy_system);
1320 g_free(compose->encdata);
1321 compose->encdata = NULL;
1322 compose->privacy_system = g_strdup(privacy);
1324 compose_update_privacy_system_menu_item(compose, FALSE);
1325 compose_use_encryption(compose, TRUE);
1329 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1331 const gchar *privacy = NULL;
1332 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1335 if (account->default_privacy_system && strlen(account->default_privacy_system))
1336 privacy = account->default_privacy_system;
1340 GSList *privacy_avail = privacy_get_system_ids();
1341 if (privacy_avail && g_slist_length(privacy_avail)) {
1342 privacy = (gchar *)(privacy_avail->data);
1346 if (privacy != NULL) {
1348 g_free(compose->privacy_system);
1349 compose->privacy_system = NULL;
1350 g_free(compose->encdata);
1351 compose->encdata = NULL;
1353 if (compose->privacy_system == NULL)
1354 compose->privacy_system = g_strdup(privacy);
1355 compose_update_privacy_system_menu_item(compose, FALSE);
1356 compose_use_signing(compose, TRUE);
1360 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1364 Compose *compose = NULL;
1366 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1368 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1369 cm_return_val_if_fail(msginfo != NULL, NULL);
1371 list_len = g_slist_length(msginfo_list);
1375 case COMPOSE_REPLY_TO_ADDRESS:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, prefs_common.default_reply_list, FALSE, body);
1379 case COMPOSE_REPLY_WITH_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1381 FALSE, prefs_common.default_reply_list, FALSE, body);
1383 case COMPOSE_REPLY_WITHOUT_QUOTE:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1385 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1387 case COMPOSE_REPLY_TO_SENDER:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1389 FALSE, FALSE, TRUE, body);
1391 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1392 compose = compose_followup_and_reply_to(msginfo,
1393 COMPOSE_QUOTE_CHECK,
1394 FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 FALSE, FALSE, TRUE, body);
1400 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 FALSE, FALSE, TRUE, NULL);
1404 case COMPOSE_REPLY_TO_ALL:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 TRUE, FALSE, FALSE, body);
1408 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 TRUE, FALSE, FALSE, body);
1412 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 TRUE, FALSE, FALSE, NULL);
1416 case COMPOSE_REPLY_TO_LIST:
1417 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1418 FALSE, TRUE, FALSE, body);
1420 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1421 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1422 FALSE, TRUE, FALSE, body);
1424 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1425 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1426 FALSE, TRUE, FALSE, NULL);
1428 case COMPOSE_FORWARD:
1429 if (prefs_common.forward_as_attachment) {
1430 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1433 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1437 case COMPOSE_FORWARD_INLINE:
1438 /* check if we reply to more than one Message */
1439 if (list_len == 1) {
1440 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1443 /* more messages FALL THROUGH */
1444 case COMPOSE_FORWARD_AS_ATTACH:
1445 compose = compose_forward_multiple(NULL, msginfo_list);
1447 case COMPOSE_REDIRECT:
1448 compose = compose_redirect(NULL, msginfo, FALSE);
1451 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1454 if (compose == NULL) {
1455 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1459 compose->rmode = mode;
1460 switch (compose->rmode) {
1462 case COMPOSE_REPLY_WITH_QUOTE:
1463 case COMPOSE_REPLY_WITHOUT_QUOTE:
1464 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1465 debug_print("reply mode Normal\n");
1466 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1467 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1469 case COMPOSE_REPLY_TO_SENDER:
1470 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1472 debug_print("reply mode Sender\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1475 case COMPOSE_REPLY_TO_ALL:
1476 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1477 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1478 debug_print("reply mode All\n");
1479 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1481 case COMPOSE_REPLY_TO_LIST:
1482 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1483 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1484 debug_print("reply mode List\n");
1485 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1487 case COMPOSE_REPLY_TO_ADDRESS:
1488 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1496 static Compose *compose_reply(MsgInfo *msginfo,
1497 ComposeQuoteMode quote_mode,
1503 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1504 to_sender, FALSE, body);
1507 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1508 ComposeQuoteMode quote_mode,
1513 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1514 to_sender, TRUE, body);
1517 static void compose_extract_original_charset(Compose *compose)
1519 MsgInfo *info = NULL;
1520 if (compose->replyinfo) {
1521 info = compose->replyinfo;
1522 } else if (compose->fwdinfo) {
1523 info = compose->fwdinfo;
1524 } else if (compose->targetinfo) {
1525 info = compose->targetinfo;
1528 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1529 MimeInfo *partinfo = mimeinfo;
1530 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1531 partinfo = procmime_mimeinfo_next(partinfo);
1533 compose->orig_charset =
1534 g_strdup(procmime_mimeinfo_get_parameter(
1535 partinfo, "charset"));
1537 procmime_mimeinfo_free_all(&mimeinfo);
1541 #define SIGNAL_BLOCK(buffer) { \
1542 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(compose_changed_cb), \
1545 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1546 G_CALLBACK(text_inserted), \
1550 #define SIGNAL_UNBLOCK(buffer) { \
1551 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1552 G_CALLBACK(compose_changed_cb), \
1554 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1555 G_CALLBACK(text_inserted), \
1559 static Compose *compose_generic_reply(MsgInfo *msginfo,
1560 ComposeQuoteMode quote_mode,
1561 gboolean to_all, gboolean to_ml,
1563 gboolean followup_and_reply_to,
1567 PrefsAccount *account = NULL;
1568 GtkTextView *textview;
1569 GtkTextBuffer *textbuf;
1570 gboolean quote = FALSE;
1571 const gchar *qmark = NULL;
1572 const gchar *body_fmt = NULL;
1573 gchar *s_system = NULL;
1575 cm_return_val_if_fail(msginfo != NULL, NULL);
1576 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1578 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1580 cm_return_val_if_fail(account != NULL, NULL);
1582 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1583 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1585 compose->updating = TRUE;
1587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1590 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1591 if (!compose->replyinfo)
1592 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1594 compose_extract_original_charset(compose);
1596 if (msginfo->folder && msginfo->folder->ret_rcpt)
1597 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1599 /* Set save folder */
1600 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1601 gchar *folderidentifier;
1603 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1604 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1605 folderidentifier = folder_item_get_identifier(msginfo->folder);
1606 compose_set_save_to(compose, folderidentifier);
1607 g_free(folderidentifier);
1610 if (compose_parse_header(compose, msginfo) < 0) {
1611 compose->updating = FALSE;
1612 compose_destroy(compose);
1616 /* override from name according to folder properties */
1617 if (msginfo->folder && msginfo->folder->prefs &&
1618 msginfo->folder->prefs->reply_with_format &&
1619 msginfo->folder->prefs->reply_override_from_format &&
1620 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1625 /* decode \-escape sequences in the internal representation of the quote format */
1626 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1627 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1630 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1631 compose->gtkaspell);
1633 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1635 quote_fmt_scan_string(tmp);
1638 buf = quote_fmt_get_buffer();
1640 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1642 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1643 quote_fmt_reset_vartable();
1644 quote_fmtlex_destroy();
1649 textview = (GTK_TEXT_VIEW(compose->text));
1650 textbuf = gtk_text_view_get_buffer(textview);
1651 compose_create_tags(textview, compose);
1653 undo_block(compose->undostruct);
1655 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1656 gtkaspell_block_check(compose->gtkaspell);
1659 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1660 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1661 /* use the reply format of folder (if enabled), or the account's one
1662 (if enabled) or fallback to the global reply format, which is always
1663 enabled (even if empty), and use the relevant quotemark */
1665 if (msginfo->folder && msginfo->folder->prefs &&
1666 msginfo->folder->prefs->reply_with_format) {
1667 qmark = msginfo->folder->prefs->reply_quotemark;
1668 body_fmt = msginfo->folder->prefs->reply_body_format;
1670 } else if (account->reply_with_format) {
1671 qmark = account->reply_quotemark;
1672 body_fmt = account->reply_body_format;
1675 qmark = prefs_common.quotemark;
1676 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1677 body_fmt = gettext(prefs_common.quotefmt);
1684 /* empty quotemark is not allowed */
1685 if (qmark == NULL || *qmark == '\0')
1687 compose_quote_fmt(compose, compose->replyinfo,
1688 body_fmt, qmark, body, FALSE, TRUE,
1689 _("The body of the \"Reply\" template has an error at line %d."));
1690 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1691 quote_fmt_reset_vartable();
1694 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1695 compose_force_encryption(compose, account, FALSE, s_system);
1698 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1699 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1700 compose_force_signing(compose, account, s_system);
1704 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1705 ((account->default_encrypt || account->default_sign) ||
1706 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1707 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1708 COMPOSE_PRIVACY_WARNING();
1710 SIGNAL_BLOCK(textbuf);
1712 if (account->auto_sig)
1713 compose_insert_sig(compose, FALSE);
1715 compose_wrap_all(compose);
1718 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1719 gtkaspell_highlight_all(compose->gtkaspell);
1720 gtkaspell_unblock_check(compose->gtkaspell);
1722 SIGNAL_UNBLOCK(textbuf);
1724 gtk_widget_grab_focus(compose->text);
1726 undo_unblock(compose->undostruct);
1728 if (prefs_common.auto_exteditor)
1729 compose_exec_ext_editor(compose);
1731 compose->modified = FALSE;
1732 compose_set_title(compose);
1734 compose->updating = FALSE;
1735 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1736 SCROLL_TO_CURSOR(compose);
1738 if (compose->deferred_destroy) {
1739 compose_destroy(compose);
1747 #define INSERT_FW_HEADER(var, hdr) \
1748 if (msginfo->var && *msginfo->var) { \
1749 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1750 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1751 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1754 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1755 gboolean as_attach, const gchar *body,
1756 gboolean no_extedit,
1760 GtkTextView *textview;
1761 GtkTextBuffer *textbuf;
1762 gint cursor_pos = -1;
1765 cm_return_val_if_fail(msginfo != NULL, NULL);
1766 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1768 if (!account && !(account = compose_find_account(msginfo)))
1769 account = cur_account;
1771 if (!prefs_common.forward_as_attachment)
1772 mode = COMPOSE_FORWARD_INLINE;
1774 mode = COMPOSE_FORWARD;
1775 compose = compose_create(account, msginfo->folder, mode, batch);
1776 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1778 compose->updating = TRUE;
1779 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1780 if (!compose->fwdinfo)
1781 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1783 compose_extract_original_charset(compose);
1785 if (msginfo->subject && *msginfo->subject) {
1786 gchar *buf, *buf2, *p;
1788 buf = p = g_strdup(msginfo->subject);
1789 p += subject_get_prefix_length(p);
1790 memmove(buf, p, strlen(p) + 1);
1792 buf2 = g_strdup_printf("Fw: %s", buf);
1793 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1799 /* override from name according to folder properties */
1800 if (msginfo->folder && msginfo->folder->prefs &&
1801 msginfo->folder->prefs->forward_with_format &&
1802 msginfo->folder->prefs->forward_override_from_format &&
1803 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1807 MsgInfo *full_msginfo = NULL;
1810 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1812 full_msginfo = procmsg_msginfo_copy(msginfo);
1814 /* decode \-escape sequences in the internal representation of the quote format */
1815 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1816 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1819 gtkaspell_block_check(compose->gtkaspell);
1820 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1821 compose->gtkaspell);
1823 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1825 quote_fmt_scan_string(tmp);
1828 buf = quote_fmt_get_buffer();
1830 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1832 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1833 quote_fmt_reset_vartable();
1834 quote_fmtlex_destroy();
1837 procmsg_msginfo_free(&full_msginfo);
1840 textview = GTK_TEXT_VIEW(compose->text);
1841 textbuf = gtk_text_view_get_buffer(textview);
1842 compose_create_tags(textview, compose);
1844 undo_block(compose->undostruct);
1848 msgfile = procmsg_get_message_file(msginfo);
1849 if (!is_file_exist(msgfile))
1850 g_warning("%s: file does not exist", msgfile);
1852 compose_attach_append(compose, msgfile, msgfile,
1853 "message/rfc822", NULL);
1857 const gchar *qmark = NULL;
1858 const gchar *body_fmt = NULL;
1859 MsgInfo *full_msginfo;
1861 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1863 full_msginfo = procmsg_msginfo_copy(msginfo);
1865 /* use the forward format of folder (if enabled), or the account's one
1866 (if enabled) or fallback to the global forward format, which is always
1867 enabled (even if empty), and use the relevant quotemark */
1868 if (msginfo->folder && msginfo->folder->prefs &&
1869 msginfo->folder->prefs->forward_with_format) {
1870 qmark = msginfo->folder->prefs->forward_quotemark;
1871 body_fmt = msginfo->folder->prefs->forward_body_format;
1873 } else if (account->forward_with_format) {
1874 qmark = account->forward_quotemark;
1875 body_fmt = account->forward_body_format;
1878 qmark = prefs_common.fw_quotemark;
1879 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1880 body_fmt = gettext(prefs_common.fw_quotefmt);
1885 /* empty quotemark is not allowed */
1886 if (qmark == NULL || *qmark == '\0')
1889 compose_quote_fmt(compose, full_msginfo,
1890 body_fmt, qmark, body, FALSE, TRUE,
1891 _("The body of the \"Forward\" template has an error at line %d."));
1892 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1893 quote_fmt_reset_vartable();
1894 compose_attach_parts(compose, msginfo);
1896 procmsg_msginfo_free(&full_msginfo);
1899 SIGNAL_BLOCK(textbuf);
1901 if (account->auto_sig)
1902 compose_insert_sig(compose, FALSE);
1904 compose_wrap_all(compose);
1907 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1908 gtkaspell_highlight_all(compose->gtkaspell);
1909 gtkaspell_unblock_check(compose->gtkaspell);
1911 SIGNAL_UNBLOCK(textbuf);
1913 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1914 (account->default_encrypt || account->default_sign))
1915 COMPOSE_PRIVACY_WARNING();
1917 cursor_pos = quote_fmt_get_cursor_pos();
1918 if (cursor_pos == -1)
1919 gtk_widget_grab_focus(compose->header_last->entry);
1921 gtk_widget_grab_focus(compose->text);
1923 if (!no_extedit && prefs_common.auto_exteditor)
1924 compose_exec_ext_editor(compose);
1927 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1928 gchar *folderidentifier;
1930 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1931 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1932 folderidentifier = folder_item_get_identifier(msginfo->folder);
1933 compose_set_save_to(compose, folderidentifier);
1934 g_free(folderidentifier);
1937 undo_unblock(compose->undostruct);
1939 compose->modified = FALSE;
1940 compose_set_title(compose);
1942 compose->updating = FALSE;
1943 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1944 SCROLL_TO_CURSOR(compose);
1946 if (compose->deferred_destroy) {
1947 compose_destroy(compose);
1951 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1956 #undef INSERT_FW_HEADER
1958 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1961 GtkTextView *textview;
1962 GtkTextBuffer *textbuf;
1966 gboolean single_mail = TRUE;
1968 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1970 if (g_slist_length(msginfo_list) > 1)
1971 single_mail = FALSE;
1973 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1974 if (((MsgInfo *)msginfo->data)->folder == NULL)
1977 /* guess account from first selected message */
1979 !(account = compose_find_account(msginfo_list->data)))
1980 account = cur_account;
1982 cm_return_val_if_fail(account != NULL, NULL);
1984 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1985 if (msginfo->data) {
1986 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1987 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1991 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1992 g_warning("no msginfo_list");
1996 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1997 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1998 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1999 (account->default_encrypt || account->default_sign))
2000 COMPOSE_PRIVACY_WARNING();
2002 compose->updating = TRUE;
2004 /* override from name according to folder properties */
2005 if (msginfo_list->data) {
2006 MsgInfo *msginfo = msginfo_list->data;
2008 if (msginfo->folder && msginfo->folder->prefs &&
2009 msginfo->folder->prefs->forward_with_format &&
2010 msginfo->folder->prefs->forward_override_from_format &&
2011 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2016 /* decode \-escape sequences in the internal representation of the quote format */
2017 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2018 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2022 compose->gtkaspell);
2024 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2026 quote_fmt_scan_string(tmp);
2029 buf = quote_fmt_get_buffer();
2031 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2033 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2034 quote_fmt_reset_vartable();
2035 quote_fmtlex_destroy();
2041 textview = GTK_TEXT_VIEW(compose->text);
2042 textbuf = gtk_text_view_get_buffer(textview);
2043 compose_create_tags(textview, compose);
2045 undo_block(compose->undostruct);
2046 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2047 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2049 if (!is_file_exist(msgfile))
2050 g_warning("%s: file does not exist", msgfile);
2052 compose_attach_append(compose, msgfile, msgfile,
2053 "message/rfc822", NULL);
2058 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2059 if (info->subject && *info->subject) {
2060 gchar *buf, *buf2, *p;
2062 buf = p = g_strdup(info->subject);
2063 p += subject_get_prefix_length(p);
2064 memmove(buf, p, strlen(p) + 1);
2066 buf2 = g_strdup_printf("Fw: %s", buf);
2067 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2073 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2074 _("Fw: multiple emails"));
2077 SIGNAL_BLOCK(textbuf);
2079 if (account->auto_sig)
2080 compose_insert_sig(compose, FALSE);
2082 compose_wrap_all(compose);
2084 SIGNAL_UNBLOCK(textbuf);
2086 gtk_text_buffer_get_start_iter(textbuf, &iter);
2087 gtk_text_buffer_place_cursor(textbuf, &iter);
2089 if (prefs_common.auto_exteditor)
2090 compose_exec_ext_editor(compose);
2092 gtk_widget_grab_focus(compose->header_last->entry);
2093 undo_unblock(compose->undostruct);
2094 compose->modified = FALSE;
2095 compose_set_title(compose);
2097 compose->updating = FALSE;
2098 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2099 SCROLL_TO_CURSOR(compose);
2101 if (compose->deferred_destroy) {
2102 compose_destroy(compose);
2106 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2111 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2113 GtkTextIter start = *iter;
2114 GtkTextIter end_iter;
2115 int start_pos = gtk_text_iter_get_offset(&start);
2117 if (!compose->account->sig_sep)
2120 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2121 start_pos+strlen(compose->account->sig_sep));
2123 /* check sig separator */
2124 str = gtk_text_iter_get_text(&start, &end_iter);
2125 if (!strcmp(str, compose->account->sig_sep)) {
2127 /* check end of line (\n) */
2128 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2129 start_pos+strlen(compose->account->sig_sep));
2130 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2131 start_pos+strlen(compose->account->sig_sep)+1);
2132 tmp = gtk_text_iter_get_text(&start, &end_iter);
2133 if (!strcmp(tmp,"\n")) {
2145 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2147 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2148 Compose *compose = (Compose *)data;
2149 FolderItem *old_item = NULL;
2150 FolderItem *new_item = NULL;
2151 gchar *old_id, *new_id;
2153 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2154 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2157 old_item = hookdata->item;
2158 new_item = hookdata->item2;
2160 old_id = folder_item_get_identifier(old_item);
2161 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2163 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2164 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2165 compose->targetinfo->folder = new_item;
2168 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2169 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2170 compose->replyinfo->folder = new_item;
2173 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2174 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2175 compose->fwdinfo->folder = new_item;
2183 static void compose_colorize_signature(Compose *compose)
2185 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2187 GtkTextIter end_iter;
2188 gtk_text_buffer_get_start_iter(buffer, &iter);
2189 while (gtk_text_iter_forward_line(&iter))
2190 if (compose_is_sig_separator(compose, buffer, &iter)) {
2191 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2192 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2196 #define BLOCK_WRAP() { \
2197 prev_autowrap = compose->autowrap; \
2198 buffer = gtk_text_view_get_buffer( \
2199 GTK_TEXT_VIEW(compose->text)); \
2200 compose->autowrap = FALSE; \
2202 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2203 G_CALLBACK(compose_changed_cb), \
2205 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2206 G_CALLBACK(text_inserted), \
2209 #define UNBLOCK_WRAP() { \
2210 compose->autowrap = prev_autowrap; \
2211 if (compose->autowrap) { \
2212 gint old = compose->draft_timeout_tag; \
2213 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2214 compose_wrap_all(compose); \
2215 compose->draft_timeout_tag = old; \
2218 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2219 G_CALLBACK(compose_changed_cb), \
2221 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2222 G_CALLBACK(text_inserted), \
2226 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2228 Compose *compose = NULL;
2229 PrefsAccount *account = NULL;
2230 GtkTextView *textview;
2231 GtkTextBuffer *textbuf;
2235 gboolean use_signing = FALSE;
2236 gboolean use_encryption = FALSE;
2237 gchar *privacy_system = NULL;
2238 int priority = PRIORITY_NORMAL;
2239 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2240 gboolean autowrap = prefs_common.autowrap;
2241 gboolean autoindent = prefs_common.auto_indent;
2242 HeaderEntry *manual_headers = NULL;
2244 cm_return_val_if_fail(msginfo != NULL, NULL);
2245 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2247 if (compose_put_existing_to_front(msginfo)) {
2251 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2252 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2253 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2254 gchar *queueheader_buf = NULL;
2257 /* Select Account from queue headers */
2258 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2259 "X-Claws-Account-Id:")) {
2260 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2261 account = account_find_from_id(id);
2262 g_free(queueheader_buf);
2264 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2265 "X-Sylpheed-Account-Id:")) {
2266 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2267 account = account_find_from_id(id);
2268 g_free(queueheader_buf);
2270 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2272 id = atoi(&queueheader_buf[strlen("NAID:")]);
2273 account = account_find_from_id(id);
2274 g_free(queueheader_buf);
2276 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2278 id = atoi(&queueheader_buf[strlen("MAID:")]);
2279 account = account_find_from_id(id);
2280 g_free(queueheader_buf);
2282 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2284 account = account_find_from_address(queueheader_buf, FALSE);
2285 g_free(queueheader_buf);
2287 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2289 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2290 use_signing = param;
2291 g_free(queueheader_buf);
2293 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2294 "X-Sylpheed-Sign:")) {
2295 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2296 use_signing = param;
2297 g_free(queueheader_buf);
2299 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2300 "X-Claws-Encrypt:")) {
2301 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2302 use_encryption = param;
2303 g_free(queueheader_buf);
2305 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2306 "X-Sylpheed-Encrypt:")) {
2307 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2308 use_encryption = param;
2309 g_free(queueheader_buf);
2311 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2312 "X-Claws-Auto-Wrapping:")) {
2313 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2315 g_free(queueheader_buf);
2317 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2318 "X-Claws-Auto-Indent:")) {
2319 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2321 g_free(queueheader_buf);
2323 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2324 "X-Claws-Privacy-System:")) {
2325 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2326 g_free(queueheader_buf);
2328 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2329 "X-Sylpheed-Privacy-System:")) {
2330 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2331 g_free(queueheader_buf);
2333 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2335 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2337 g_free(queueheader_buf);
2339 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2341 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2342 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2343 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2344 if (orig_item != NULL) {
2345 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2350 g_free(queueheader_buf);
2352 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2354 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2355 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2356 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2357 if (orig_item != NULL) {
2358 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2363 g_free(queueheader_buf);
2365 /* Get manual headers */
2366 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2367 "X-Claws-Manual-Headers:")) {
2368 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2369 if (listmh && *listmh != '\0') {
2370 debug_print("Got manual headers: %s\n", listmh);
2371 manual_headers = procheader_entries_from_str(listmh);
2375 g_free(queueheader_buf);
2378 account = msginfo->folder->folder->account;
2381 if (!account && prefs_common.reedit_account_autosel) {
2383 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2384 extract_address(from);
2385 account = account_find_from_address(from, FALSE);
2391 account = cur_account;
2394 g_warning("can't select account");
2396 procheader_entries_free(manual_headers);
2400 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2402 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2403 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2404 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2405 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2406 compose->autowrap = autowrap;
2407 compose->replyinfo = replyinfo;
2408 compose->fwdinfo = fwdinfo;
2410 compose->updating = TRUE;
2411 compose->priority = priority;
2413 if (privacy_system != NULL) {
2414 compose->privacy_system = privacy_system;
2415 compose_use_signing(compose, use_signing);
2416 compose_use_encryption(compose, use_encryption);
2417 compose_update_privacy_system_menu_item(compose, FALSE);
2419 compose_activate_privacy_system(compose, account, FALSE);
2421 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2422 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2423 (account->default_encrypt || account->default_sign))
2424 COMPOSE_PRIVACY_WARNING();
2426 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2427 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2429 compose_extract_original_charset(compose);
2431 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2432 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2433 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2434 gchar *queueheader_buf = NULL;
2436 /* Set message save folder */
2437 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2438 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2439 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2440 compose_set_save_to(compose, &queueheader_buf[4]);
2441 g_free(queueheader_buf);
2443 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2444 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2446 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2448 g_free(queueheader_buf);
2452 if (compose_parse_header(compose, msginfo) < 0) {
2453 compose->updating = FALSE;
2454 compose_destroy(compose);
2456 procheader_entries_free(manual_headers);
2459 compose_reedit_set_entry(compose, msginfo);
2461 textview = GTK_TEXT_VIEW(compose->text);
2462 textbuf = gtk_text_view_get_buffer(textview);
2463 compose_create_tags(textview, compose);
2465 mark = gtk_text_buffer_get_insert(textbuf);
2466 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2468 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2469 G_CALLBACK(compose_changed_cb),
2472 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2473 fp = procmime_get_first_encrypted_text_content(msginfo);
2475 compose_force_encryption(compose, account, TRUE, NULL);
2478 fp = procmime_get_first_text_content(msginfo);
2481 g_warning("can't get text part");
2485 gchar buf[BUFFSIZE];
2486 gboolean prev_autowrap;
2487 GtkTextBuffer *buffer;
2489 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2491 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2497 compose_attach_parts(compose, msginfo);
2499 compose_colorize_signature(compose);
2501 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2502 G_CALLBACK(compose_changed_cb),
2505 if (manual_headers != NULL) {
2506 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2507 procheader_entries_free(manual_headers);
2508 compose->updating = FALSE;
2509 compose_destroy(compose);
2512 procheader_entries_free(manual_headers);
2515 gtk_widget_grab_focus(compose->text);
2517 if (prefs_common.auto_exteditor) {
2518 compose_exec_ext_editor(compose);
2520 compose->modified = FALSE;
2521 compose_set_title(compose);
2523 compose->updating = FALSE;
2524 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2525 SCROLL_TO_CURSOR(compose);
2527 if (compose->deferred_destroy) {
2528 compose_destroy(compose);
2532 compose->sig_str = account_get_signature_str(compose->account);
2534 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2539 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2546 cm_return_val_if_fail(msginfo != NULL, NULL);
2549 account = account_get_reply_account(msginfo,
2550 prefs_common.reply_account_autosel);
2551 cm_return_val_if_fail(account != NULL, NULL);
2553 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2555 compose->updating = TRUE;
2557 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2558 compose->replyinfo = NULL;
2559 compose->fwdinfo = NULL;
2561 compose_show_first_last_header(compose, TRUE);
2563 gtk_widget_grab_focus(compose->header_last->entry);
2565 filename = procmsg_get_message_file(msginfo);
2567 if (filename == NULL) {
2568 compose->updating = FALSE;
2569 compose_destroy(compose);
2574 compose->redirect_filename = filename;
2576 /* Set save folder */
2577 item = msginfo->folder;
2578 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2579 gchar *folderidentifier;
2581 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2582 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2583 folderidentifier = folder_item_get_identifier(item);
2584 compose_set_save_to(compose, folderidentifier);
2585 g_free(folderidentifier);
2588 compose_attach_parts(compose, msginfo);
2590 if (msginfo->subject)
2591 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2593 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2595 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2596 _("The body of the \"Redirect\" template has an error at line %d."));
2597 quote_fmt_reset_vartable();
2598 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2600 compose_colorize_signature(compose);
2603 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2604 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2605 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2607 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2608 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2609 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2610 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2611 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2612 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2613 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2614 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2615 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2617 if (compose->toolbar->draft_btn)
2618 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2619 if (compose->toolbar->insert_btn)
2620 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2621 if (compose->toolbar->attach_btn)
2622 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2623 if (compose->toolbar->sig_btn)
2624 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2625 if (compose->toolbar->exteditor_btn)
2626 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2627 if (compose->toolbar->linewrap_current_btn)
2628 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2629 if (compose->toolbar->linewrap_all_btn)
2630 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2631 if (compose->toolbar->privacy_sign_btn)
2632 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2633 if (compose->toolbar->privacy_encrypt_btn)
2634 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2636 compose->modified = FALSE;
2637 compose_set_title(compose);
2638 compose->updating = FALSE;
2639 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2640 SCROLL_TO_CURSOR(compose);
2642 if (compose->deferred_destroy) {
2643 compose_destroy(compose);
2647 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2652 const GList *compose_get_compose_list(void)
2654 return compose_list;
2657 void compose_entry_append(Compose *compose, const gchar *address,
2658 ComposeEntryType type, ComposePrefType pref_type)
2660 const gchar *header;
2662 gboolean in_quote = FALSE;
2663 if (!address || *address == '\0') return;
2670 header = N_("Bcc:");
2672 case COMPOSE_REPLYTO:
2673 header = N_("Reply-To:");
2675 case COMPOSE_NEWSGROUPS:
2676 header = N_("Newsgroups:");
2678 case COMPOSE_FOLLOWUPTO:
2679 header = N_( "Followup-To:");
2681 case COMPOSE_INREPLYTO:
2682 header = N_( "In-Reply-To:");
2689 header = prefs_common_translated_header_name(header);
2691 cur = begin = (gchar *)address;
2693 /* we separate the line by commas, but not if we're inside a quoted
2695 while (*cur != '\0') {
2697 in_quote = !in_quote;
2698 if (*cur == ',' && !in_quote) {
2699 gchar *tmp = g_strdup(begin);
2701 tmp[cur-begin]='\0';
2704 while (*tmp == ' ' || *tmp == '\t')
2706 compose_add_header_entry(compose, header, tmp, pref_type);
2707 compose_entry_indicate(compose, tmp);
2714 gchar *tmp = g_strdup(begin);
2716 tmp[cur-begin]='\0';
2717 while (*tmp == ' ' || *tmp == '\t')
2719 compose_add_header_entry(compose, header, tmp, pref_type);
2720 compose_entry_indicate(compose, tmp);
2725 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2730 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2731 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2732 if (gtk_entry_get_text(entry) &&
2733 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2734 gtk_widget_modify_base(
2735 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2736 GTK_STATE_NORMAL, &default_header_bgcolor);
2737 gtk_widget_modify_text(
2738 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2739 GTK_STATE_NORMAL, &default_header_color);
2744 void compose_toolbar_cb(gint action, gpointer data)
2746 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2747 Compose *compose = (Compose*)toolbar_item->parent;
2749 cm_return_if_fail(compose != NULL);
2753 compose_send_cb(NULL, compose);
2756 compose_send_later_cb(NULL, compose);
2759 compose_draft(compose, COMPOSE_QUIT_EDITING);
2762 compose_insert_file_cb(NULL, compose);
2765 compose_attach_cb(NULL, compose);
2768 compose_insert_sig(compose, FALSE);
2771 compose_insert_sig(compose, TRUE);
2774 compose_ext_editor_cb(NULL, compose);
2776 case A_LINEWRAP_CURRENT:
2777 compose_beautify_paragraph(compose, NULL, TRUE);
2779 case A_LINEWRAP_ALL:
2780 compose_wrap_all_full(compose, TRUE);
2783 compose_address_cb(NULL, compose);
2786 case A_CHECK_SPELLING:
2787 compose_check_all(NULL, compose);
2790 case A_PRIVACY_SIGN:
2792 case A_PRIVACY_ENCRYPT:
2799 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2804 gchar *subject = NULL;
2808 gchar **attach = NULL;
2809 gchar *inreplyto = NULL;
2810 MailField mfield = NO_FIELD_PRESENT;
2812 /* get mailto parts but skip from */
2813 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2816 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2817 mfield = TO_FIELD_PRESENT;
2820 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2822 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2824 if (!g_utf8_validate (subject, -1, NULL)) {
2825 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2826 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2829 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2831 mfield = SUBJECT_FIELD_PRESENT;
2834 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2835 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2838 gboolean prev_autowrap = compose->autowrap;
2840 compose->autowrap = FALSE;
2842 mark = gtk_text_buffer_get_insert(buffer);
2843 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2845 if (!g_utf8_validate (body, -1, NULL)) {
2846 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2847 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2850 gtk_text_buffer_insert(buffer, &iter, body, -1);
2852 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2854 compose->autowrap = prev_autowrap;
2855 if (compose->autowrap)
2856 compose_wrap_all(compose);
2857 mfield = BODY_FIELD_PRESENT;
2861 gint i = 0, att = 0;
2862 gchar *warn_files = NULL;
2863 while (attach[i] != NULL) {
2864 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2865 if (utf8_filename) {
2866 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2867 gchar *tmp = g_strdup_printf("%s%s\n",
2868 warn_files?warn_files:"",
2874 g_free(utf8_filename);
2876 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2881 alertpanel_notice(ngettext(
2882 "The following file has been attached: \n%s",
2883 "The following files have been attached: \n%s", att), warn_files);
2888 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2901 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2903 static HeaderEntry hentry[] = {
2904 {"Reply-To:", NULL, TRUE },
2905 {"Cc:", NULL, TRUE },
2906 {"References:", NULL, FALSE },
2907 {"Bcc:", NULL, TRUE },
2908 {"Newsgroups:", NULL, TRUE },
2909 {"Followup-To:", NULL, TRUE },
2910 {"List-Post:", NULL, FALSE },
2911 {"X-Priority:", NULL, FALSE },
2912 {NULL, NULL, FALSE }
2929 cm_return_val_if_fail(msginfo != NULL, -1);
2931 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2932 procheader_get_header_fields(fp, hentry);
2935 if (hentry[H_REPLY_TO].body != NULL) {
2936 if (hentry[H_REPLY_TO].body[0] != '\0') {
2938 conv_unmime_header(hentry[H_REPLY_TO].body,
2941 g_free(hentry[H_REPLY_TO].body);
2942 hentry[H_REPLY_TO].body = NULL;
2944 if (hentry[H_CC].body != NULL) {
2945 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2946 g_free(hentry[H_CC].body);
2947 hentry[H_CC].body = NULL;
2949 if (hentry[H_REFERENCES].body != NULL) {
2950 if (compose->mode == COMPOSE_REEDIT)
2951 compose->references = hentry[H_REFERENCES].body;
2953 compose->references = compose_parse_references
2954 (hentry[H_REFERENCES].body, msginfo->msgid);
2955 g_free(hentry[H_REFERENCES].body);
2957 hentry[H_REFERENCES].body = NULL;
2959 if (hentry[H_BCC].body != NULL) {
2960 if (compose->mode == COMPOSE_REEDIT)
2962 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2963 g_free(hentry[H_BCC].body);
2964 hentry[H_BCC].body = NULL;
2966 if (hentry[H_NEWSGROUPS].body != NULL) {
2967 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2968 hentry[H_NEWSGROUPS].body = NULL;
2970 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2971 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2972 compose->followup_to =
2973 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2976 g_free(hentry[H_FOLLOWUP_TO].body);
2977 hentry[H_FOLLOWUP_TO].body = NULL;
2979 if (hentry[H_LIST_POST].body != NULL) {
2980 gchar *to = NULL, *start = NULL;
2982 extract_address(hentry[H_LIST_POST].body);
2983 if (hentry[H_LIST_POST].body[0] != '\0') {
2984 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2986 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2987 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2990 g_free(compose->ml_post);
2991 compose->ml_post = to;
2994 g_free(hentry[H_LIST_POST].body);
2995 hentry[H_LIST_POST].body = NULL;
2998 /* CLAWS - X-Priority */
2999 if (compose->mode == COMPOSE_REEDIT)
3000 if (hentry[H_X_PRIORITY].body != NULL) {
3003 priority = atoi(hentry[H_X_PRIORITY].body);
3004 g_free(hentry[H_X_PRIORITY].body);
3006 hentry[H_X_PRIORITY].body = NULL;
3008 if (priority < PRIORITY_HIGHEST ||
3009 priority > PRIORITY_LOWEST)
3010 priority = PRIORITY_NORMAL;
3012 compose->priority = priority;
3015 if (compose->mode == COMPOSE_REEDIT) {
3016 if (msginfo->inreplyto && *msginfo->inreplyto)
3017 compose->inreplyto = g_strdup(msginfo->inreplyto);
3019 if (msginfo->msgid && *msginfo->msgid &&
3020 compose->folder != NULL &&
3021 compose->folder->stype == F_DRAFT)
3022 compose->msgid = g_strdup(msginfo->msgid);
3024 if (msginfo->msgid && *msginfo->msgid)
3025 compose->inreplyto = g_strdup(msginfo->msgid);
3027 if (!compose->references) {
3028 if (msginfo->msgid && *msginfo->msgid) {
3029 if (msginfo->inreplyto && *msginfo->inreplyto)
3030 compose->references =
3031 g_strdup_printf("<%s>\n\t<%s>",
3035 compose->references =
3036 g_strconcat("<", msginfo->msgid, ">",
3038 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3039 compose->references =
3040 g_strconcat("<", msginfo->inreplyto, ">",
3049 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3054 cm_return_val_if_fail(msginfo != NULL, -1);
3056 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3057 procheader_get_header_fields(fp, entries);
3061 while (he != NULL && he->name != NULL) {
3063 GtkListStore *model = NULL;
3065 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3066 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3067 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3068 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3069 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3076 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3078 GSList *ref_id_list, *cur;
3082 ref_id_list = references_list_append(NULL, ref);
3083 if (!ref_id_list) return NULL;
3084 if (msgid && *msgid)
3085 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3090 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3091 /* "<" + Message-ID + ">" + CR+LF+TAB */
3092 len += strlen((gchar *)cur->data) + 5;
3094 if (len > MAX_REFERENCES_LEN) {
3095 /* remove second message-ID */
3096 if (ref_id_list && ref_id_list->next &&
3097 ref_id_list->next->next) {
3098 g_free(ref_id_list->next->data);
3099 ref_id_list = g_slist_remove
3100 (ref_id_list, ref_id_list->next->data);
3102 slist_free_strings_full(ref_id_list);
3109 new_ref = g_string_new("");
3110 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3111 if (new_ref->len > 0)
3112 g_string_append(new_ref, "\n\t");
3113 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3116 slist_free_strings_full(ref_id_list);
3118 new_ref_str = new_ref->str;
3119 g_string_free(new_ref, FALSE);
3124 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3125 const gchar *fmt, const gchar *qmark,
3126 const gchar *body, gboolean rewrap,
3127 gboolean need_unescape,
3128 const gchar *err_msg)
3130 MsgInfo* dummyinfo = NULL;
3131 gchar *quote_str = NULL;
3133 gboolean prev_autowrap;
3134 const gchar *trimmed_body = body;
3135 gint cursor_pos = -1;
3136 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3137 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3142 SIGNAL_BLOCK(buffer);
3145 dummyinfo = compose_msginfo_new_from_compose(compose);
3146 msginfo = dummyinfo;
3149 if (qmark != NULL) {
3151 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3152 compose->gtkaspell);
3154 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3156 quote_fmt_scan_string(qmark);
3159 buf = quote_fmt_get_buffer();
3162 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3164 Xstrdup_a(quote_str, buf, goto error)
3167 if (fmt && *fmt != '\0') {
3170 while (*trimmed_body == '\n')
3174 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3175 compose->gtkaspell);
3177 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3179 if (need_unescape) {
3182 /* decode \-escape sequences in the internal representation of the quote format */
3183 tmp = g_malloc(strlen(fmt)+1);
3184 pref_get_unescaped_pref(tmp, fmt);
3185 quote_fmt_scan_string(tmp);
3189 quote_fmt_scan_string(fmt);
3193 buf = quote_fmt_get_buffer();
3196 gint line = quote_fmt_get_line();
3197 alertpanel_error(err_msg, line);
3205 prev_autowrap = compose->autowrap;
3206 compose->autowrap = FALSE;
3208 mark = gtk_text_buffer_get_insert(buffer);
3209 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3210 if (g_utf8_validate(buf, -1, NULL)) {
3211 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3213 gchar *tmpout = NULL;
3214 tmpout = conv_codeset_strdup
3215 (buf, conv_get_locale_charset_str_no_utf8(),
3217 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3219 tmpout = g_malloc(strlen(buf)*2+1);
3220 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3222 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3226 cursor_pos = quote_fmt_get_cursor_pos();
3227 if (cursor_pos == -1)
3228 cursor_pos = gtk_text_iter_get_offset(&iter);
3229 compose->set_cursor_pos = cursor_pos;
3231 gtk_text_buffer_get_start_iter(buffer, &iter);
3232 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3233 gtk_text_buffer_place_cursor(buffer, &iter);
3235 compose->autowrap = prev_autowrap;
3236 if (compose->autowrap && rewrap)
3237 compose_wrap_all(compose);
3244 SIGNAL_UNBLOCK(buffer);
3246 procmsg_msginfo_free( &dummyinfo );
3251 /* if ml_post is of type addr@host and from is of type
3252 * addr-anything@host, return TRUE
3254 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3256 gchar *left_ml = NULL;
3257 gchar *right_ml = NULL;
3258 gchar *left_from = NULL;
3259 gchar *right_from = NULL;
3260 gboolean result = FALSE;
3262 if (!ml_post || !from)
3265 left_ml = g_strdup(ml_post);
3266 if (strstr(left_ml, "@")) {
3267 right_ml = strstr(left_ml, "@")+1;
3268 *(strstr(left_ml, "@")) = '\0';
3271 left_from = g_strdup(from);
3272 if (strstr(left_from, "@")) {
3273 right_from = strstr(left_from, "@")+1;
3274 *(strstr(left_from, "@")) = '\0';
3277 if (right_ml && right_from
3278 && !strncmp(left_from, left_ml, strlen(left_ml))
3279 && !strcmp(right_from, right_ml)) {
3288 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3289 gboolean respect_default_to)
3293 if (!folder || !folder->prefs)
3296 if (folder->prefs->enable_default_from) {
3297 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from);
3298 compose_entry_indicate(compose, folder->prefs->default_from);
3300 if (respect_default_to && folder->prefs->enable_default_to) {
3301 compose_entry_append(compose, folder->prefs->default_to,
3302 COMPOSE_TO, PREF_FOLDER);
3303 compose_entry_indicate(compose, folder->prefs->default_to);
3305 if (folder->prefs->enable_default_cc) {
3306 compose_entry_append(compose, folder->prefs->default_cc,
3307 COMPOSE_CC, PREF_FOLDER);
3308 compose_entry_indicate(compose, folder->prefs->default_cc);
3310 if (folder->prefs->enable_default_bcc) {
3311 compose_entry_append(compose, folder->prefs->default_bcc,
3312 COMPOSE_BCC, PREF_FOLDER);
3313 compose_entry_indicate(compose, folder->prefs->default_bcc);
3315 if (folder->prefs->enable_default_replyto) {
3316 compose_entry_append(compose, folder->prefs->default_replyto,
3317 COMPOSE_REPLYTO, PREF_FOLDER);
3318 compose_entry_indicate(compose, folder->prefs->default_replyto);
3322 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3327 if (!compose || !msginfo)
3330 if (msginfo->subject && *msginfo->subject) {
3331 buf = p = g_strdup(msginfo->subject);
3332 p += subject_get_prefix_length(p);
3333 memmove(buf, p, strlen(p) + 1);
3335 buf2 = g_strdup_printf("Re: %s", buf);
3336 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3341 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3344 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3345 gboolean to_all, gboolean to_ml,
3347 gboolean followup_and_reply_to)
3349 GSList *cc_list = NULL;
3352 gchar *replyto = NULL;
3353 gchar *ac_email = NULL;
3355 gboolean reply_to_ml = FALSE;
3356 gboolean default_reply_to = FALSE;
3358 cm_return_if_fail(compose->account != NULL);
3359 cm_return_if_fail(msginfo != NULL);
3361 reply_to_ml = to_ml && compose->ml_post;
3363 default_reply_to = msginfo->folder &&
3364 msginfo->folder->prefs->enable_default_reply_to;
3366 if (compose->account->protocol != A_NNTP) {
3367 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3369 if (reply_to_ml && !default_reply_to) {
3371 gboolean is_subscr = is_subscription(compose->ml_post,
3374 /* normal answer to ml post with a reply-to */
3375 compose_entry_append(compose,
3377 COMPOSE_TO, PREF_ML);
3378 if (compose->replyto)
3379 compose_entry_append(compose,
3381 COMPOSE_CC, PREF_ML);
3383 /* answer to subscription confirmation */
3384 if (compose->replyto)
3385 compose_entry_append(compose,
3387 COMPOSE_TO, PREF_ML);
3388 else if (msginfo->from)
3389 compose_entry_append(compose,
3391 COMPOSE_TO, PREF_ML);
3394 else if (!(to_all || to_sender) && default_reply_to) {
3395 compose_entry_append(compose,
3396 msginfo->folder->prefs->default_reply_to,
3397 COMPOSE_TO, PREF_FOLDER);
3398 compose_entry_indicate(compose,
3399 msginfo->folder->prefs->default_reply_to);
3405 compose_entry_append(compose, msginfo->from,
3406 COMPOSE_TO, PREF_NONE);
3408 Xstrdup_a(tmp1, msginfo->from, return);
3409 extract_address(tmp1);
3410 compose_entry_append(compose,
3411 (!account_find_from_address(tmp1, FALSE))
3414 COMPOSE_TO, PREF_NONE);
3415 if (compose->replyto)
3416 compose_entry_append(compose,
3418 COMPOSE_CC, PREF_NONE);
3420 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3421 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3422 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3423 if (compose->replyto) {
3424 compose_entry_append(compose,
3426 COMPOSE_TO, PREF_NONE);
3428 compose_entry_append(compose,
3429 msginfo->from ? msginfo->from : "",
3430 COMPOSE_TO, PREF_NONE);
3433 /* replying to own mail, use original recp */
3434 compose_entry_append(compose,
3435 msginfo->to ? msginfo->to : "",
3436 COMPOSE_TO, PREF_NONE);
3437 compose_entry_append(compose,
3438 msginfo->cc ? msginfo->cc : "",
3439 COMPOSE_CC, PREF_NONE);
3444 if (to_sender || (compose->followup_to &&
3445 !strncmp(compose->followup_to, "poster", 6)))
3446 compose_entry_append
3448 (compose->replyto ? compose->replyto :
3449 msginfo->from ? msginfo->from : ""),
3450 COMPOSE_TO, PREF_NONE);
3452 else if (followup_and_reply_to || to_all) {
3453 compose_entry_append
3455 (compose->replyto ? compose->replyto :
3456 msginfo->from ? msginfo->from : ""),
3457 COMPOSE_TO, PREF_NONE);
3459 compose_entry_append
3461 compose->followup_to ? compose->followup_to :
3462 compose->newsgroups ? compose->newsgroups : "",
3463 COMPOSE_NEWSGROUPS, PREF_NONE);
3465 compose_entry_append
3467 msginfo->cc ? msginfo->cc : "",
3468 COMPOSE_CC, PREF_NONE);
3471 compose_entry_append
3473 compose->followup_to ? compose->followup_to :
3474 compose->newsgroups ? compose->newsgroups : "",
3475 COMPOSE_NEWSGROUPS, PREF_NONE);
3477 compose_reply_set_subject(compose, msginfo);
3479 if (to_ml && compose->ml_post) return;
3480 if (!to_all || compose->account->protocol == A_NNTP) return;
3482 if (compose->replyto) {
3483 Xstrdup_a(replyto, compose->replyto, return);
3484 extract_address(replyto);
3486 if (msginfo->from) {
3487 Xstrdup_a(from, msginfo->from, return);
3488 extract_address(from);
3491 if (replyto && from)
3492 cc_list = address_list_append_with_comments(cc_list, from);
3493 if (to_all && msginfo->folder &&
3494 msginfo->folder->prefs->enable_default_reply_to)
3495 cc_list = address_list_append_with_comments(cc_list,
3496 msginfo->folder->prefs->default_reply_to);
3497 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3498 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3500 ac_email = g_utf8_strdown(compose->account->address, -1);
3503 for (cur = cc_list; cur != NULL; cur = cur->next) {
3504 gchar *addr = g_utf8_strdown(cur->data, -1);
3505 extract_address(addr);
3507 if (strcmp(ac_email, addr))
3508 compose_entry_append(compose, (gchar *)cur->data,
3509 COMPOSE_CC, PREF_NONE);
3511 debug_print("Cc address same as compose account's, ignoring\n");
3516 slist_free_strings_full(cc_list);
3522 #define SET_ENTRY(entry, str) \
3525 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3528 #define SET_ADDRESS(type, str) \
3531 compose_entry_append(compose, str, type, PREF_NONE); \
3534 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3536 cm_return_if_fail(msginfo != NULL);
3538 SET_ENTRY(subject_entry, msginfo->subject);
3539 SET_ENTRY(from_name, msginfo->from);
3540 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3541 SET_ADDRESS(COMPOSE_CC, compose->cc);
3542 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3543 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3544 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3545 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3547 compose_update_priority_menu_item(compose);
3548 compose_update_privacy_system_menu_item(compose, FALSE);
3549 compose_show_first_last_header(compose, TRUE);
3555 static void compose_insert_sig(Compose *compose, gboolean replace)
3557 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3558 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3560 GtkTextIter iter, iter_end;
3561 gint cur_pos, ins_pos;
3562 gboolean prev_autowrap;
3563 gboolean found = FALSE;
3564 gboolean exists = FALSE;
3566 cm_return_if_fail(compose->account != NULL);
3570 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3571 G_CALLBACK(compose_changed_cb),
3574 mark = gtk_text_buffer_get_insert(buffer);
3575 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3576 cur_pos = gtk_text_iter_get_offset (&iter);
3579 gtk_text_buffer_get_end_iter(buffer, &iter);
3581 exists = (compose->sig_str != NULL);
3584 GtkTextIter first_iter, start_iter, end_iter;
3586 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3588 if (!exists || compose->sig_str[0] == '\0')
3591 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3592 compose->signature_tag);
3595 /* include previous \n\n */
3596 gtk_text_iter_backward_chars(&first_iter, 1);
3597 start_iter = first_iter;
3598 end_iter = first_iter;
3600 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3601 compose->signature_tag);
3602 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3603 compose->signature_tag);
3605 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3611 g_free(compose->sig_str);
3612 compose->sig_str = account_get_signature_str(compose->account);
3614 cur_pos = gtk_text_iter_get_offset(&iter);
3616 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3617 g_free(compose->sig_str);
3618 compose->sig_str = NULL;
3620 if (compose->sig_inserted == FALSE)
3621 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3622 compose->sig_inserted = TRUE;
3624 cur_pos = gtk_text_iter_get_offset(&iter);
3625 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3627 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3628 gtk_text_iter_forward_chars(&iter, 1);
3629 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3630 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3632 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3633 cur_pos = gtk_text_buffer_get_char_count (buffer);
3636 /* put the cursor where it should be
3637 * either where the quote_fmt says, either where it was */
3638 if (compose->set_cursor_pos < 0)
3639 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3641 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3642 compose->set_cursor_pos);
3644 compose->set_cursor_pos = -1;
3645 gtk_text_buffer_place_cursor(buffer, &iter);
3646 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3647 G_CALLBACK(compose_changed_cb),
3653 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3656 GtkTextBuffer *buffer;
3659 const gchar *cur_encoding;
3660 gchar buf[BUFFSIZE];
3663 gboolean prev_autowrap;
3667 GError *error = NULL;
3673 GString *file_contents = NULL;
3674 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3676 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3678 /* get the size of the file we are about to insert */
3680 f = g_file_new_for_path(file);
3681 fi = g_file_query_info(f, "standard::size",
3682 G_FILE_QUERY_INFO_NONE, NULL, &error);
3684 if (error != NULL) {
3685 g_warning(error->message);
3687 g_error_free(error);
3691 ret = g_stat(file, &file_stat);
3694 gchar *shortfile = g_path_get_basename(file);
3695 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3697 return COMPOSE_INSERT_NO_FILE;
3698 } else if (prefs_common.warn_large_insert == TRUE) {
3700 size = g_file_info_get_size(fi);
3704 size = file_stat.st_size;
3707 /* ask user for confirmation if the file is large */
3708 if (prefs_common.warn_large_insert_size < 0 ||
3709 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3713 msg = g_strdup_printf(_("You are about to insert a file of %s "
3714 "in the message body. Are you sure you want to do that?"),
3715 to_human_readable(size));
3716 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3717 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3718 NULL, ALERT_QUESTION);
3721 /* do we ask for confirmation next time? */
3722 if (aval & G_ALERTDISABLE) {
3723 /* no confirmation next time, disable feature in preferences */
3724 aval &= ~G_ALERTDISABLE;
3725 prefs_common.warn_large_insert = FALSE;
3728 /* abort file insertion if user canceled action */
3729 if (aval != G_ALERTALTERNATE) {
3730 return COMPOSE_INSERT_NO_FILE;
3736 if ((fp = claws_fopen(file, "rb")) == NULL) {
3737 FILE_OP_ERROR(file, "claws_fopen");
3738 return COMPOSE_INSERT_READ_ERROR;
3741 prev_autowrap = compose->autowrap;
3742 compose->autowrap = FALSE;
3744 text = GTK_TEXT_VIEW(compose->text);
3745 buffer = gtk_text_view_get_buffer(text);
3746 mark = gtk_text_buffer_get_insert(buffer);
3747 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3749 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3750 G_CALLBACK(text_inserted),
3753 cur_encoding = conv_get_locale_charset_str_no_utf8();
3755 file_contents = g_string_new("");
3756 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3759 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3760 str = g_strdup(buf);
3762 codeconv_set_strict(TRUE);
3763 str = conv_codeset_strdup
3764 (buf, cur_encoding, CS_INTERNAL);
3765 codeconv_set_strict(FALSE);
3768 result = COMPOSE_INSERT_INVALID_CHARACTER;
3774 /* strip <CR> if DOS/Windows file,
3775 replace <CR> with <LF> if Macintosh file. */
3778 if (len > 0 && str[len - 1] != '\n') {
3780 if (str[len] == '\r') str[len] = '\n';
3783 file_contents = g_string_append(file_contents, str);
3787 if (result == COMPOSE_INSERT_SUCCESS) {
3788 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3790 compose_changed_cb(NULL, compose);
3791 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3792 G_CALLBACK(text_inserted),
3794 compose->autowrap = prev_autowrap;
3795 if (compose->autowrap)
3796 compose_wrap_all(compose);
3799 g_string_free(file_contents, TRUE);
3805 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3806 const gchar *filename,
3807 const gchar *content_type,
3808 const gchar *charset)
3816 GtkListStore *store;
3818 gboolean has_binary = FALSE;
3820 if (!is_file_exist(file)) {
3821 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3822 gboolean result = FALSE;
3823 if (file_from_uri && is_file_exist(file_from_uri)) {
3824 result = compose_attach_append(
3825 compose, file_from_uri,
3826 filename, content_type,
3829 g_free(file_from_uri);
3832 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3835 if ((size = get_file_size(file)) < 0) {
3836 alertpanel_error("Can't get file size of %s\n", filename);
3840 /* In batch mode, we allow 0-length files to be attached no questions asked */
3841 if (size == 0 && !compose->batch) {
3842 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3843 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3844 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3845 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3848 if (aval != G_ALERTALTERNATE) {
3852 if ((fp = claws_fopen(file, "rb")) == NULL) {
3853 alertpanel_error(_("Can't read %s."), filename);
3858 ainfo = g_new0(AttachInfo, 1);
3859 auto_ainfo = g_auto_pointer_new_with_free
3860 (ainfo, (GFreeFunc) compose_attach_info_free);
3861 ainfo->file = g_strdup(file);
3864 ainfo->content_type = g_strdup(content_type);
3865 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3867 MsgFlags flags = {0, 0};
3869 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3870 ainfo->encoding = ENC_7BIT;
3872 ainfo->encoding = ENC_8BIT;
3874 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3875 if (msginfo && msginfo->subject)
3876 name = g_strdup(msginfo->subject);
3878 name = g_path_get_basename(filename ? filename : file);
3880 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3882 procmsg_msginfo_free(&msginfo);
3884 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3885 ainfo->charset = g_strdup(charset);
3886 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3888 ainfo->encoding = ENC_BASE64;
3890 name = g_path_get_basename(filename ? filename : file);
3891 ainfo->name = g_strdup(name);
3895 ainfo->content_type = procmime_get_mime_type(file);
3896 if (!ainfo->content_type) {
3897 ainfo->content_type =
3898 g_strdup("application/octet-stream");
3899 ainfo->encoding = ENC_BASE64;
3900 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3902 procmime_get_encoding_for_text_file(file, &has_binary);
3904 ainfo->encoding = ENC_BASE64;
3905 name = g_path_get_basename(filename ? filename : file);
3906 ainfo->name = g_strdup(name);
3910 if (ainfo->name != NULL
3911 && !strcmp(ainfo->name, ".")) {
3912 g_free(ainfo->name);
3916 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3917 g_free(ainfo->content_type);
3918 ainfo->content_type = g_strdup("application/octet-stream");
3919 g_free(ainfo->charset);
3920 ainfo->charset = NULL;
3923 ainfo->size = (goffset)size;
3924 size_text = to_human_readable((goffset)size);
3926 store = GTK_LIST_STORE(gtk_tree_view_get_model
3927 (GTK_TREE_VIEW(compose->attach_clist)));
3929 gtk_list_store_append(store, &iter);
3930 gtk_list_store_set(store, &iter,
3931 COL_MIMETYPE, ainfo->content_type,
3932 COL_SIZE, size_text,
3933 COL_NAME, ainfo->name,
3934 COL_CHARSET, ainfo->charset,
3936 COL_AUTODATA, auto_ainfo,
3939 g_auto_pointer_free(auto_ainfo);
3940 compose_attach_update_label(compose);
3944 void compose_use_signing(Compose *compose, gboolean use_signing)
3946 compose->use_signing = use_signing;
3947 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3950 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3952 compose->use_encryption = use_encryption;
3953 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3956 #define NEXT_PART_NOT_CHILD(info) \
3958 node = info->node; \
3959 while (node->children) \
3960 node = g_node_last_child(node); \
3961 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3964 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3968 MimeInfo *firsttext = NULL;
3969 MimeInfo *encrypted = NULL;
3972 const gchar *partname = NULL;
3974 mimeinfo = procmime_scan_message(msginfo);
3975 if (!mimeinfo) return;
3977 if (mimeinfo->node->children == NULL) {
3978 procmime_mimeinfo_free_all(&mimeinfo);
3982 /* find first content part */
3983 child = (MimeInfo *) mimeinfo->node->children->data;
3984 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3985 child = (MimeInfo *)child->node->children->data;
3988 if (child->type == MIMETYPE_TEXT) {
3990 debug_print("First text part found\n");
3991 } else if (compose->mode == COMPOSE_REEDIT &&
3992 child->type == MIMETYPE_APPLICATION &&
3993 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3994 encrypted = (MimeInfo *)child->node->parent->data;
3997 child = (MimeInfo *) mimeinfo->node->children->data;
3998 while (child != NULL) {
4001 if (child == encrypted) {
4002 /* skip this part of tree */
4003 NEXT_PART_NOT_CHILD(child);
4007 if (child->type == MIMETYPE_MULTIPART) {
4008 /* get the actual content */
4009 child = procmime_mimeinfo_next(child);
4013 if (child == firsttext) {
4014 child = procmime_mimeinfo_next(child);
4018 outfile = procmime_get_tmp_file_name(child);
4019 if ((err = procmime_get_part(outfile, child)) < 0)
4020 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err));
4022 gchar *content_type;
4024 content_type = procmime_get_content_type_str(child->type, child->subtype);
4026 /* if we meet a pgp signature, we don't attach it, but
4027 * we force signing. */
4028 if ((strcmp(content_type, "application/pgp-signature") &&
4029 strcmp(content_type, "application/pkcs7-signature") &&
4030 strcmp(content_type, "application/x-pkcs7-signature"))
4031 || compose->mode == COMPOSE_REDIRECT) {
4032 partname = procmime_mimeinfo_get_parameter(child, "filename");
4033 if (partname == NULL)
4034 partname = procmime_mimeinfo_get_parameter(child, "name");
4035 if (partname == NULL)
4037 compose_attach_append(compose, outfile,
4038 partname, content_type,
4039 procmime_mimeinfo_get_parameter(child, "charset"));
4041 compose_force_signing(compose, compose->account, NULL);
4043 g_free(content_type);
4046 NEXT_PART_NOT_CHILD(child);
4048 procmime_mimeinfo_free_all(&mimeinfo);
4051 #undef NEXT_PART_NOT_CHILD
4056 WAIT_FOR_INDENT_CHAR,
4057 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4060 /* return indent length, we allow:
4061 indent characters followed by indent characters or spaces/tabs,
4062 alphabets and numbers immediately followed by indent characters,
4063 and the repeating sequences of the above
4064 If quote ends with multiple spaces, only the first one is included. */
4065 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4066 const GtkTextIter *start, gint *len)
4068 GtkTextIter iter = *start;
4072 IndentState state = WAIT_FOR_INDENT_CHAR;
4075 gint alnum_count = 0;
4076 gint space_count = 0;
4079 if (prefs_common.quote_chars == NULL) {
4083 while (!gtk_text_iter_ends_line(&iter)) {
4084 wc = gtk_text_iter_get_char(&iter);
4085 if (g_unichar_iswide(wc))
4087 clen = g_unichar_to_utf8(wc, ch);
4091 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4092 is_space = g_unichar_isspace(wc);
4094 if (state == WAIT_FOR_INDENT_CHAR) {
4095 if (!is_indent && !g_unichar_isalnum(wc))
4098 quote_len += alnum_count + space_count + 1;
4099 alnum_count = space_count = 0;
4100 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4103 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4104 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4108 else if (is_indent) {
4109 quote_len += alnum_count + space_count + 1;
4110 alnum_count = space_count = 0;
4113 state = WAIT_FOR_INDENT_CHAR;
4117 gtk_text_iter_forward_char(&iter);
4120 if (quote_len > 0 && space_count > 0)
4126 if (quote_len > 0) {
4128 gtk_text_iter_forward_chars(&iter, quote_len);
4129 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4135 /* return >0 if the line is itemized */
4136 static int compose_itemized_length(GtkTextBuffer *buffer,
4137 const GtkTextIter *start)
4139 GtkTextIter iter = *start;
4144 if (gtk_text_iter_ends_line(&iter))
4149 wc = gtk_text_iter_get_char(&iter);
4150 if (!g_unichar_isspace(wc))
4152 gtk_text_iter_forward_char(&iter);
4153 if (gtk_text_iter_ends_line(&iter))
4157 clen = g_unichar_to_utf8(wc, ch);
4158 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4160 wc == 0x2022 || /* BULLET */
4161 wc == 0x2023 || /* TRIANGULAR BULLET */
4162 wc == 0x2043 || /* HYPHEN BULLET */
4163 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4164 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4165 wc == 0x2219 || /* BULLET OPERATOR */
4166 wc == 0x25d8 || /* INVERSE BULLET */
4167 wc == 0x25e6 || /* WHITE BULLET */
4168 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4169 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4170 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4171 wc == 0x29be || /* CIRCLED WHITE BULLET */
4172 wc == 0x29bf /* CIRCLED BULLET */
4176 gtk_text_iter_forward_char(&iter);
4177 if (gtk_text_iter_ends_line(&iter))
4179 wc = gtk_text_iter_get_char(&iter);
4180 if (g_unichar_isspace(wc)) {
4186 /* return the string at the start of the itemization */
4187 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4188 const GtkTextIter *start)
4190 GtkTextIter iter = *start;
4193 GString *item_chars = g_string_new("");
4196 if (gtk_text_iter_ends_line(&iter)) {
4197 g_string_free(item_chars, FALSE);
4203 wc = gtk_text_iter_get_char(&iter);
4204 if (!g_unichar_isspace(wc))
4206 gtk_text_iter_forward_char(&iter);
4207 if (gtk_text_iter_ends_line(&iter))
4209 g_string_append_unichar(item_chars, wc);
4212 str = item_chars->str;
4213 g_string_free(item_chars, FALSE);
4217 /* return the number of spaces at a line's start */
4218 static int compose_left_offset_length(GtkTextBuffer *buffer,
4219 const GtkTextIter *start)
4221 GtkTextIter iter = *start;
4224 if (gtk_text_iter_ends_line(&iter))
4228 wc = gtk_text_iter_get_char(&iter);
4229 if (!g_unichar_isspace(wc))
4232 gtk_text_iter_forward_char(&iter);
4233 if (gtk_text_iter_ends_line(&iter))
4237 gtk_text_iter_forward_char(&iter);
4238 if (gtk_text_iter_ends_line(&iter))
4243 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4244 const GtkTextIter *start,
4245 GtkTextIter *break_pos,
4249 GtkTextIter iter = *start, line_end = *start;
4250 PangoLogAttr *attrs;
4257 gboolean can_break = FALSE;
4258 gboolean do_break = FALSE;
4259 gboolean was_white = FALSE;
4260 gboolean prev_dont_break = FALSE;
4262 gtk_text_iter_forward_to_line_end(&line_end);
4263 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4264 len = g_utf8_strlen(str, -1);
4268 g_warning("compose_get_line_break_pos: len = 0!");
4272 /* g_print("breaking line: %d: %s (len = %d)\n",
4273 gtk_text_iter_get_line(&iter), str, len); */
4275 attrs = g_new(PangoLogAttr, len + 1);
4277 pango_default_break(str, -1, NULL, attrs, len + 1);
4281 /* skip quote and leading spaces */
4282 for (i = 0; *p != '\0' && i < len; i++) {
4285 wc = g_utf8_get_char(p);
4286 if (i >= quote_len && !g_unichar_isspace(wc))
4288 if (g_unichar_iswide(wc))
4290 else if (*p == '\t')
4294 p = g_utf8_next_char(p);
4297 for (; *p != '\0' && i < len; i++) {
4298 PangoLogAttr *attr = attrs + i;
4299 gunichar wc = g_utf8_get_char(p);
4302 /* attr->is_line_break will be false for some characters that
4303 * we want to break a line before, like '/' or ':', so we
4304 * also allow breaking on any non-wide character. The
4305 * mentioned pango attribute is still useful to decide on
4306 * line breaks when wide characters are involved. */
4307 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4308 && can_break && was_white && !prev_dont_break)
4311 was_white = attr->is_white;
4313 /* don't wrap URI */
4314 if ((uri_len = get_uri_len(p)) > 0) {
4316 if (pos > 0 && col > max_col) {
4326 if (g_unichar_iswide(wc)) {
4328 if (prev_dont_break && can_break && attr->is_line_break)
4330 } else if (*p == '\t')
4334 if (pos > 0 && col > max_col) {
4339 if (*p == '-' || *p == '/')
4340 prev_dont_break = TRUE;
4342 prev_dont_break = FALSE;
4344 p = g_utf8_next_char(p);
4348 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4353 *break_pos = *start;
4354 gtk_text_iter_set_line_offset(break_pos, pos);
4359 static gboolean compose_join_next_line(Compose *compose,
4360 GtkTextBuffer *buffer,
4362 const gchar *quote_str)
4364 GtkTextIter iter_ = *iter, cur, prev, next, end;
4365 PangoLogAttr attrs[3];
4367 gchar *next_quote_str;
4370 gboolean keep_cursor = FALSE;
4372 if (!gtk_text_iter_forward_line(&iter_) ||
4373 gtk_text_iter_ends_line(&iter_)) {
4376 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4378 if ((quote_str || next_quote_str) &&
4379 g_strcmp0(quote_str, next_quote_str) != 0) {
4380 g_free(next_quote_str);
4383 g_free(next_quote_str);
4386 if (quote_len > 0) {
4387 gtk_text_iter_forward_chars(&end, quote_len);
4388 if (gtk_text_iter_ends_line(&end)) {
4393 /* don't join itemized lines */
4394 if (compose_itemized_length(buffer, &end) > 0) {
4398 /* don't join signature separator */
4399 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4402 /* delete quote str */
4404 gtk_text_buffer_delete(buffer, &iter_, &end);
4406 /* don't join line breaks put by the user */
4408 gtk_text_iter_backward_char(&cur);
4409 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4410 gtk_text_iter_forward_char(&cur);
4414 gtk_text_iter_forward_char(&cur);
4415 /* delete linebreak and extra spaces */
4416 while (gtk_text_iter_backward_char(&cur)) {
4417 wc1 = gtk_text_iter_get_char(&cur);
4418 if (!g_unichar_isspace(wc1))
4423 while (!gtk_text_iter_ends_line(&cur)) {
4424 wc1 = gtk_text_iter_get_char(&cur);
4425 if (!g_unichar_isspace(wc1))
4427 gtk_text_iter_forward_char(&cur);
4430 if (!gtk_text_iter_equal(&prev, &next)) {
4433 mark = gtk_text_buffer_get_insert(buffer);
4434 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4435 if (gtk_text_iter_equal(&prev, &cur))
4437 gtk_text_buffer_delete(buffer, &prev, &next);
4441 /* insert space if required */
4442 gtk_text_iter_backward_char(&prev);
4443 wc1 = gtk_text_iter_get_char(&prev);
4444 wc2 = gtk_text_iter_get_char(&next);
4445 gtk_text_iter_forward_char(&next);
4446 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4447 pango_default_break(str, -1, NULL, attrs, 3);
4448 if (!attrs[1].is_line_break ||
4449 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4450 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4452 gtk_text_iter_backward_char(&iter_);
4453 gtk_text_buffer_place_cursor(buffer, &iter_);
4462 #define ADD_TXT_POS(bp_, ep_, pti_) \
4463 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4464 last = last->next; \
4465 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4466 last->next = NULL; \
4468 g_warning("alloc error scanning URIs"); \
4471 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4473 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4474 GtkTextBuffer *buffer;
4475 GtkTextIter iter, break_pos, end_of_line;
4476 gchar *quote_str = NULL;
4478 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4479 gboolean prev_autowrap = compose->autowrap;
4480 gint startq_offset = -1, noq_offset = -1;
4481 gint uri_start = -1, uri_stop = -1;
4482 gint nouri_start = -1, nouri_stop = -1;
4483 gint num_blocks = 0;
4484 gint quotelevel = -1;
4485 gboolean modified = force;
4486 gboolean removed = FALSE;
4487 gboolean modified_before_remove = FALSE;
4489 gboolean start = TRUE;
4490 gint itemized_len = 0, rem_item_len = 0;
4491 gchar *itemized_chars = NULL;
4492 gboolean item_continuation = FALSE;
4497 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4501 compose->autowrap = FALSE;
4503 buffer = gtk_text_view_get_buffer(text);
4504 undo_wrapping(compose->undostruct, TRUE);
4509 mark = gtk_text_buffer_get_insert(buffer);
4510 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4514 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4515 if (gtk_text_iter_ends_line(&iter)) {
4516 while (gtk_text_iter_ends_line(&iter) &&
4517 gtk_text_iter_forward_line(&iter))
4520 while (gtk_text_iter_backward_line(&iter)) {
4521 if (gtk_text_iter_ends_line(&iter)) {
4522 gtk_text_iter_forward_line(&iter);
4528 /* move to line start */
4529 gtk_text_iter_set_line_offset(&iter, 0);
4532 itemized_len = compose_itemized_length(buffer, &iter);
4534 if (!itemized_len) {
4535 itemized_len = compose_left_offset_length(buffer, &iter);
4536 item_continuation = TRUE;
4540 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4542 /* go until paragraph end (empty line) */
4543 while (start || !gtk_text_iter_ends_line(&iter)) {
4544 gchar *scanpos = NULL;
4545 /* parse table - in order of priority */
4547 const gchar *needle; /* token */
4549 /* token search function */
4550 gchar *(*search) (const gchar *haystack,
4551 const gchar *needle);
4552 /* part parsing function */
4553 gboolean (*parse) (const gchar *start,
4554 const gchar *scanpos,
4558 /* part to URI function */
4559 gchar *(*build_uri) (const gchar *bp,
4563 static struct table parser[] = {
4564 {"http://", strcasestr, get_uri_part, make_uri_string},
4565 {"https://", strcasestr, get_uri_part, make_uri_string},
4566 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4567 {"ftps://", strcasestr, get_uri_part, make_uri_string},
4568 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4569 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4570 {"www.", strcasestr, get_uri_part, make_http_string},
4571 {"webcal://",strcasestr, get_uri_part, make_uri_string},
4572 {"webcals://",strcasestr, get_uri_part, make_uri_string},
4573 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4574 {"@", strcasestr, get_email_part, make_email_string}
4576 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4577 gint last_index = PARSE_ELEMS;
4579 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4583 if (!prev_autowrap && num_blocks == 0) {
4585 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4586 G_CALLBACK(text_inserted),
4589 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4592 uri_start = uri_stop = -1;
4594 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4597 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4598 if (startq_offset == -1)
4599 startq_offset = gtk_text_iter_get_offset(&iter);
4600 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4601 if (quotelevel > 2) {
4602 /* recycle colors */
4603 if (prefs_common.recycle_quote_colors)
4612 if (startq_offset == -1)
4613 noq_offset = gtk_text_iter_get_offset(&iter);
4617 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4620 if (gtk_text_iter_ends_line(&iter)) {
4622 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4623 prefs_common.linewrap_len,
4625 GtkTextIter prev, next, cur;
4626 if (prev_autowrap != FALSE || force) {
4627 compose->automatic_break = TRUE;
4629 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4630 compose->automatic_break = FALSE;
4631 if (itemized_len && compose->autoindent) {
4632 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4633 if (!item_continuation)
4634 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4636 } else if (quote_str && wrap_quote) {
4637 compose->automatic_break = TRUE;
4639 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4640 compose->automatic_break = FALSE;
4641 if (itemized_len && compose->autoindent) {
4642 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4643 if (!item_continuation)
4644 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4648 /* remove trailing spaces */
4650 rem_item_len = itemized_len;
4651 while (compose->autoindent && rem_item_len-- > 0)
4652 gtk_text_iter_backward_char(&cur);
4653 gtk_text_iter_backward_char(&cur);
4656 while (!gtk_text_iter_starts_line(&cur)) {
4659 gtk_text_iter_backward_char(&cur);
4660 wc = gtk_text_iter_get_char(&cur);
4661 if (!g_unichar_isspace(wc))
4665 if (!gtk_text_iter_equal(&prev, &next)) {
4666 gtk_text_buffer_delete(buffer, &prev, &next);
4668 gtk_text_iter_forward_char(&break_pos);
4672 gtk_text_buffer_insert(buffer, &break_pos,
4676 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4678 /* move iter to current line start */
4679 gtk_text_iter_set_line_offset(&iter, 0);
4686 /* move iter to next line start */
4692 if (!prev_autowrap && num_blocks > 0) {
4694 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4695 G_CALLBACK(text_inserted),
4699 while (!gtk_text_iter_ends_line(&end_of_line)) {
4700 gtk_text_iter_forward_char(&end_of_line);
4702 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4704 nouri_start = gtk_text_iter_get_offset(&iter);
4705 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4707 walk_pos = gtk_text_iter_get_offset(&iter);
4708 /* FIXME: this looks phony. scanning for anything in the parse table */
4709 for (n = 0; n < PARSE_ELEMS; n++) {
4712 tmp = parser[n].search(walk, parser[n].needle);
4714 if (scanpos == NULL || tmp < scanpos) {
4723 /* check if URI can be parsed */
4724 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4725 (const gchar **)&ep, FALSE)
4726 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4730 strlen(parser[last_index].needle);
4733 uri_start = walk_pos + (bp - o_walk);
4734 uri_stop = walk_pos + (ep - o_walk);
4738 gtk_text_iter_forward_line(&iter);
4741 if (startq_offset != -1) {
4742 GtkTextIter startquote, endquote;
4743 gtk_text_buffer_get_iter_at_offset(
4744 buffer, &startquote, startq_offset);
4747 switch (quotelevel) {
4749 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4750 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4751 gtk_text_buffer_apply_tag_by_name(
4752 buffer, "quote0", &startquote, &endquote);
4753 gtk_text_buffer_remove_tag_by_name(
4754 buffer, "quote1", &startquote, &endquote);
4755 gtk_text_buffer_remove_tag_by_name(
4756 buffer, "quote2", &startquote, &endquote);
4761 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4762 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4763 gtk_text_buffer_apply_tag_by_name(
4764 buffer, "quote1", &startquote, &endquote);
4765 gtk_text_buffer_remove_tag_by_name(
4766 buffer, "quote0", &startquote, &endquote);
4767 gtk_text_buffer_remove_tag_by_name(
4768 buffer, "quote2", &startquote, &endquote);
4773 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4774 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4775 gtk_text_buffer_apply_tag_by_name(
4776 buffer, "quote2", &startquote, &endquote);
4777 gtk_text_buffer_remove_tag_by_name(
4778 buffer, "quote0", &startquote, &endquote);
4779 gtk_text_buffer_remove_tag_by_name(
4780 buffer, "quote1", &startquote, &endquote);
4786 } else if (noq_offset != -1) {
4787 GtkTextIter startnoquote, endnoquote;
4788 gtk_text_buffer_get_iter_at_offset(
4789 buffer, &startnoquote, noq_offset);
4792 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4793 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4794 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4795 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4796 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4797 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4798 gtk_text_buffer_remove_tag_by_name(
4799 buffer, "quote0", &startnoquote, &endnoquote);
4800 gtk_text_buffer_remove_tag_by_name(
4801 buffer, "quote1", &startnoquote, &endnoquote);
4802 gtk_text_buffer_remove_tag_by_name(
4803 buffer, "quote2", &startnoquote, &endnoquote);
4809 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4810 GtkTextIter nouri_start_iter, nouri_end_iter;
4811 gtk_text_buffer_get_iter_at_offset(
4812 buffer, &nouri_start_iter, nouri_start);
4813 gtk_text_buffer_get_iter_at_offset(
4814 buffer, &nouri_end_iter, nouri_stop);
4815 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4816 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4817 gtk_text_buffer_remove_tag_by_name(
4818 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4819 modified_before_remove = modified;
4824 if (uri_start >= 0 && uri_stop > 0) {
4825 GtkTextIter uri_start_iter, uri_end_iter, back;
4826 gtk_text_buffer_get_iter_at_offset(
4827 buffer, &uri_start_iter, uri_start);
4828 gtk_text_buffer_get_iter_at_offset(
4829 buffer, &uri_end_iter, uri_stop);
4830 back = uri_end_iter;
4831 gtk_text_iter_backward_char(&back);
4832 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4833 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4834 gtk_text_buffer_apply_tag_by_name(
4835 buffer, "link", &uri_start_iter, &uri_end_iter);
4837 if (removed && !modified_before_remove) {
4843 /* debug_print("not modified, out after %d lines\n", lines); */
4847 /* debug_print("modified, out after %d lines\n", lines); */
4849 g_free(itemized_chars);
4852 undo_wrapping(compose->undostruct, FALSE);
4853 compose->autowrap = prev_autowrap;
4858 void compose_action_cb(void *data)
4860 Compose *compose = (Compose *)data;
4861 compose_wrap_all(compose);
4864 static void compose_wrap_all(Compose *compose)
4866 compose_wrap_all_full(compose, FALSE);
4869 static void compose_wrap_all_full(Compose *compose, gboolean force)
4871 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4872 GtkTextBuffer *buffer;
4874 gboolean modified = TRUE;
4876 buffer = gtk_text_view_get_buffer(text);
4878 gtk_text_buffer_get_start_iter(buffer, &iter);
4880 undo_wrapping(compose->undostruct, TRUE);
4882 while (!gtk_text_iter_is_end(&iter) && modified)
4883 modified = compose_beautify_paragraph(compose, &iter, force);
4885 undo_wrapping(compose->undostruct, FALSE);
4889 static void compose_set_title(Compose *compose)
4895 edited = compose->modified ? _(" [Edited]") : "";
4897 subject = gtk_editable_get_chars(
4898 GTK_EDITABLE(compose->subject_entry), 0, -1);
4900 #ifndef GENERIC_UMPC
4901 if (subject && strlen(subject))
4902 str = g_strdup_printf(_("%s - Compose message%s"),
4905 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4907 str = g_strdup(_("Compose message"));
4910 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4916 * compose_current_mail_account:
4918 * Find a current mail account (the currently selected account, or the
4919 * default account, if a news account is currently selected). If a
4920 * mail account cannot be found, display an error message.
4922 * Return value: Mail account, or NULL if not found.
4924 static PrefsAccount *
4925 compose_current_mail_account(void)
4929 if (cur_account && cur_account->protocol != A_NNTP)
4932 ac = account_get_default();
4933 if (!ac || ac->protocol == A_NNTP) {
4934 alertpanel_error(_("Account for sending mail is not specified.\n"
4935 "Please select a mail account before sending."));
4942 #define QUOTE_IF_REQUIRED(out, str) \
4944 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4948 len = strlen(str) + 3; \
4949 if ((__tmp = alloca(len)) == NULL) { \
4950 g_warning("can't allocate memory"); \
4951 g_string_free(header, TRUE); \
4954 g_snprintf(__tmp, len, "\"%s\"", str); \
4959 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4960 g_warning("can't allocate memory"); \
4961 g_string_free(header, TRUE); \
4964 strcpy(__tmp, str); \
4970 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4972 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4976 len = strlen(str) + 3; \
4977 if ((__tmp = alloca(len)) == NULL) { \
4978 g_warning("can't allocate memory"); \
4981 g_snprintf(__tmp, len, "\"%s\"", str); \
4986 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4987 g_warning("can't allocate memory"); \
4990 strcpy(__tmp, str); \
4996 static void compose_select_account(Compose *compose, PrefsAccount *account,
4999 gchar *from = NULL, *header = NULL;
5000 ComposeHeaderEntry *header_entry;
5003 cm_return_if_fail(account != NULL);
5005 compose->account = account;
5006 if (account->name && *account->name) {
5008 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
5009 qbuf = escape_internal_quotes(buf, '"');
5010 from = g_strdup_printf("%s <%s>",
5011 qbuf, account->address);
5014 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5016 from = g_strdup_printf("<%s>",
5018 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5023 compose_set_title(compose);
5025 compose_activate_privacy_system(compose, account, FALSE);
5027 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5028 compose->mode != COMPOSE_REDIRECT)
5029 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5031 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5032 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5033 compose->mode != COMPOSE_REDIRECT)
5034 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5036 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5038 if (!init && compose->mode != COMPOSE_REDIRECT) {
5039 undo_block(compose->undostruct);
5040 compose_insert_sig(compose, TRUE);
5041 undo_unblock(compose->undostruct);
5044 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5045 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5046 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5047 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5049 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5050 if (account->protocol == A_NNTP) {
5051 if (!strcmp(header, _("To:")))
5052 combobox_select_by_text(
5053 GTK_COMBO_BOX(header_entry->combo),
5056 if (!strcmp(header, _("Newsgroups:")))
5057 combobox_select_by_text(
5058 GTK_COMBO_BOX(header_entry->combo),
5066 /* use account's dict info if set */
5067 if (compose->gtkaspell) {
5068 if (account->enable_default_dictionary)
5069 gtkaspell_change_dict(compose->gtkaspell,
5070 account->default_dictionary, FALSE);
5071 if (account->enable_default_alt_dictionary)
5072 gtkaspell_change_alt_dict(compose->gtkaspell,
5073 account->default_alt_dictionary);
5074 if (account->enable_default_dictionary
5075 || account->enable_default_alt_dictionary)
5076 compose_spell_menu_changed(compose);
5081 gboolean compose_check_for_valid_recipient(Compose *compose) {
5082 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5083 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5084 gboolean recipient_found = FALSE;
5088 /* free to and newsgroup list */
5089 slist_free_strings_full(compose->to_list);
5090 compose->to_list = NULL;
5092 slist_free_strings_full(compose->newsgroup_list);
5093 compose->newsgroup_list = NULL;
5095 /* search header entries for to and newsgroup entries */
5096 for (list = compose->header_list; list; list = list->next) {
5099 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5100 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5103 if (entry[0] != '\0') {
5104 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5105 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5106 compose->to_list = address_list_append(compose->to_list, entry);
5107 recipient_found = TRUE;
5110 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5111 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5112 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5113 recipient_found = TRUE;
5120 return recipient_found;
5123 static gboolean compose_check_for_set_recipients(Compose *compose)
5125 if (compose->account->set_autocc && compose->account->auto_cc) {
5126 gboolean found_other = FALSE;
5128 /* search header entries for to and newsgroup entries */
5129 for (list = compose->header_list; list; list = list->next) {
5132 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5133 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5136 if (strcmp(entry, compose->account->auto_cc)
5137 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5148 if (compose->batch) {
5149 gtk_widget_show_all(compose->window);
5151 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5152 prefs_common_translated_header_name("Cc"));
5153 aval = alertpanel(_("Send"),
5155 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5157 if (aval != G_ALERTALTERNATE)
5161 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5162 gboolean found_other = FALSE;
5164 /* search header entries for to and newsgroup entries */
5165 for (list = compose->header_list; list; list = list->next) {
5168 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5169 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5172 if (strcmp(entry, compose->account->auto_bcc)
5173 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5185 if (compose->batch) {
5186 gtk_widget_show_all(compose->window);
5188 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5189 prefs_common_translated_header_name("Bcc"));
5190 aval = alertpanel(_("Send"),
5192 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5194 if (aval != G_ALERTALTERNATE)
5201 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5205 if (compose_check_for_valid_recipient(compose) == FALSE) {
5206 if (compose->batch) {
5207 gtk_widget_show_all(compose->window);
5209 alertpanel_error(_("Recipient is not specified."));
5213 if (compose_check_for_set_recipients(compose) == FALSE) {
5217 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5218 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5219 if (*str == '\0' && check_everything == TRUE &&
5220 compose->mode != COMPOSE_REDIRECT) {
5224 message = g_strdup_printf(_("Subject is empty. %s"),
5225 compose->sending?_("Send it anyway?"):
5226 _("Queue it anyway?"));
5228 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5229 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5230 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5232 if (aval & G_ALERTDISABLE) {
5233 aval &= ~G_ALERTDISABLE;
5234 prefs_common.warn_empty_subj = FALSE;
5236 if (aval != G_ALERTALTERNATE)
5241 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5242 && check_everything == TRUE) {
5246 /* count To and Cc recipients */
5247 for (list = compose->header_list; list; list = list->next) {
5251 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5252 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5255 if ((entry[0] != '\0') &&
5256 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5257 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5263 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5267 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5268 compose->sending?_("Send it anyway?"):
5269 _("Queue it anyway?"));
5271 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5272 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5273 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5275 if (aval & G_ALERTDISABLE) {
5276 aval &= ~G_ALERTDISABLE;
5277 prefs_common.warn_sending_many_recipients_num = 0;
5279 if (aval != G_ALERTALTERNATE)
5284 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5290 static void _display_queue_error(ComposeQueueResult val)
5293 case COMPOSE_QUEUE_SUCCESS:
5295 case COMPOSE_QUEUE_ERROR_NO_MSG:
5296 alertpanel_error(_("Could not queue message."));
5298 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5299 alertpanel_error(_("Could not queue message:\n\n%s."),
5302 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5303 alertpanel_error(_("Could not queue message for sending:\n\n"
5304 "Signature failed: %s"),
5305 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5307 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5308 alertpanel_error(_("Could not queue message for sending:\n\n"
5309 "Encryption failed: %s"),
5310 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5312 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5313 alertpanel_error(_("Could not queue message for sending:\n\n"
5314 "Charset conversion failed."));
5316 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5317 alertpanel_error(_("Could not queue message for sending:\n\n"
5318 "Couldn't get recipient encryption key."));
5320 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5321 debug_print("signing cancelled\n");
5324 /* unhandled error */
5325 debug_print("oops, unhandled compose_queue() return value %d\n",
5331 gint compose_send(Compose *compose)
5334 FolderItem *folder = NULL;
5335 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5336 gchar *msgpath = NULL;
5337 gboolean discard_window = FALSE;
5338 gchar *errstr = NULL;
5339 gchar *tmsgid = NULL;
5340 MainWindow *mainwin = mainwindow_get_mainwindow();
5341 gboolean queued_removed = FALSE;
5343 if (prefs_common.send_dialog_invisible
5344 || compose->batch == TRUE)
5345 discard_window = TRUE;
5347 compose_allow_user_actions (compose, FALSE);
5348 compose->sending = TRUE;
5350 if (compose_check_entries(compose, TRUE) == FALSE) {
5351 if (compose->batch) {
5352 gtk_widget_show_all(compose->window);
5358 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5360 if (val != COMPOSE_QUEUE_SUCCESS) {
5361 if (compose->batch) {
5362 gtk_widget_show_all(compose->window);
5365 _display_queue_error(val);
5370 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5371 if (discard_window) {
5372 compose->sending = FALSE;
5373 compose_close(compose);
5374 /* No more compose access in the normal codepath
5375 * after this point! */
5380 alertpanel_error(_("The message was queued but could not be "
5381 "sent.\nUse \"Send queued messages\" from "
5382 "the main window to retry."));
5383 if (!discard_window) {
5390 if (msgpath == NULL) {
5391 msgpath = folder_item_fetch_msg(folder, msgnum);
5392 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5395 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5396 claws_unlink(msgpath);
5399 if (!discard_window) {
5401 if (!queued_removed)
5402 folder_item_remove_msg(folder, msgnum);
5403 folder_item_scan(folder);
5405 /* make sure we delete that */
5406 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5408 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5409 folder_item_remove_msg(folder, tmp->msgnum);
5410 procmsg_msginfo_free(&tmp);
5417 if (!queued_removed)
5418 folder_item_remove_msg(folder, msgnum);
5419 folder_item_scan(folder);
5421 /* make sure we delete that */
5422 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5424 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5425 folder_item_remove_msg(folder, tmp->msgnum);
5426 procmsg_msginfo_free(&tmp);
5429 if (!discard_window) {
5430 compose->sending = FALSE;
5431 compose_allow_user_actions (compose, TRUE);
5432 compose_close(compose);
5436 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5437 "the main window to retry."), errstr);
5440 alertpanel_error_log(_("The message was queued but could not be "
5441 "sent.\nUse \"Send queued messages\" from "
5442 "the main window to retry."));
5444 if (!discard_window) {
5453 toolbar_main_set_sensitive(mainwin);
5454 main_window_set_menu_sensitive(mainwin);
5460 compose_allow_user_actions (compose, TRUE);
5461 compose->sending = FALSE;
5462 compose->modified = TRUE;
5463 toolbar_main_set_sensitive(mainwin);
5464 main_window_set_menu_sensitive(mainwin);
5469 static gboolean compose_use_attach(Compose *compose)
5471 GtkTreeModel *model = gtk_tree_view_get_model
5472 (GTK_TREE_VIEW(compose->attach_clist));
5473 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5476 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5479 gchar buf[BUFFSIZE];
5481 gboolean first_to_address;
5482 gboolean first_cc_address;
5484 ComposeHeaderEntry *headerentry;
5485 const gchar *headerentryname;
5486 const gchar *cc_hdr;
5487 const gchar *to_hdr;
5488 gboolean err = FALSE;
5490 debug_print("Writing redirect header\n");
5492 cc_hdr = prefs_common_translated_header_name("Cc:");
5493 to_hdr = prefs_common_translated_header_name("To:");
5495 first_to_address = TRUE;
5496 for (list = compose->header_list; list; list = list->next) {
5497 headerentry = ((ComposeHeaderEntry *)list->data);
5498 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5500 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5501 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5502 Xstrdup_a(str, entstr, return -1);
5504 if (str[0] != '\0') {
5505 compose_convert_header
5506 (compose, buf, sizeof(buf), str,
5507 strlen("Resent-To") + 2, TRUE);
5509 if (first_to_address) {
5510 err |= (fprintf(fp, "Resent-To: ") < 0);
5511 first_to_address = FALSE;
5513 err |= (fprintf(fp, ",") < 0);
5515 err |= (fprintf(fp, "%s", buf) < 0);
5519 if (!first_to_address) {
5520 err |= (fprintf(fp, "\n") < 0);
5523 first_cc_address = TRUE;
5524 for (list = compose->header_list; list; list = list->next) {
5525 headerentry = ((ComposeHeaderEntry *)list->data);
5526 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5528 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5529 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5530 Xstrdup_a(str, strg, return -1);
5532 if (str[0] != '\0') {
5533 compose_convert_header
5534 (compose, buf, sizeof(buf), str,
5535 strlen("Resent-Cc") + 2, TRUE);
5537 if (first_cc_address) {
5538 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5539 first_cc_address = FALSE;
5541 err |= (fprintf(fp, ",") < 0);
5543 err |= (fprintf(fp, "%s", buf) < 0);
5547 if (!first_cc_address) {
5548 err |= (fprintf(fp, "\n") < 0);
5551 return (err ? -1:0);
5554 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5556 gchar date[RFC822_DATE_BUFFSIZE];
5557 gchar buf[BUFFSIZE];
5559 const gchar *entstr;
5560 /* struct utsname utsbuf; */
5561 gboolean err = FALSE;
5563 cm_return_val_if_fail(fp != NULL, -1);
5564 cm_return_val_if_fail(compose->account != NULL, -1);
5565 cm_return_val_if_fail(compose->account->address != NULL, -1);
5568 if (prefs_common.hide_timezone)
5569 get_rfc822_date_hide_tz(date, sizeof(date));
5571 get_rfc822_date(date, sizeof(date));
5572 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5575 if (compose->account->name && *compose->account->name) {
5576 compose_convert_header
5577 (compose, buf, sizeof(buf), compose->account->name,
5578 strlen("From: "), TRUE);
5579 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5580 buf, compose->account->address) < 0);
5582 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5585 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5586 if (*entstr != '\0') {
5587 Xstrdup_a(str, entstr, return -1);
5590 compose_convert_header(compose, buf, sizeof(buf), str,
5591 strlen("Subject: "), FALSE);
5592 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5596 /* Resent-Message-ID */
5597 if (compose->account->gen_msgid) {
5598 gchar *addr = prefs_account_generate_msgid(compose->account);
5599 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5601 g_free(compose->msgid);
5602 compose->msgid = addr;
5604 compose->msgid = NULL;
5607 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5610 /* separator between header and body */
5611 err |= (claws_fputs("\n", fp) == EOF);
5613 return (err ? -1:0);
5616 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5621 gchar rewrite_buf[BUFFSIZE];
5623 gboolean skip = FALSE;
5624 gboolean err = FALSE;
5625 gchar *not_included[]={
5626 "Return-Path:", "Delivered-To:", "Received:",
5627 "Subject:", "X-UIDL:", "AF:",
5628 "NF:", "PS:", "SRH:",
5629 "SFN:", "DSR:", "MID:",
5630 "CFG:", "PT:", "S:",
5631 "RQ:", "SSV:", "NSV:",
5632 "SSH:", "R:", "MAID:",
5633 "NAID:", "RMID:", "FMID:",
5634 "SCF:", "RRCPT:", "NG:",
5635 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5636 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5637 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5638 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5639 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5644 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5645 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5649 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5651 for (i = 0; not_included[i] != NULL; i++) {
5652 if (g_ascii_strncasecmp(buf, not_included[i],
5653 strlen(not_included[i])) == 0) {
5663 if (claws_fputs(buf, fdest) == -1) {
5669 if (!prefs_common.redirect_keep_from) {
5670 if (g_ascii_strncasecmp(buf, "From:",
5671 strlen("From:")) == 0) {
5672 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5673 if (compose->account->name
5674 && *compose->account->name) {
5675 gchar buffer[BUFFSIZE];
5677 compose_convert_header
5678 (compose, buffer, sizeof(buffer),
5679 compose->account->name,
5682 err |= (fprintf(fdest, "%s <%s>",
5684 compose->account->address) < 0);
5686 err |= (fprintf(fdest, "%s",
5687 compose->account->address) < 0);
5688 err |= (claws_fputs(")", fdest) == EOF);
5694 if (claws_fputs("\n", fdest) == -1)
5701 if (compose_redirect_write_headers(compose, fdest))
5704 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5705 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5719 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5721 GtkTextBuffer *buffer;
5722 GtkTextIter start, end, tmp;
5723 gchar *chars, *tmp_enc_file = NULL, *content;
5725 const gchar *out_codeset;
5726 EncodingType encoding = ENC_UNKNOWN;
5727 MimeInfo *mimemsg, *mimetext;
5729 const gchar *src_codeset = CS_INTERNAL;
5730 gchar *from_addr = NULL;
5731 gchar *from_name = NULL;
5734 if (action == COMPOSE_WRITE_FOR_SEND) {
5735 attach_parts = TRUE;
5737 /* We're sending the message, generate a Message-ID
5739 if (compose->msgid == NULL &&
5740 compose->account->gen_msgid) {
5741 compose->msgid = prefs_account_generate_msgid(compose->account);
5745 /* create message MimeInfo */
5746 mimemsg = procmime_mimeinfo_new();
5747 mimemsg->type = MIMETYPE_MESSAGE;
5748 mimemsg->subtype = g_strdup("rfc822");
5749 mimemsg->content = MIMECONTENT_MEM;
5750 mimemsg->tmp = TRUE; /* must free content later */
5751 mimemsg->data.mem = compose_get_header(compose);
5753 /* Create text part MimeInfo */
5754 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5755 gtk_text_buffer_get_end_iter(buffer, &end);
5758 /* We make sure that there is a newline at the end. */
5759 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5760 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5761 if (*chars != '\n') {
5762 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5767 /* get all composed text */
5768 gtk_text_buffer_get_start_iter(buffer, &start);
5769 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5771 out_codeset = conv_get_charset_str(compose->out_encoding);
5773 if (!out_codeset && is_ascii_str(chars)) {
5774 out_codeset = CS_US_ASCII;
5775 } else if (prefs_common.outgoing_fallback_to_ascii &&
5776 is_ascii_str(chars)) {
5777 out_codeset = CS_US_ASCII;
5778 encoding = ENC_7BIT;
5782 gchar *test_conv_global_out = NULL;
5783 gchar *test_conv_reply = NULL;
5785 /* automatic mode. be automatic. */
5786 codeconv_set_strict(TRUE);
5788 out_codeset = conv_get_outgoing_charset_str();
5790 debug_print("trying to convert to %s\n", out_codeset);
5791 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5794 if (!test_conv_global_out && compose->orig_charset
5795 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5796 out_codeset = compose->orig_charset;
5797 debug_print("failure; trying to convert to %s\n", out_codeset);
5798 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5801 if (!test_conv_global_out && !test_conv_reply) {
5803 out_codeset = CS_INTERNAL;
5804 debug_print("failure; finally using %s\n", out_codeset);
5806 g_free(test_conv_global_out);
5807 g_free(test_conv_reply);
5808 codeconv_set_strict(FALSE);
5811 if (encoding == ENC_UNKNOWN) {
5812 if (prefs_common.encoding_method == CTE_BASE64)
5813 encoding = ENC_BASE64;
5814 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5815 encoding = ENC_QUOTED_PRINTABLE;
5816 else if (prefs_common.encoding_method == CTE_8BIT)
5817 encoding = ENC_8BIT;
5819 encoding = procmime_get_encoding_for_charset(out_codeset);
5822 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5823 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5825 if (action == COMPOSE_WRITE_FOR_SEND) {
5826 codeconv_set_strict(TRUE);
5827 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5828 codeconv_set_strict(FALSE);
5833 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5834 "to the specified %s charset.\n"
5835 "Send it as %s?"), out_codeset, src_codeset);
5836 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5837 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5841 if (aval != G_ALERTALTERNATE) {
5843 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5846 out_codeset = src_codeset;
5852 out_codeset = src_codeset;
5857 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5858 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5859 strstr(buf, "\nFrom ") != NULL) {
5860 encoding = ENC_QUOTED_PRINTABLE;
5864 mimetext = procmime_mimeinfo_new();
5865 mimetext->content = MIMECONTENT_MEM;
5866 mimetext->tmp = TRUE; /* must free content later */
5867 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5868 * and free the data, which we need later. */
5869 mimetext->data.mem = g_strdup(buf);
5870 mimetext->type = MIMETYPE_TEXT;
5871 mimetext->subtype = g_strdup("plain");
5872 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5873 g_strdup(out_codeset));
5875 /* protect trailing spaces when signing message */
5876 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5877 privacy_system_can_sign(compose->privacy_system)) {
5878 encoding = ENC_QUOTED_PRINTABLE;
5881 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n",
5882 strlen(buf), out_codeset, encoding);
5884 /* check for line length limit */
5885 if (action == COMPOSE_WRITE_FOR_SEND &&
5886 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5887 check_line_length(buf, 1000, &line) < 0) {
5890 msg = g_strdup_printf
5891 (_("Line %d exceeds the line length limit (998 bytes).\n"
5892 "The contents of the message might be broken on the way to the delivery.\n"
5894 "Send it anyway?"), line + 1);
5895 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5898 if (aval != G_ALERTALTERNATE) {
5900 return COMPOSE_QUEUE_ERROR_NO_MSG;
5904 if (encoding != ENC_UNKNOWN)
5905 procmime_encode_content(mimetext, encoding);
5907 /* append attachment parts */
5908 if (compose_use_attach(compose) && attach_parts) {
5909 MimeInfo *mimempart;
5910 gchar *boundary = NULL;
5911 mimempart = procmime_mimeinfo_new();
5912 mimempart->content = MIMECONTENT_EMPTY;
5913 mimempart->type = MIMETYPE_MULTIPART;
5914 mimempart->subtype = g_strdup("mixed");
5918 boundary = generate_mime_boundary(NULL);
5919 } while (strstr(buf, boundary) != NULL);
5921 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5924 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5926 g_node_append(mimempart->node, mimetext->node);
5927 g_node_append(mimemsg->node, mimempart->node);
5929 if (compose_add_attachments(compose, mimempart) < 0)
5930 return COMPOSE_QUEUE_ERROR_NO_MSG;
5932 g_node_append(mimemsg->node, mimetext->node);
5936 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5937 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5938 /* extract name and address */
5939 if (strstr(spec, " <") && strstr(spec, ">")) {
5940 from_addr = g_strdup(strrchr(spec, '<')+1);
5941 *(strrchr(from_addr, '>')) = '\0';
5942 from_name = g_strdup(spec);
5943 *(strrchr(from_name, '<')) = '\0';
5950 /* sign message if sending */
5951 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5952 privacy_system_can_sign(compose->privacy_system))
5953 if (!privacy_sign(compose->privacy_system, mimemsg,
5954 compose->account, from_addr)) {
5957 if (!privacy_peek_error())
5958 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5960 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5965 if (compose->use_encryption) {
5966 if (compose->encdata != NULL &&
5967 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5969 /* First, write an unencrypted copy and save it to outbox, if
5970 * user wants that. */
5971 if (compose->account->save_encrypted_as_clear_text) {
5972 debug_print("saving sent message unencrypted...\n");
5973 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5975 claws_fclose(tmpfp);
5977 /* fp now points to a file with headers written,
5978 * let's make a copy. */
5980 content = file_read_stream_to_str(fp);
5982 str_write_to_file(content, tmp_enc_file, TRUE);
5985 /* Now write the unencrypted body. */
5986 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5987 procmime_write_mimeinfo(mimemsg, tmpfp);
5988 claws_fclose(tmpfp);
5990 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5992 outbox = folder_get_default_outbox();
5994 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5995 claws_unlink(tmp_enc_file);
5997 g_warning("can't open file '%s'", tmp_enc_file);
6000 g_warning("couldn't get tempfile");
6003 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
6004 debug_print("Couldn't encrypt mime structure: %s.\n",
6005 privacy_get_error());
6007 g_free(tmp_enc_file);
6008 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
6013 g_free(tmp_enc_file);
6015 procmime_write_mimeinfo(mimemsg, fp);
6017 procmime_mimeinfo_free_all(&mimemsg);
6022 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
6024 GtkTextBuffer *buffer;
6025 GtkTextIter start, end;
6030 if ((fp = claws_fopen(file, "wb")) == NULL) {
6031 FILE_OP_ERROR(file, "claws_fopen");
6035 /* chmod for security */
6036 if (change_file_mode_rw(fp, file) < 0) {
6037 FILE_OP_ERROR(file, "chmod");
6038 g_warning("can't change file mode");
6041 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6042 gtk_text_buffer_get_start_iter(buffer, &start);
6043 gtk_text_buffer_get_end_iter(buffer, &end);
6044 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6046 chars = conv_codeset_strdup
6047 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6056 len = strlen(chars);
6057 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6058 FILE_OP_ERROR(file, "claws_fwrite");
6067 if (claws_safe_fclose(fp) == EOF) {
6068 FILE_OP_ERROR(file, "claws_fclose");
6075 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6078 MsgInfo *msginfo = compose->targetinfo;
6080 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6081 if (!msginfo) return -1;
6083 if (!force && MSG_IS_LOCKED(msginfo->flags))
6086 item = msginfo->folder;
6087 cm_return_val_if_fail(item != NULL, -1);
6089 if (procmsg_msg_exist(msginfo) &&
6090 (folder_has_parent_of_type(item, F_QUEUE) ||
6091 folder_has_parent_of_type(item, F_DRAFT)
6092 || msginfo == compose->autosaved_draft)) {
6093 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6094 g_warning("can't remove the old message");
6097 debug_print("removed reedit target %d\n", msginfo->msgnum);
6104 static void compose_remove_draft(Compose *compose)
6107 MsgInfo *msginfo = compose->targetinfo;
6108 drafts = account_get_special_folder(compose->account, F_DRAFT);
6110 if (procmsg_msg_exist(msginfo)) {
6111 folder_item_remove_msg(drafts, msginfo->msgnum);
6116 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6117 gboolean remove_reedit_target)
6119 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6122 static gboolean compose_warn_encryption(Compose *compose)
6124 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6125 AlertValue val = G_ALERTALTERNATE;
6127 if (warning == NULL)
6130 val = alertpanel_full(_("Encryption warning"), warning,
6131 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6132 TRUE, NULL, ALERT_WARNING);
6133 if (val & G_ALERTDISABLE) {
6134 val &= ~G_ALERTDISABLE;
6135 if (val == G_ALERTALTERNATE)
6136 privacy_inhibit_encrypt_warning(compose->privacy_system,
6140 if (val == G_ALERTALTERNATE) {
6147 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6148 gchar **msgpath, gboolean perform_checks,
6149 gboolean remove_reedit_target)
6156 PrefsAccount *mailac = NULL, *newsac = NULL;
6157 gboolean err = FALSE;
6159 debug_print("queueing message...\n");
6160 cm_return_val_if_fail(compose->account != NULL, -1);
6162 if (compose_check_entries(compose, perform_checks) == FALSE) {
6163 if (compose->batch) {
6164 gtk_widget_show_all(compose->window);
6166 return COMPOSE_QUEUE_ERROR_NO_MSG;
6169 if (!compose->to_list && !compose->newsgroup_list) {
6170 g_warning("can't get recipient list");
6171 return COMPOSE_QUEUE_ERROR_NO_MSG;
6174 if (compose->to_list) {
6175 if (compose->account->protocol != A_NNTP)
6176 mailac = compose->account;
6177 else if (cur_account && cur_account->protocol != A_NNTP)
6178 mailac = cur_account;
6179 else if (!(mailac = compose_current_mail_account())) {
6180 alertpanel_error(_("No account for sending mails available!"));
6181 return COMPOSE_QUEUE_ERROR_NO_MSG;
6185 if (compose->newsgroup_list) {
6186 if (compose->account->protocol == A_NNTP)
6187 newsac = compose->account;
6189 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6190 return COMPOSE_QUEUE_ERROR_NO_MSG;
6194 /* write queue header */
6195 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6196 G_DIR_SEPARATOR, compose, (guint) rand());
6197 debug_print("queuing to %s\n", tmp);
6198 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6199 FILE_OP_ERROR(tmp, "claws_fopen");
6201 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6204 if (change_file_mode_rw(fp, tmp) < 0) {
6205 FILE_OP_ERROR(tmp, "chmod");
6206 g_warning("can't change file mode");
6209 /* queueing variables */
6210 err |= (fprintf(fp, "AF:\n") < 0);
6211 err |= (fprintf(fp, "NF:0\n") < 0);
6212 err |= (fprintf(fp, "PS:10\n") < 0);
6213 err |= (fprintf(fp, "SRH:1\n") < 0);
6214 err |= (fprintf(fp, "SFN:\n") < 0);
6215 err |= (fprintf(fp, "DSR:\n") < 0);
6217 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6219 err |= (fprintf(fp, "MID:\n") < 0);
6220 err |= (fprintf(fp, "CFG:\n") < 0);
6221 err |= (fprintf(fp, "PT:0\n") < 0);
6222 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6223 err |= (fprintf(fp, "RQ:\n") < 0);
6225 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6227 err |= (fprintf(fp, "SSV:\n") < 0);
6229 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6231 err |= (fprintf(fp, "NSV:\n") < 0);
6232 err |= (fprintf(fp, "SSH:\n") < 0);
6233 /* write recipient list */
6234 if (compose->to_list) {
6235 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6236 for (cur = compose->to_list->next; cur != NULL;
6238 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6239 err |= (fprintf(fp, "\n") < 0);
6241 /* write newsgroup list */
6242 if (compose->newsgroup_list) {
6243 err |= (fprintf(fp, "NG:") < 0);
6244 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6245 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6246 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6247 err |= (fprintf(fp, "\n") < 0);
6251 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6253 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6256 if (compose->privacy_system != NULL) {
6257 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6258 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6259 if (compose->use_encryption) {
6260 if (!compose_warn_encryption(compose)) {
6264 return COMPOSE_QUEUE_ERROR_NO_MSG;
6266 if (mailac && mailac->encrypt_to_self) {
6267 GSList *tmp_list = g_slist_copy(compose->to_list);
6268 tmp_list = g_slist_append(tmp_list, compose->account->address);
6269 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6270 g_slist_free(tmp_list);
6272 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6274 if (compose->encdata != NULL) {
6275 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6276 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6277 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6278 compose->encdata) < 0);
6279 } /* else we finally dont want to encrypt */
6281 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6282 /* and if encdata was null, it means there's been a problem in
6285 g_warning("failed to write queue message");
6289 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6294 /* Save copy folder */
6295 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6296 gchar *savefolderid;
6298 savefolderid = compose_get_save_to(compose);
6299 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6300 g_free(savefolderid);
6302 /* Save copy folder */
6303 if (compose->return_receipt) {
6304 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6306 /* Message-ID of message replying to */
6307 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6308 gchar *folderid = NULL;
6310 if (compose->replyinfo->folder)
6311 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6312 if (folderid == NULL)
6313 folderid = g_strdup("NULL");
6315 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6318 /* Message-ID of message forwarding to */
6319 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6320 gchar *folderid = NULL;
6322 if (compose->fwdinfo->folder)
6323 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6324 if (folderid == NULL)
6325 folderid = g_strdup("NULL");
6327 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6331 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6332 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6334 /* end of headers */
6335 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6337 if (compose->redirect_filename != NULL) {
6338 if (compose_redirect_write_to_file(compose, fp) < 0) {
6342 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6346 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6354 g_warning("failed to write queue message");
6358 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6360 if (claws_safe_fclose(fp) == EOF) {
6361 FILE_OP_ERROR(tmp, "claws_fclose");
6364 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6367 if (item && *item) {
6370 queue = account_get_special_folder(compose->account, F_QUEUE);
6373 g_warning("can't find queue folder");
6376 return COMPOSE_QUEUE_ERROR_NO_MSG;
6378 folder_item_scan(queue);
6379 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6380 g_warning("can't queue the message");
6383 return COMPOSE_QUEUE_ERROR_NO_MSG;
6386 if (msgpath == NULL) {
6392 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6393 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6395 procmsg_msginfo_change_flags(mi,
6396 compose->targetinfo->flags.perm_flags,
6397 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6400 g_slist_free(mi->tags);
6401 mi->tags = g_slist_copy(compose->targetinfo->tags);
6402 procmsg_msginfo_free(&mi);
6406 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6407 compose_remove_reedit_target(compose, FALSE);
6410 if ((msgnum != NULL) && (item != NULL)) {
6415 return COMPOSE_QUEUE_SUCCESS;
6418 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6421 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6426 GError *error = NULL;
6431 gchar *type, *subtype;
6432 GtkTreeModel *model;
6435 model = gtk_tree_view_get_model(tree_view);
6437 if (!gtk_tree_model_get_iter_first(model, &iter))
6440 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6442 if (!is_file_exist(ainfo->file)) {
6443 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6444 AlertValue val = alertpanel_full(_("Warning"), msg,
6445 _("Cancel sending"), _("Ignore attachment"), NULL,
6446 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6448 if (val == G_ALERTDEFAULT) {
6454 f = g_file_new_for_path(ainfo->file);
6455 fi = g_file_query_info(f, "standard::size",
6456 G_FILE_QUERY_INFO_NONE, NULL, &error);
6457 if (error != NULL) {
6458 g_warning(error->message);
6459 g_error_free(error);
6463 size = g_file_info_get_size(fi);
6467 if (g_stat(ainfo->file, &statbuf) < 0)
6469 size = statbuf.st_size;
6472 mimepart = procmime_mimeinfo_new();
6473 mimepart->content = MIMECONTENT_FILE;
6474 mimepart->data.filename = g_strdup(ainfo->file);
6475 mimepart->tmp = FALSE; /* or we destroy our attachment */
6476 mimepart->offset = 0;
6477 mimepart->length = size;
6479 type = g_strdup(ainfo->content_type);
6481 if (!strchr(type, '/')) {
6483 type = g_strdup("application/octet-stream");
6486 subtype = strchr(type, '/') + 1;
6487 *(subtype - 1) = '\0';
6488 mimepart->type = procmime_get_media_type(type);
6489 mimepart->subtype = g_strdup(subtype);
6492 if (mimepart->type == MIMETYPE_MESSAGE &&
6493 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6494 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6495 } else if (mimepart->type == MIMETYPE_TEXT) {
6496 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6497 /* Text parts with no name come from multipart/alternative
6498 * forwards. Make sure the recipient won't look at the
6499 * original HTML part by mistake. */
6500 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6501 ainfo->name = g_strdup_printf(_("Original %s part"),
6505 g_hash_table_insert(mimepart->typeparameters,
6506 g_strdup("charset"), g_strdup(ainfo->charset));
6508 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6509 if (mimepart->type == MIMETYPE_APPLICATION &&
6510 !g_strcmp0(mimepart->subtype, "octet-stream"))
6511 g_hash_table_insert(mimepart->typeparameters,
6512 g_strdup("name"), g_strdup(ainfo->name));
6513 g_hash_table_insert(mimepart->dispositionparameters,
6514 g_strdup("filename"), g_strdup(ainfo->name));
6515 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6518 if (mimepart->type == MIMETYPE_MESSAGE
6519 || mimepart->type == MIMETYPE_MULTIPART)
6520 ainfo->encoding = ENC_BINARY;
6521 else if (compose->use_signing || compose->fwdinfo != NULL) {
6522 if (ainfo->encoding == ENC_7BIT)
6523 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6524 else if (ainfo->encoding == ENC_8BIT)
6525 ainfo->encoding = ENC_BASE64;
6528 procmime_encode_content(mimepart, ainfo->encoding);
6530 g_node_append(parent->node, mimepart->node);
6531 } while (gtk_tree_model_iter_next(model, &iter));
6536 static gchar *compose_quote_list_of_addresses(gchar *str)
6538 GSList *list = NULL, *item = NULL;
6539 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6541 list = address_list_append_with_comments(list, str);
6542 for (item = list; item != NULL; item = item->next) {
6543 gchar *spec = item->data;
6544 gchar *endofname = strstr(spec, " <");
6545 if (endofname != NULL) {
6548 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6549 qqname = escape_internal_quotes(qname, '"');
6551 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6552 gchar *addr = g_strdup(endofname);
6553 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6554 faddr = g_strconcat(name, addr, NULL);
6557 debug_print("new auto-quoted address: '%s'\n", faddr);
6561 result = g_strdup((faddr != NULL)? faddr: spec);
6563 gchar *tmp = g_strconcat(result,
6565 (faddr != NULL)? faddr: spec,
6570 if (faddr != NULL) {
6575 slist_free_strings_full(list);
6580 #define IS_IN_CUSTOM_HEADER(header) \
6581 (compose->account->add_customhdr && \
6582 custom_header_find(compose->account->customhdr_list, header) != NULL)
6584 static const gchar *compose_untranslated_header_name(gchar *header_name)
6586 /* return the untranslated header name, if header_name is a known
6587 header name, in either its translated or untranslated form, with
6588 or without trailing colon. otherwise, returns header_name. */
6589 gchar *translated_header_name;
6590 gchar *translated_header_name_wcolon;
6591 const gchar *untranslated_header_name;
6592 const gchar *untranslated_header_name_wcolon;
6595 cm_return_val_if_fail(header_name != NULL, NULL);
6597 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6598 untranslated_header_name = HEADERS[i].header_name;
6599 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6601 translated_header_name = gettext(untranslated_header_name);
6602 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6604 if (!strcmp(header_name, untranslated_header_name) ||
6605 !strcmp(header_name, translated_header_name)) {
6606 return untranslated_header_name;
6608 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6609 !strcmp(header_name, translated_header_name_wcolon)) {
6610 return untranslated_header_name_wcolon;
6614 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6618 static void compose_add_headerfield_from_headerlist(Compose *compose,
6620 const gchar *fieldname,
6621 const gchar *seperator)
6623 gchar *str, *fieldname_w_colon;
6624 gboolean add_field = FALSE;
6626 ComposeHeaderEntry *headerentry;
6627 const gchar *headerentryname;
6628 const gchar *trans_fieldname;
6631 if (IS_IN_CUSTOM_HEADER(fieldname))
6634 debug_print("Adding %s-fields\n", fieldname);
6636 fieldstr = g_string_sized_new(64);
6638 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6639 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6641 for (list = compose->header_list; list; list = list->next) {
6642 headerentry = ((ComposeHeaderEntry *)list->data);
6643 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6645 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6646 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6648 str = compose_quote_list_of_addresses(ustr);
6650 if (str != NULL && str[0] != '\0') {
6652 g_string_append(fieldstr, seperator);
6653 g_string_append(fieldstr, str);
6662 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6663 compose_convert_header
6664 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6665 strlen(fieldname) + 2, TRUE);
6666 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6670 g_free(fieldname_w_colon);
6671 g_string_free(fieldstr, TRUE);
6676 static gchar *compose_get_manual_headers_info(Compose *compose)
6678 GString *sh_header = g_string_new(" ");
6680 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6682 for (list = compose->header_list; list; list = list->next) {
6683 ComposeHeaderEntry *headerentry;
6686 gchar *headername_wcolon;
6687 const gchar *headername_trans;
6689 gboolean standard_header = FALSE;
6691 headerentry = ((ComposeHeaderEntry *)list->data);
6693 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6695 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6700 if (!strstr(tmp, ":")) {
6701 headername_wcolon = g_strconcat(tmp, ":", NULL);
6702 headername = g_strdup(tmp);
6704 headername_wcolon = g_strdup(tmp);
6705 headername = g_strdup(strtok(tmp, ":"));
6709 string = std_headers;
6710 while (*string != NULL) {
6711 headername_trans = prefs_common_translated_header_name(*string);
6712 if (!strcmp(headername_trans, headername_wcolon))
6713 standard_header = TRUE;
6716 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6717 g_string_append_printf(sh_header, "%s ", headername);
6719 g_free(headername_wcolon);
6721 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6722 return g_string_free(sh_header, FALSE);
6725 static gchar *compose_get_header(Compose *compose)
6727 gchar date[RFC822_DATE_BUFFSIZE];
6728 gchar buf[BUFFSIZE];
6729 const gchar *entry_str;
6733 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6735 gchar *from_name = NULL, *from_address = NULL;
6738 cm_return_val_if_fail(compose->account != NULL, NULL);
6739 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6741 header = g_string_sized_new(64);
6744 if (prefs_common.hide_timezone)
6745 get_rfc822_date_hide_tz(date, sizeof(date));
6747 get_rfc822_date(date, sizeof(date));
6748 g_string_append_printf(header, "Date: %s\n", date);
6752 if (compose->account->name && *compose->account->name) {
6754 QUOTE_IF_REQUIRED(buf, compose->account->name);
6755 tmp = g_strdup_printf("%s <%s>",
6756 buf, compose->account->address);
6758 tmp = g_strdup_printf("%s",
6759 compose->account->address);
6761 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6762 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6764 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6765 from_address = g_strdup(compose->account->address);
6767 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6768 /* extract name and address */
6769 if (strstr(spec, " <") && strstr(spec, ">")) {
6770 from_address = g_strdup(strrchr(spec, '<')+1);
6771 *(strrchr(from_address, '>')) = '\0';
6772 from_name = g_strdup(spec);
6773 *(strrchr(from_name, '<')) = '\0';
6776 from_address = g_strdup(spec);
6783 if (from_name && *from_name) {
6785 compose_convert_header
6786 (compose, buf, sizeof(buf), from_name,
6787 strlen("From: "), TRUE);
6788 QUOTE_IF_REQUIRED(name, buf);
6789 qname = escape_internal_quotes(name, '"');
6791 g_string_append_printf(header, "From: %s <%s>\n",
6792 qname, from_address);
6793 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6794 compose->return_receipt) {
6795 compose_convert_header(compose, buf, sizeof(buf), from_name,
6796 strlen("Disposition-Notification-To: "),
6798 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6803 g_string_append_printf(header, "From: %s\n", from_address);
6804 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6805 compose->return_receipt)
6806 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6810 g_free(from_address);
6813 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6816 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6819 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6823 * If this account is a NNTP account remove Bcc header from
6824 * message body since it otherwise will be publicly shown
6826 if (compose->account->protocol != A_NNTP)
6827 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6830 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6832 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6835 compose_convert_header(compose, buf, sizeof(buf), str,
6836 strlen("Subject: "), FALSE);
6837 g_string_append_printf(header, "Subject: %s\n", buf);
6843 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6844 g_string_append_printf(header, "Message-ID: <%s>\n",
6848 if (compose->remove_references == FALSE) {
6850 if (compose->inreplyto && compose->to_list)
6851 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6854 if (compose->references)
6855 g_string_append_printf(header, "References: %s\n", compose->references);
6859 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6862 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6865 if (compose->account->organization &&
6866 strlen(compose->account->organization) &&
6867 !IS_IN_CUSTOM_HEADER("Organization")) {
6868 compose_convert_header(compose, buf, sizeof(buf),
6869 compose->account->organization,
6870 strlen("Organization: "), FALSE);
6871 g_string_append_printf(header, "Organization: %s\n", buf);
6874 /* Program version and system info */
6875 if (compose->account->gen_xmailer &&
6876 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6877 !compose->newsgroup_list) {
6878 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6880 gtk_major_version, gtk_minor_version, gtk_micro_version,
6883 if (compose->account->gen_xmailer &&
6884 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6885 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6887 gtk_major_version, gtk_minor_version, gtk_micro_version,
6891 /* custom headers */
6892 if (compose->account->add_customhdr) {
6895 for (cur = compose->account->customhdr_list; cur != NULL;
6897 CustomHeader *chdr = (CustomHeader *)cur->data;
6899 if (custom_header_is_allowed(chdr->name)
6900 && chdr->value != NULL
6901 && *(chdr->value) != '\0') {
6902 compose_convert_header
6903 (compose, buf, sizeof(buf),
6905 strlen(chdr->name) + 2, FALSE);
6906 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6911 /* Automatic Faces and X-Faces */
6912 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6913 g_string_append_printf(header, "X-Face: %s\n", buf);
6915 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6916 g_string_append_printf(header, "X-Face: %s\n", buf);
6918 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6919 g_string_append_printf(header, "Face: %s\n", buf);
6921 else if (get_default_face (buf, sizeof(buf)) == 0) {
6922 g_string_append_printf(header, "Face: %s\n", buf);
6926 switch (compose->priority) {
6927 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6928 "X-Priority: 1 (Highest)\n");
6930 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6931 "X-Priority: 2 (High)\n");
6933 case PRIORITY_NORMAL: break;
6934 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6935 "X-Priority: 4 (Low)\n");
6937 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6938 "X-Priority: 5 (Lowest)\n");
6940 default: debug_print("compose: priority unknown : %d\n",
6944 /* get special headers */
6945 for (list = compose->header_list; list; list = list->next) {
6946 ComposeHeaderEntry *headerentry;
6949 gchar *headername_wcolon;
6950 const gchar *headername_trans;
6953 gboolean standard_header = FALSE;
6955 headerentry = ((ComposeHeaderEntry *)list->data);
6957 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6959 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6964 if (!strstr(tmp, ":")) {
6965 headername_wcolon = g_strconcat(tmp, ":", NULL);
6966 headername = g_strdup(tmp);
6968 headername_wcolon = g_strdup(tmp);
6969 headername = g_strdup(strtok(tmp, ":"));
6973 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6974 Xstrdup_a(headervalue, entry_str, return NULL);
6975 subst_char(headervalue, '\r', ' ');
6976 subst_char(headervalue, '\n', ' ');
6977 g_strstrip(headervalue);
6978 if (*headervalue != '\0') {
6979 string = std_headers;
6980 while (*string != NULL && !standard_header) {
6981 headername_trans = prefs_common_translated_header_name(*string);
6982 /* support mixed translated and untranslated headers */
6983 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6984 standard_header = TRUE;
6987 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6988 /* store untranslated header name */
6989 g_string_append_printf(header, "%s %s\n",
6990 compose_untranslated_header_name(headername_wcolon), headervalue);
6994 g_free(headername_wcolon);
6998 g_string_free(header, FALSE);
7003 #undef IS_IN_CUSTOM_HEADER
7005 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
7006 gint header_len, gboolean addr_field)
7008 gchar *tmpstr = NULL;
7009 const gchar *out_codeset = NULL;
7011 cm_return_if_fail(src != NULL);
7012 cm_return_if_fail(dest != NULL);
7014 if (len < 1) return;
7016 tmpstr = g_strdup(src);
7018 subst_char(tmpstr, '\n', ' ');
7019 subst_char(tmpstr, '\r', ' ');
7022 if (!g_utf8_validate(tmpstr, -1, NULL)) {
7023 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
7024 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
7029 codeconv_set_strict(TRUE);
7030 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7031 conv_get_charset_str(compose->out_encoding));
7032 codeconv_set_strict(FALSE);
7034 if (!dest || *dest == '\0') {
7035 gchar *test_conv_global_out = NULL;
7036 gchar *test_conv_reply = NULL;
7038 /* automatic mode. be automatic. */
7039 codeconv_set_strict(TRUE);
7041 out_codeset = conv_get_outgoing_charset_str();
7043 debug_print("trying to convert to %s\n", out_codeset);
7044 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7047 if (!test_conv_global_out && compose->orig_charset
7048 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7049 out_codeset = compose->orig_charset;
7050 debug_print("failure; trying to convert to %s\n", out_codeset);
7051 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7054 if (!test_conv_global_out && !test_conv_reply) {
7056 out_codeset = CS_INTERNAL;
7057 debug_print("finally using %s\n", out_codeset);
7059 g_free(test_conv_global_out);
7060 g_free(test_conv_reply);
7061 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7063 codeconv_set_strict(FALSE);
7068 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7072 cm_return_if_fail(user_data != NULL);
7074 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7075 g_strstrip(address);
7076 if (*address != '\0') {
7077 gchar *name = procheader_get_fromname(address);
7078 extract_address(address);
7079 #ifndef USE_ALT_ADDRBOOK
7080 addressbook_add_contact(name, address, NULL, NULL);
7082 debug_print("%s: %s\n", name, address);
7083 if (addressadd_selection(name, address, NULL, NULL)) {
7084 debug_print( "addressbook_add_contact - added\n" );
7091 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7093 GtkWidget *menuitem;
7096 cm_return_if_fail(menu != NULL);
7097 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7099 menuitem = gtk_separator_menu_item_new();
7100 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7101 gtk_widget_show(menuitem);
7103 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7104 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7106 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7107 g_strstrip(address);
7108 if (*address == '\0') {
7109 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7112 g_signal_connect(G_OBJECT(menuitem), "activate",
7113 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7114 gtk_widget_show(menuitem);
7117 void compose_add_extra_header(gchar *header, GtkListStore *model)
7120 if (strcmp(header, "")) {
7121 COMBOBOX_ADD(model, header, COMPOSE_TO);
7125 void compose_add_extra_header_entries(GtkListStore *model)
7129 gchar buf[BUFFSIZE];
7132 if (extra_headers == NULL) {
7133 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7134 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7135 debug_print("extra headers file not found\n");
7136 goto extra_headers_done;
7138 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7139 lastc = strlen(buf) - 1; /* remove trailing control chars */
7140 while (lastc >= 0 && buf[lastc] != ':')
7141 buf[lastc--] = '\0';
7142 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7143 buf[lastc] = '\0'; /* remove trailing : for comparison */
7144 if (custom_header_is_allowed(buf)) {
7146 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7149 g_message("disallowed extra header line: %s\n", buf);
7153 g_message("invalid extra header line: %s\n", buf);
7159 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7160 extra_headers = g_slist_reverse(extra_headers);
7162 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7166 static void _ldap_srv_func(gpointer data, gpointer user_data)
7168 LdapServer *server = (LdapServer *)data;
7169 gboolean *enable = (gboolean *)user_data;
7171 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7172 server->searchFlag = *enable;
7176 static void compose_create_header_entry(Compose *compose)
7178 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7185 const gchar *header = NULL;
7186 ComposeHeaderEntry *headerentry;
7187 gboolean standard_header = FALSE;
7188 GtkListStore *model;
7191 headerentry = g_new0(ComposeHeaderEntry, 1);
7193 /* Combo box model */
7194 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7195 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7197 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7199 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7201 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7202 COMPOSE_NEWSGROUPS);
7203 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7205 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7206 COMPOSE_FOLLOWUPTO);
7207 compose_add_extra_header_entries(model);
7210 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7211 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7212 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7213 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7214 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7215 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7216 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7217 G_CALLBACK(compose_grab_focus_cb), compose);
7218 gtk_widget_show(combo);
7220 /* Putting only the combobox child into focus chain of its parent causes
7221 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7222 * This eliminates need to pres Tab twice in order to really get from the
7223 * combobox to next widget. */
7225 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7226 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7229 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7230 compose->header_nextrow, compose->header_nextrow+1,
7231 GTK_SHRINK, GTK_FILL, 0, 0);
7232 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7233 const gchar *last_header_entry = gtk_entry_get_text(
7234 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7236 while (*string != NULL) {
7237 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7238 standard_header = TRUE;
7241 if (standard_header)
7242 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7244 if (!compose->header_last || !standard_header) {
7245 switch(compose->account->protocol) {
7247 header = prefs_common_translated_header_name("Newsgroups:");
7250 header = prefs_common_translated_header_name("To:");
7255 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7257 gtk_editable_set_editable(
7258 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7259 prefs_common.type_any_header);
7261 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7262 G_CALLBACK(compose_grab_focus_cb), compose);
7264 /* Entry field with cleanup button */
7265 button = gtk_button_new();
7266 gtk_button_set_image(GTK_BUTTON(button),
7267 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7268 gtk_widget_show(button);
7269 CLAWS_SET_TIP(button,
7270 _("Delete entry contents"));
7271 entry = gtk_entry_new();
7272 gtk_widget_show(entry);
7273 CLAWS_SET_TIP(entry,
7274 _("Use <tab> to autocomplete from addressbook"));
7275 hbox = gtk_hbox_new (FALSE, 0);
7276 gtk_widget_show(hbox);
7277 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7278 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7279 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7280 compose->header_nextrow, compose->header_nextrow+1,
7281 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7283 g_signal_connect(G_OBJECT(entry), "key-press-event",
7284 G_CALLBACK(compose_headerentry_key_press_event_cb),
7286 g_signal_connect(G_OBJECT(entry), "changed",
7287 G_CALLBACK(compose_headerentry_changed_cb),
7289 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7290 G_CALLBACK(compose_grab_focus_cb), compose);
7292 g_signal_connect(G_OBJECT(button), "clicked",
7293 G_CALLBACK(compose_headerentry_button_clicked_cb),
7297 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7298 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7299 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7300 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7301 G_CALLBACK(compose_header_drag_received_cb),
7303 g_signal_connect(G_OBJECT(entry), "drag-drop",
7304 G_CALLBACK(compose_drag_drop),
7306 g_signal_connect(G_OBJECT(entry), "populate-popup",
7307 G_CALLBACK(compose_entry_popup_extend),
7311 #ifndef PASSWORD_CRYPTO_OLD
7312 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7313 if (pwd_servers != NULL && master_passphrase() == NULL) {
7314 gboolean enable = FALSE;
7315 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7316 /* Temporarily disable password-protected LDAP servers,
7317 * because user did not provide a master passphrase.
7318 * We can safely enable searchFlag on all servers in this list
7319 * later, since addrindex_get_password_protected_ldap_servers()
7320 * includes servers which have it enabled initially. */
7321 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7322 compose->passworded_ldap_servers = pwd_servers;
7324 #endif /* PASSWORD_CRYPTO_OLD */
7325 #endif /* USE_LDAP */
7327 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7329 headerentry->compose = compose;
7330 headerentry->combo = combo;
7331 headerentry->entry = entry;
7332 headerentry->button = button;
7333 headerentry->hbox = hbox;
7334 headerentry->headernum = compose->header_nextrow;
7335 headerentry->type = PREF_NONE;
7337 compose->header_nextrow++;
7338 compose->header_last = headerentry;
7339 compose->header_list =
7340 g_slist_append(compose->header_list,
7344 static void compose_add_header_entry(Compose *compose, const gchar *header,
7345 gchar *text, ComposePrefType pref_type)
7347 ComposeHeaderEntry *last_header = compose->header_last;
7348 gchar *tmp = g_strdup(text), *email;
7349 gboolean replyto_hdr;
7351 replyto_hdr = (!strcasecmp(header,
7352 prefs_common_translated_header_name("Reply-To:")) ||
7354 prefs_common_translated_header_name("Followup-To:")) ||
7356 prefs_common_translated_header_name("In-Reply-To:")));
7358 extract_address(tmp);
7359 email = g_utf8_strdown(tmp, -1);
7361 if (replyto_hdr == FALSE &&
7362 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7364 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7365 header, text, (gint) pref_type);
7371 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7372 gtk_entry_set_text(GTK_ENTRY(
7373 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7375 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7376 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7377 last_header->type = pref_type;
7379 if (replyto_hdr == FALSE)
7380 g_hash_table_insert(compose->email_hashtable, email,
7381 GUINT_TO_POINTER(1));
7388 static void compose_destroy_headerentry(Compose *compose,
7389 ComposeHeaderEntry *headerentry)
7391 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7394 extract_address(text);
7395 email = g_utf8_strdown(text, -1);
7396 g_hash_table_remove(compose->email_hashtable, email);
7400 gtk_widget_destroy(headerentry->combo);
7401 gtk_widget_destroy(headerentry->entry);
7402 gtk_widget_destroy(headerentry->button);
7403 gtk_widget_destroy(headerentry->hbox);
7404 g_free(headerentry);
7407 static void compose_remove_header_entries(Compose *compose)
7410 for (list = compose->header_list; list; list = list->next)
7411 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7413 compose->header_last = NULL;
7414 g_slist_free(compose->header_list);
7415 compose->header_list = NULL;
7416 compose->header_nextrow = 1;
7417 compose_create_header_entry(compose);
7420 static GtkWidget *compose_create_header(Compose *compose)
7422 GtkWidget *from_optmenu_hbox;
7423 GtkWidget *header_table_main;
7424 GtkWidget *header_scrolledwin;
7425 GtkWidget *header_table;
7427 /* parent with account selection and from header */
7428 header_table_main = gtk_table_new(2, 2, FALSE);
7429 gtk_widget_show(header_table_main);
7430 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7432 from_optmenu_hbox = compose_account_option_menu_create(compose);
7433 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7434 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7436 /* child with header labels and entries */
7437 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7438 gtk_widget_show(header_scrolledwin);
7439 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7441 header_table = gtk_table_new(2, 2, FALSE);
7442 gtk_widget_show(header_table);
7443 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7444 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7445 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7446 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7447 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7449 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7450 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7452 compose->header_table = header_table;
7453 compose->header_list = NULL;
7454 compose->header_nextrow = 0;
7456 compose_create_header_entry(compose);
7458 compose->table = NULL;
7460 return header_table_main;
7463 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7465 Compose *compose = (Compose *)data;
7466 GdkEventButton event;
7469 event.time = gtk_get_current_event_time();
7471 return attach_button_pressed(compose->attach_clist, &event, compose);
7474 static GtkWidget *compose_create_attach(Compose *compose)
7476 GtkWidget *attach_scrwin;
7477 GtkWidget *attach_clist;
7479 GtkListStore *store;
7480 GtkCellRenderer *renderer;
7481 GtkTreeViewColumn *column;
7482 GtkTreeSelection *selection;
7484 /* attachment list */
7485 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7486 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7487 GTK_POLICY_AUTOMATIC,
7488 GTK_POLICY_AUTOMATIC);
7489 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7491 store = gtk_list_store_new(N_ATTACH_COLS,
7497 G_TYPE_AUTO_POINTER,
7499 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7500 (GTK_TREE_MODEL(store)));
7501 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7502 g_object_unref(store);
7504 renderer = gtk_cell_renderer_text_new();
7505 column = gtk_tree_view_column_new_with_attributes
7506 (_("Mime type"), renderer, "text",
7507 COL_MIMETYPE, NULL);
7508 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7510 renderer = gtk_cell_renderer_text_new();
7511 column = gtk_tree_view_column_new_with_attributes
7512 (_("Size"), renderer, "text",
7514 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7516 renderer = gtk_cell_renderer_text_new();
7517 column = gtk_tree_view_column_new_with_attributes
7518 (_("Name"), renderer, "text",
7520 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7522 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7523 prefs_common.use_stripes_everywhere);
7524 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7525 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7527 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7528 G_CALLBACK(attach_selected), compose);
7529 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7530 G_CALLBACK(attach_button_pressed), compose);
7531 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7532 G_CALLBACK(popup_attach_button_pressed), compose);
7533 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7534 G_CALLBACK(attach_key_pressed), compose);
7537 gtk_drag_dest_set(attach_clist,
7538 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7539 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7540 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7541 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7542 G_CALLBACK(compose_attach_drag_received_cb),
7544 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7545 G_CALLBACK(compose_drag_drop),
7548 compose->attach_scrwin = attach_scrwin;
7549 compose->attach_clist = attach_clist;
7551 return attach_scrwin;
7554 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7556 static GtkWidget *compose_create_others(Compose *compose)
7559 GtkWidget *savemsg_checkbtn;
7560 GtkWidget *savemsg_combo;
7561 GtkWidget *savemsg_select;
7564 gchar *folderidentifier;
7566 /* Table for settings */
7567 table = gtk_table_new(3, 1, FALSE);
7568 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7569 gtk_widget_show(table);
7570 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7573 /* Save Message to folder */
7574 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7575 gtk_widget_show(savemsg_checkbtn);
7576 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7577 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7578 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7581 savemsg_combo = gtk_combo_box_text_new_with_entry();
7582 compose->savemsg_checkbtn = savemsg_checkbtn;
7583 compose->savemsg_combo = savemsg_combo;
7584 gtk_widget_show(savemsg_combo);
7586 if (prefs_common.compose_save_to_history)
7587 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7588 prefs_common.compose_save_to_history);
7589 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7590 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7591 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7592 G_CALLBACK(compose_grab_focus_cb), compose);
7593 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7594 if (compose->account->set_sent_folder || prefs_common.savemsg)
7595 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7597 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7598 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7599 folderidentifier = folder_item_get_identifier(account_get_special_folder
7600 (compose->account, F_OUTBOX));
7601 compose_set_save_to(compose, folderidentifier);
7602 g_free(folderidentifier);
7605 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7606 gtk_widget_show(savemsg_select);
7607 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7608 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7609 G_CALLBACK(compose_savemsg_select_cb),
7615 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7620 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7621 _("Select folder to save message to"));
7624 path = folder_item_get_identifier(dest);
7626 compose_set_save_to(compose, path);
7630 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7631 GdkAtom clip, GtkTextIter *insert_place);
7634 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7638 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7640 if (event->button == 3) {
7642 GtkTextIter sel_start, sel_end;
7643 gboolean stuff_selected;
7645 /* move the cursor to allow GtkAspell to check the word
7646 * under the mouse */
7647 if (event->x && event->y) {
7648 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7649 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7651 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7654 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7655 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7658 stuff_selected = gtk_text_buffer_get_selection_bounds(
7660 &sel_start, &sel_end);
7662 gtk_text_buffer_place_cursor (buffer, &iter);
7663 /* reselect stuff */
7665 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7666 gtk_text_buffer_select_range(buffer,
7667 &sel_start, &sel_end);
7669 return FALSE; /* pass the event so that the right-click goes through */
7672 if (event->button == 2) {
7677 /* get the middle-click position to paste at the correct place */
7678 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7679 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7681 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7684 entry_paste_clipboard(compose, text,
7685 prefs_common.linewrap_pastes,
7686 GDK_SELECTION_PRIMARY, &iter);
7694 static void compose_spell_menu_changed(void *data)
7696 Compose *compose = (Compose *)data;
7698 GtkWidget *menuitem;
7699 GtkWidget *parent_item;
7700 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7703 if (compose->gtkaspell == NULL)
7706 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7707 "/Menu/Spelling/Options");
7709 /* setting the submenu removes /Spelling/Options from the factory
7710 * so we need to save it */
7712 if (parent_item == NULL) {
7713 parent_item = compose->aspell_options_menu;
7714 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7716 compose->aspell_options_menu = parent_item;
7718 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7720 spell_menu = g_slist_reverse(spell_menu);
7721 for (items = spell_menu;
7722 items; items = items->next) {
7723 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7724 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7725 gtk_widget_show(GTK_WIDGET(menuitem));
7727 g_slist_free(spell_menu);
7729 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7730 gtk_widget_show(parent_item);
7733 static void compose_dict_changed(void *data)
7735 Compose *compose = (Compose *) data;
7737 if(!compose->gtkaspell)
7739 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7742 gtkaspell_highlight_all(compose->gtkaspell);
7743 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7747 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7749 Compose *compose = (Compose *)data;
7750 GdkEventButton event;
7753 event.time = gtk_get_current_event_time();
7757 return text_clicked(compose->text, &event, compose);
7760 static gboolean compose_force_window_origin = TRUE;
7761 static Compose *compose_create(PrefsAccount *account,
7770 GtkWidget *handlebox;
7772 GtkWidget *notebook;
7774 GtkWidget *attach_hbox;
7775 GtkWidget *attach_lab1;
7776 GtkWidget *attach_lab2;
7781 GtkWidget *subject_hbox;
7782 GtkWidget *subject_frame;
7783 GtkWidget *subject_entry;
7787 GtkWidget *edit_vbox;
7788 GtkWidget *ruler_hbox;
7790 GtkWidget *scrolledwin;
7792 GtkTextBuffer *buffer;
7793 GtkClipboard *clipboard;
7795 UndoMain *undostruct;
7797 GtkWidget *popupmenu;
7798 GtkWidget *tmpl_menu;
7799 GtkActionGroup *action_group = NULL;
7802 GtkAspell * gtkaspell = NULL;
7805 static GdkGeometry geometry;
7807 cm_return_val_if_fail(account != NULL, NULL);
7809 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7810 &default_header_bgcolor);
7811 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7812 &default_header_color);
7814 debug_print("Creating compose window...\n");
7815 compose = g_new0(Compose, 1);
7817 compose->batch = batch;
7818 compose->account = account;
7819 compose->folder = folder;
7821 g_mutex_init(&compose->mutex);
7822 compose->set_cursor_pos = -1;
7824 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7826 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7827 gtk_widget_set_size_request(window, prefs_common.compose_width,
7828 prefs_common.compose_height);
7830 if (!geometry.max_width) {
7831 geometry.max_width = gdk_screen_width();
7832 geometry.max_height = gdk_screen_height();
7835 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7836 &geometry, GDK_HINT_MAX_SIZE);
7837 if (!geometry.min_width) {
7838 geometry.min_width = 600;
7839 geometry.min_height = 440;
7841 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7842 &geometry, GDK_HINT_MIN_SIZE);
7844 #ifndef GENERIC_UMPC
7845 if (compose_force_window_origin)
7846 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7847 prefs_common.compose_y);
7849 g_signal_connect(G_OBJECT(window), "delete_event",
7850 G_CALLBACK(compose_delete_cb), compose);
7851 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7852 gtk_widget_realize(window);
7854 gtkut_widget_set_composer_icon(window);
7856 vbox = gtk_vbox_new(FALSE, 0);
7857 gtk_container_add(GTK_CONTAINER(window), vbox);
7859 compose->ui_manager = gtk_ui_manager_new();
7860 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7861 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7862 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7863 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7864 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7865 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7866 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7867 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7868 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7869 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7878 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7887 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7889 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7907 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7913 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7955 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7965 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7966 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7968 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7970 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7973 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7974 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7976 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7982 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7984 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7988 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)
7989 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)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7995 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)
7996 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)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
8001 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)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
8005 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)
8006 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
8010 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
8011 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)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
8014 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
8015 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
8017 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
8018 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)
8019 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)
8020 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
8021 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
8023 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
8024 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
8025 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
8026 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8027 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8028 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8030 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8031 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8032 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)
8034 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8035 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8036 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8040 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8041 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8042 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8043 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8044 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8045 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8048 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8050 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8051 gtk_widget_show_all(menubar);
8053 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8054 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8056 if (prefs_common.toolbar_detachable) {
8057 handlebox = gtk_handle_box_new();
8059 handlebox = gtk_hbox_new(FALSE, 0);
8061 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8063 gtk_widget_realize(handlebox);
8064 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8067 vbox2 = gtk_vbox_new(FALSE, 2);
8068 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8069 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8072 notebook = gtk_notebook_new();
8073 gtk_widget_show(notebook);
8075 /* header labels and entries */
8076 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8077 compose_create_header(compose),
8078 gtk_label_new_with_mnemonic(_("Hea_der")));
8079 /* attachment list */
8080 attach_hbox = gtk_hbox_new(FALSE, 0);
8081 gtk_widget_show(attach_hbox);
8083 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8084 gtk_widget_show(attach_lab1);
8085 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8087 attach_lab2 = gtk_label_new("");
8088 gtk_widget_show(attach_lab2);
8089 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8091 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8092 compose_create_attach(compose),
8095 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8096 compose_create_others(compose),
8097 gtk_label_new_with_mnemonic(_("Othe_rs")));
8100 subject_hbox = gtk_hbox_new(FALSE, 0);
8101 gtk_widget_show(subject_hbox);
8103 subject_frame = gtk_frame_new(NULL);
8104 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8105 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8106 gtk_widget_show(subject_frame);
8108 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8109 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8110 gtk_widget_show(subject);
8112 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8113 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8114 gtk_widget_show(label);
8117 subject_entry = claws_spell_entry_new();
8119 subject_entry = gtk_entry_new();
8121 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8122 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8123 G_CALLBACK(compose_grab_focus_cb), compose);
8124 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8125 gtk_widget_show(subject_entry);
8126 compose->subject_entry = subject_entry;
8127 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8129 edit_vbox = gtk_vbox_new(FALSE, 0);
8131 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8134 ruler_hbox = gtk_hbox_new(FALSE, 0);
8135 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8137 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8138 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8139 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8143 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8144 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8145 GTK_POLICY_AUTOMATIC,
8146 GTK_POLICY_AUTOMATIC);
8147 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8149 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8151 text = gtk_text_view_new();
8152 if (prefs_common.show_compose_margin) {
8153 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8154 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8156 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8157 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8158 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8159 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8160 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8162 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8163 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8164 G_CALLBACK(compose_edit_size_alloc),
8166 g_signal_connect(G_OBJECT(buffer), "changed",
8167 G_CALLBACK(compose_changed_cb), compose);
8168 g_signal_connect(G_OBJECT(text), "grab_focus",
8169 G_CALLBACK(compose_grab_focus_cb), compose);
8170 g_signal_connect(G_OBJECT(buffer), "insert_text",
8171 G_CALLBACK(text_inserted), compose);
8172 g_signal_connect(G_OBJECT(text), "button_press_event",
8173 G_CALLBACK(text_clicked), compose);
8174 g_signal_connect(G_OBJECT(text), "popup-menu",
8175 G_CALLBACK(compose_popup_menu), compose);
8176 g_signal_connect(G_OBJECT(subject_entry), "changed",
8177 G_CALLBACK(compose_changed_cb), compose);
8178 g_signal_connect(G_OBJECT(subject_entry), "activate",
8179 G_CALLBACK(compose_subject_entry_activated), compose);
8182 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8183 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8184 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8185 g_signal_connect(G_OBJECT(text), "drag_data_received",
8186 G_CALLBACK(compose_insert_drag_received_cb),
8188 g_signal_connect(G_OBJECT(text), "drag-drop",
8189 G_CALLBACK(compose_drag_drop),
8191 g_signal_connect(G_OBJECT(text), "key-press-event",
8192 G_CALLBACK(completion_set_focus_to_subject),
8194 gtk_widget_show_all(vbox);
8196 /* pane between attach clist and text */
8197 paned = gtk_vpaned_new();
8198 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8199 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8200 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8201 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8202 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8203 G_CALLBACK(compose_notebook_size_alloc), paned);
8205 gtk_widget_show_all(paned);
8208 if (prefs_common.textfont) {
8209 PangoFontDescription *font_desc;
8211 font_desc = pango_font_description_from_string
8212 (prefs_common.textfont);
8214 gtk_widget_modify_font(text, font_desc);
8215 pango_font_description_free(font_desc);
8219 gtk_action_group_add_actions(action_group, compose_popup_entries,
8220 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8221 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8225 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8226 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8228 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8230 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8231 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8232 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8234 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8236 undostruct = undo_init(text);
8237 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8240 address_completion_start(window);
8242 compose->window = window;
8243 compose->vbox = vbox;
8244 compose->menubar = menubar;
8245 compose->handlebox = handlebox;
8247 compose->vbox2 = vbox2;
8249 compose->paned = paned;
8251 compose->attach_label = attach_lab2;
8253 compose->notebook = notebook;
8254 compose->edit_vbox = edit_vbox;
8255 compose->ruler_hbox = ruler_hbox;
8256 compose->ruler = ruler;
8257 compose->scrolledwin = scrolledwin;
8258 compose->text = text;
8260 compose->focused_editable = NULL;
8262 compose->popupmenu = popupmenu;
8264 compose->tmpl_menu = tmpl_menu;
8266 compose->mode = mode;
8267 compose->rmode = mode;
8269 compose->targetinfo = NULL;
8270 compose->replyinfo = NULL;
8271 compose->fwdinfo = NULL;
8273 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8274 g_str_equal, (GDestroyNotify) g_free, NULL);
8276 compose->replyto = NULL;
8278 compose->bcc = NULL;
8279 compose->followup_to = NULL;
8281 compose->ml_post = NULL;
8283 compose->inreplyto = NULL;
8284 compose->references = NULL;
8285 compose->msgid = NULL;
8286 compose->boundary = NULL;
8288 compose->autowrap = prefs_common.autowrap;
8289 compose->autoindent = prefs_common.auto_indent;
8290 compose->use_signing = FALSE;
8291 compose->use_encryption = FALSE;
8292 compose->privacy_system = NULL;
8293 compose->encdata = NULL;
8295 compose->modified = FALSE;
8297 compose->return_receipt = FALSE;
8299 compose->to_list = NULL;
8300 compose->newsgroup_list = NULL;
8302 compose->undostruct = undostruct;
8304 compose->sig_str = NULL;
8306 compose->exteditor_file = NULL;
8307 compose->exteditor_pid = INVALID_PID;
8308 compose->exteditor_tag = -1;
8309 compose->exteditor_socket = NULL;
8310 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8312 compose->folder_update_callback_id =
8313 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8314 compose_update_folder_hook,
8315 (gpointer) compose);
8318 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8319 if (mode != COMPOSE_REDIRECT) {
8320 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8321 strcmp(prefs_common.dictionary, "")) {
8322 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8323 prefs_common.alt_dictionary,
8324 conv_get_locale_charset_str(),
8325 prefs_common.color[COL_MISSPELLED],
8326 prefs_common.check_while_typing,
8327 prefs_common.recheck_when_changing_dict,
8328 prefs_common.use_alternate,
8329 prefs_common.use_both_dicts,
8330 GTK_TEXT_VIEW(text),
8331 GTK_WINDOW(compose->window),
8332 compose_dict_changed,
8333 compose_spell_menu_changed,
8336 alertpanel_error(_("Spell checker could not "
8338 gtkaspell_checkers_strerror());
8339 gtkaspell_checkers_reset_error();
8341 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8345 compose->gtkaspell = gtkaspell;
8346 compose_spell_menu_changed(compose);
8347 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8350 compose_select_account(compose, account, TRUE);
8352 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8353 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8355 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8356 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8358 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8359 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8361 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8362 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8364 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8365 if (account->protocol != A_NNTP)
8366 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8367 prefs_common_translated_header_name("To:"));
8369 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8370 prefs_common_translated_header_name("Newsgroups:"));
8372 #ifndef USE_ALT_ADDRBOOK
8373 addressbook_set_target_compose(compose);
8375 if (mode != COMPOSE_REDIRECT)
8376 compose_set_template_menu(compose);
8378 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8381 compose_list = g_list_append(compose_list, compose);
8383 if (!prefs_common.show_ruler)
8384 gtk_widget_hide(ruler_hbox);
8386 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8389 compose->priority = PRIORITY_NORMAL;
8390 compose_update_priority_menu_item(compose);
8392 compose_set_out_encoding(compose);
8395 compose_update_actions_menu(compose);
8397 /* Privacy Systems menu */
8398 compose_update_privacy_systems_menu(compose);
8399 compose_activate_privacy_system(compose, account, TRUE);
8401 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8403 gtk_widget_realize(window);
8405 gtk_widget_show(window);
8411 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8416 GtkWidget *optmenubox;
8417 GtkWidget *fromlabel;
8420 GtkWidget *from_name = NULL;
8422 gint num = 0, def_menu = 0;
8424 accounts = account_get_list();
8425 cm_return_val_if_fail(accounts != NULL, NULL);
8427 optmenubox = gtk_event_box_new();
8428 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8429 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8431 hbox = gtk_hbox_new(FALSE, 4);
8432 from_name = gtk_entry_new();
8434 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8435 G_CALLBACK(compose_grab_focus_cb), compose);
8436 g_signal_connect_after(G_OBJECT(from_name), "activate",
8437 G_CALLBACK(from_name_activate_cb), optmenu);
8439 for (; accounts != NULL; accounts = accounts->next, num++) {
8440 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8441 gchar *name, *from = NULL;
8443 if (ac == compose->account) def_menu = num;
8445 name = g_markup_printf_escaped("<i>%s</i>",
8448 if (ac == compose->account) {
8449 if (ac->name && *ac->name) {
8451 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8452 from = g_strdup_printf("%s <%s>",
8454 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8456 from = g_strdup_printf("%s",
8458 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8460 if (cur_account != compose->account) {
8461 gtk_widget_modify_base(
8462 GTK_WIDGET(from_name),
8463 GTK_STATE_NORMAL, &default_header_bgcolor);
8464 gtk_widget_modify_text(
8465 GTK_WIDGET(from_name),
8466 GTK_STATE_NORMAL, &default_header_color);
8469 COMBOBOX_ADD(menu, name, ac->account_id);
8474 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8476 g_signal_connect(G_OBJECT(optmenu), "changed",
8477 G_CALLBACK(account_activated),
8479 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8480 G_CALLBACK(compose_entry_popup_extend),
8483 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8484 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8486 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8487 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8488 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8490 /* Putting only the GtkEntry into focus chain of parent hbox causes
8491 * the account selector combobox next to it to be unreachable when
8492 * navigating widgets in GtkTable with up/down arrow keys.
8493 * Note: gtk_widget_set_can_focus() was not enough. */
8495 l = g_list_prepend(l, from_name);
8496 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8499 CLAWS_SET_TIP(optmenubox,
8500 _("Account to use for this email"));
8501 CLAWS_SET_TIP(from_name,
8502 _("Sender address to be used"));
8504 compose->account_combo = optmenu;
8505 compose->from_name = from_name;
8510 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8512 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8513 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8514 Compose *compose = (Compose *) data;
8516 compose->priority = value;
8520 static void compose_reply_change_mode(Compose *compose,
8523 gboolean was_modified = compose->modified;
8525 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8527 cm_return_if_fail(compose->replyinfo != NULL);
8529 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8531 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8533 if (action == COMPOSE_REPLY_TO_ALL)
8535 if (action == COMPOSE_REPLY_TO_SENDER)
8537 if (action == COMPOSE_REPLY_TO_LIST)
8540 compose_remove_header_entries(compose);
8541 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8542 if (compose->account->set_autocc && compose->account->auto_cc)
8543 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8545 if (compose->account->set_autobcc && compose->account->auto_bcc)
8546 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8548 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8549 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8550 compose_show_first_last_header(compose, TRUE);
8551 compose->modified = was_modified;
8552 compose_set_title(compose);
8555 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8557 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8558 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8559 Compose *compose = (Compose *) data;
8562 compose_reply_change_mode(compose, value);
8565 static void compose_update_priority_menu_item(Compose * compose)
8567 GtkWidget *menuitem = NULL;
8568 switch (compose->priority) {
8569 case PRIORITY_HIGHEST:
8570 menuitem = gtk_ui_manager_get_widget
8571 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8574 menuitem = gtk_ui_manager_get_widget
8575 (compose->ui_manager, "/Menu/Options/Priority/High");
8577 case PRIORITY_NORMAL:
8578 menuitem = gtk_ui_manager_get_widget
8579 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8582 menuitem = gtk_ui_manager_get_widget
8583 (compose->ui_manager, "/Menu/Options/Priority/Low");
8585 case PRIORITY_LOWEST:
8586 menuitem = gtk_ui_manager_get_widget
8587 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8590 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8593 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8595 Compose *compose = (Compose *) data;
8597 gboolean can_sign = FALSE, can_encrypt = FALSE;
8599 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8601 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8604 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8605 g_free(compose->privacy_system);
8606 compose->privacy_system = NULL;
8607 g_free(compose->encdata);
8608 compose->encdata = NULL;
8609 if (systemid != NULL) {
8610 compose->privacy_system = g_strdup(systemid);
8612 can_sign = privacy_system_can_sign(systemid);
8613 can_encrypt = privacy_system_can_encrypt(systemid);
8616 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8618 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8619 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8620 if (compose->toolbar->privacy_sign_btn != NULL) {
8621 gtk_widget_set_sensitive(
8622 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8624 gtk_toggle_tool_button_set_active(
8625 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8626 can_sign ? compose->use_signing : FALSE);
8628 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8629 gtk_widget_set_sensitive(
8630 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8632 gtk_toggle_tool_button_set_active(
8633 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8634 can_encrypt ? compose->use_encryption : FALSE);
8638 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8640 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8641 GtkWidget *menuitem = NULL;
8642 GList *children, *amenu;
8643 gboolean can_sign = FALSE, can_encrypt = FALSE;
8644 gboolean found = FALSE;
8646 if (compose->privacy_system != NULL) {
8648 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8649 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8650 cm_return_if_fail(menuitem != NULL);
8652 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8655 while (amenu != NULL) {
8656 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8657 if (systemid != NULL) {
8658 if (strcmp(systemid, compose->privacy_system) == 0 &&
8659 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8660 menuitem = GTK_WIDGET(amenu->data);
8662 can_sign = privacy_system_can_sign(systemid);
8663 can_encrypt = privacy_system_can_encrypt(systemid);
8667 } else if (strlen(compose->privacy_system) == 0 &&
8668 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8669 menuitem = GTK_WIDGET(amenu->data);
8672 can_encrypt = FALSE;
8677 amenu = amenu->next;
8679 g_list_free(children);
8680 if (menuitem != NULL)
8681 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8683 if (warn && !found && strlen(compose->privacy_system)) {
8684 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8685 "will not be able to sign or encrypt this message."),
8686 compose->privacy_system);
8690 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8691 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8692 if (compose->toolbar->privacy_sign_btn != NULL) {
8693 gtk_widget_set_sensitive(
8694 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8697 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8698 gtk_widget_set_sensitive(
8699 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8704 static void compose_set_out_encoding(Compose *compose)
8706 CharSet out_encoding;
8707 const gchar *branch = NULL;
8708 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8710 switch(out_encoding) {
8711 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8712 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8713 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8714 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8715 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8716 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8717 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8718 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8719 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8720 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8721 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8722 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8723 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8724 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8725 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8726 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8727 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8728 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8729 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8730 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8731 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8732 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8733 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8734 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8735 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8736 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8737 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8738 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8739 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8740 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8741 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8742 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8743 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8744 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8746 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8749 static void compose_set_template_menu(Compose *compose)
8751 GSList *tmpl_list, *cur;
8755 tmpl_list = template_get_config();
8757 menu = gtk_menu_new();
8759 gtk_menu_set_accel_group (GTK_MENU (menu),
8760 gtk_ui_manager_get_accel_group(compose->ui_manager));
8761 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8762 Template *tmpl = (Template *)cur->data;
8763 gchar *accel_path = NULL;
8764 item = gtk_menu_item_new_with_label(tmpl->name);
8765 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8766 g_signal_connect(G_OBJECT(item), "activate",
8767 G_CALLBACK(compose_template_activate_cb),
8769 g_object_set_data(G_OBJECT(item), "template", tmpl);
8770 gtk_widget_show(item);
8771 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8772 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8776 gtk_widget_show(menu);
8777 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8780 void compose_update_actions_menu(Compose *compose)
8782 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8785 static void compose_update_privacy_systems_menu(Compose *compose)
8787 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8788 GSList *systems, *cur;
8790 GtkWidget *system_none;
8792 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8793 GtkWidget *privacy_menu = gtk_menu_new();
8795 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8796 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8798 g_signal_connect(G_OBJECT(system_none), "activate",
8799 G_CALLBACK(compose_set_privacy_system_cb), compose);
8801 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8802 gtk_widget_show(system_none);
8804 systems = privacy_get_system_ids();
8805 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8806 gchar *systemid = cur->data;
8808 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8809 widget = gtk_radio_menu_item_new_with_label(group,
8810 privacy_system_get_name(systemid));
8811 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8812 g_strdup(systemid), g_free);
8813 g_signal_connect(G_OBJECT(widget), "activate",
8814 G_CALLBACK(compose_set_privacy_system_cb), compose);
8816 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8817 gtk_widget_show(widget);
8820 g_slist_free(systems);
8821 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8822 gtk_widget_show_all(privacy_menu);
8823 gtk_widget_show_all(privacy_menuitem);
8826 void compose_reflect_prefs_all(void)
8831 for (cur = compose_list; cur != NULL; cur = cur->next) {
8832 compose = (Compose *)cur->data;
8833 compose_set_template_menu(compose);
8837 void compose_reflect_prefs_pixmap_theme(void)
8842 for (cur = compose_list; cur != NULL; cur = cur->next) {
8843 compose = (Compose *)cur->data;
8844 toolbar_update(TOOLBAR_COMPOSE, compose);
8848 static const gchar *compose_quote_char_from_context(Compose *compose)
8850 const gchar *qmark = NULL;
8852 cm_return_val_if_fail(compose != NULL, NULL);
8854 switch (compose->mode) {
8855 /* use forward-specific quote char */
8856 case COMPOSE_FORWARD:
8857 case COMPOSE_FORWARD_AS_ATTACH:
8858 case COMPOSE_FORWARD_INLINE:
8859 if (compose->folder && compose->folder->prefs &&
8860 compose->folder->prefs->forward_with_format)
8861 qmark = compose->folder->prefs->forward_quotemark;
8862 else if (compose->account->forward_with_format)
8863 qmark = compose->account->forward_quotemark;
8865 qmark = prefs_common.fw_quotemark;
8868 /* use reply-specific quote char in all other modes */
8870 if (compose->folder && compose->folder->prefs &&
8871 compose->folder->prefs->reply_with_format)
8872 qmark = compose->folder->prefs->reply_quotemark;
8873 else if (compose->account->reply_with_format)
8874 qmark = compose->account->reply_quotemark;
8876 qmark = prefs_common.quotemark;
8880 if (qmark == NULL || *qmark == '\0')
8886 static void compose_template_apply(Compose *compose, Template *tmpl,
8890 GtkTextBuffer *buffer;
8894 gchar *parsed_str = NULL;
8895 gint cursor_pos = 0;
8896 const gchar *err_msg = _("The body of the template has an error at line %d.");
8899 /* process the body */
8901 text = GTK_TEXT_VIEW(compose->text);
8902 buffer = gtk_text_view_get_buffer(text);
8905 qmark = compose_quote_char_from_context(compose);
8907 if (compose->replyinfo != NULL) {
8910 gtk_text_buffer_set_text(buffer, "", -1);
8911 mark = gtk_text_buffer_get_insert(buffer);
8912 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8914 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8915 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8917 } else if (compose->fwdinfo != NULL) {
8920 gtk_text_buffer_set_text(buffer, "", -1);
8921 mark = gtk_text_buffer_get_insert(buffer);
8922 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8924 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8925 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8928 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8930 GtkTextIter start, end;
8933 gtk_text_buffer_get_start_iter(buffer, &start);
8934 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8935 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8937 /* clear the buffer now */
8939 gtk_text_buffer_set_text(buffer, "", -1);
8941 parsed_str = compose_quote_fmt(compose, dummyinfo,
8942 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8943 procmsg_msginfo_free( &dummyinfo );
8949 gtk_text_buffer_set_text(buffer, "", -1);
8950 mark = gtk_text_buffer_get_insert(buffer);
8951 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8954 if (replace && parsed_str && compose->account->auto_sig)
8955 compose_insert_sig(compose, FALSE);
8957 if (replace && parsed_str) {
8958 gtk_text_buffer_get_start_iter(buffer, &iter);
8959 gtk_text_buffer_place_cursor(buffer, &iter);
8963 cursor_pos = quote_fmt_get_cursor_pos();
8964 compose->set_cursor_pos = cursor_pos;
8965 if (cursor_pos == -1)
8967 gtk_text_buffer_get_start_iter(buffer, &iter);
8968 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8969 gtk_text_buffer_place_cursor(buffer, &iter);
8972 /* process the other fields */
8974 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8975 compose_template_apply_fields(compose, tmpl);
8976 quote_fmt_reset_vartable();
8977 quote_fmtlex_destroy();
8979 compose_changed_cb(NULL, compose);
8982 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8983 gtkaspell_highlight_all(compose->gtkaspell);
8987 static void compose_template_apply_fields_error(const gchar *header)
8992 tr = g_strdup(C_("'%s' stands for a header name",
8993 "Template '%s' format error."));
8994 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8995 alertpanel_error("%s", text);
9001 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
9003 MsgInfo* dummyinfo = NULL;
9004 MsgInfo *msginfo = NULL;
9007 if (compose->replyinfo != NULL)
9008 msginfo = compose->replyinfo;
9009 else if (compose->fwdinfo != NULL)
9010 msginfo = compose->fwdinfo;
9012 dummyinfo = compose_msginfo_new_from_compose(compose);
9013 msginfo = dummyinfo;
9016 if (tmpl->from && *tmpl->from != '\0') {
9018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9019 compose->gtkaspell);
9021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9023 quote_fmt_scan_string(tmpl->from);
9026 buf = quote_fmt_get_buffer();
9028 compose_template_apply_fields_error("From");
9030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9033 quote_fmt_reset_vartable();
9034 quote_fmtlex_destroy();
9037 if (tmpl->to && *tmpl->to != '\0') {
9039 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9040 compose->gtkaspell);
9042 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9044 quote_fmt_scan_string(tmpl->to);
9047 buf = quote_fmt_get_buffer();
9049 compose_template_apply_fields_error("To");
9051 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9054 quote_fmt_reset_vartable();
9055 quote_fmtlex_destroy();
9058 if (tmpl->cc && *tmpl->cc != '\0') {
9060 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9061 compose->gtkaspell);
9063 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9065 quote_fmt_scan_string(tmpl->cc);
9068 buf = quote_fmt_get_buffer();
9070 compose_template_apply_fields_error("Cc");
9072 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9075 quote_fmt_reset_vartable();
9076 quote_fmtlex_destroy();
9079 if (tmpl->bcc && *tmpl->bcc != '\0') {
9081 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9082 compose->gtkaspell);
9084 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9086 quote_fmt_scan_string(tmpl->bcc);
9089 buf = quote_fmt_get_buffer();
9091 compose_template_apply_fields_error("Bcc");
9093 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9096 quote_fmt_reset_vartable();
9097 quote_fmtlex_destroy();
9100 if (tmpl->replyto && *tmpl->replyto != '\0') {
9102 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9103 compose->gtkaspell);
9105 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9107 quote_fmt_scan_string(tmpl->replyto);
9110 buf = quote_fmt_get_buffer();
9112 compose_template_apply_fields_error("Reply-To");
9114 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9117 quote_fmt_reset_vartable();
9118 quote_fmtlex_destroy();
9121 /* process the subject */
9122 if (tmpl->subject && *tmpl->subject != '\0') {
9124 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9125 compose->gtkaspell);
9127 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9129 quote_fmt_scan_string(tmpl->subject);
9132 buf = quote_fmt_get_buffer();
9134 compose_template_apply_fields_error("Subject");
9136 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9139 quote_fmt_reset_vartable();
9140 quote_fmtlex_destroy();
9143 procmsg_msginfo_free( &dummyinfo );
9146 static void compose_destroy(Compose *compose)
9148 GtkAllocation allocation;
9149 GtkTextBuffer *buffer;
9150 GtkClipboard *clipboard;
9152 compose_list = g_list_remove(compose_list, compose);
9155 gboolean enable = TRUE;
9156 g_slist_foreach(compose->passworded_ldap_servers,
9157 _ldap_srv_func, &enable);
9158 g_slist_free(compose->passworded_ldap_servers);
9161 if (compose->updating) {
9162 debug_print("danger, not destroying anything now\n");
9163 compose->deferred_destroy = TRUE;
9167 /* NOTE: address_completion_end() does nothing with the window
9168 * however this may change. */
9169 address_completion_end(compose->window);
9171 slist_free_strings_full(compose->to_list);
9172 slist_free_strings_full(compose->newsgroup_list);
9173 slist_free_strings_full(compose->header_list);
9175 slist_free_strings_full(extra_headers);
9176 extra_headers = NULL;
9178 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9180 g_hash_table_destroy(compose->email_hashtable);
9182 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9183 compose->folder_update_callback_id);
9185 procmsg_msginfo_free(&(compose->targetinfo));
9186 procmsg_msginfo_free(&(compose->replyinfo));
9187 procmsg_msginfo_free(&(compose->fwdinfo));
9189 g_free(compose->replyto);
9190 g_free(compose->cc);
9191 g_free(compose->bcc);
9192 g_free(compose->newsgroups);
9193 g_free(compose->followup_to);
9195 g_free(compose->ml_post);
9197 g_free(compose->inreplyto);
9198 g_free(compose->references);
9199 g_free(compose->msgid);
9200 g_free(compose->boundary);
9202 g_free(compose->redirect_filename);
9203 if (compose->undostruct)
9204 undo_destroy(compose->undostruct);
9206 g_free(compose->sig_str);
9208 g_free(compose->exteditor_file);
9210 g_free(compose->orig_charset);
9212 g_free(compose->privacy_system);
9213 g_free(compose->encdata);
9215 #ifndef USE_ALT_ADDRBOOK
9216 if (addressbook_get_target_compose() == compose)
9217 addressbook_set_target_compose(NULL);
9220 if (compose->gtkaspell) {
9221 gtkaspell_delete(compose->gtkaspell);
9222 compose->gtkaspell = NULL;
9226 if (!compose->batch) {
9227 gtk_widget_get_allocation(GTK_WIDGET(compose->window),
9229 prefs_common.compose_width = allocation.width;
9230 prefs_common.compose_height = allocation.height;
9233 if (!gtk_widget_get_parent(compose->paned))
9234 gtk_widget_destroy(compose->paned);
9235 gtk_widget_destroy(compose->popupmenu);
9237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9238 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9239 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9241 message_search_close(compose);
9242 gtk_widget_destroy(compose->window);
9243 toolbar_destroy(compose->toolbar);
9244 g_free(compose->toolbar);
9245 g_mutex_clear(&compose->mutex);
9249 static void compose_attach_info_free(AttachInfo *ainfo)
9251 g_free(ainfo->file);
9252 g_free(ainfo->content_type);
9253 g_free(ainfo->name);
9254 g_free(ainfo->charset);
9258 static void compose_attach_update_label(Compose *compose)
9263 GtkTreeModel *model;
9267 if (compose == NULL)
9270 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9271 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9272 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9276 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9277 total_size = ainfo->size;
9278 while(gtk_tree_model_iter_next(model, &iter)) {
9279 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9280 total_size += ainfo->size;
9283 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9284 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9288 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9290 Compose *compose = (Compose *)data;
9291 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9292 GtkTreeSelection *selection;
9294 GtkTreeModel *model;
9296 selection = gtk_tree_view_get_selection(tree_view);
9297 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9298 cm_return_if_fail(sel);
9300 for (cur = sel; cur != NULL; cur = cur->next) {
9301 GtkTreePath *path = cur->data;
9302 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9305 gtk_tree_path_free(path);
9308 for (cur = sel; cur != NULL; cur = cur->next) {
9309 GtkTreeRowReference *ref = cur->data;
9310 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9313 if (gtk_tree_model_get_iter(model, &iter, path))
9314 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9316 gtk_tree_path_free(path);
9317 gtk_tree_row_reference_free(ref);
9321 compose_attach_update_label(compose);
9324 static struct _AttachProperty
9327 GtkWidget *mimetype_entry;
9328 GtkWidget *encoding_optmenu;
9329 GtkWidget *path_entry;
9330 GtkWidget *filename_entry;
9332 GtkWidget *cancel_btn;
9335 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9337 gtk_tree_path_free((GtkTreePath *)ptr);
9340 static void compose_attach_property(GtkAction *action, gpointer data)
9342 Compose *compose = (Compose *)data;
9343 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9345 GtkComboBox *optmenu;
9346 GtkTreeSelection *selection;
9348 GtkTreeModel *model;
9351 static gboolean cancelled;
9353 /* only if one selected */
9354 selection = gtk_tree_view_get_selection(tree_view);
9355 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9358 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9359 cm_return_if_fail(sel);
9361 path = (GtkTreePath *) sel->data;
9362 gtk_tree_model_get_iter(model, &iter, path);
9363 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9366 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9372 if (!attach_prop.window)
9373 compose_attach_property_create(&cancelled);
9374 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9375 gtk_widget_grab_focus(attach_prop.ok_btn);
9376 gtk_widget_show(attach_prop.window);
9377 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9378 GTK_WINDOW(compose->window));
9380 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9381 if (ainfo->encoding == ENC_UNKNOWN)
9382 combobox_select_by_data(optmenu, ENC_BASE64);
9384 combobox_select_by_data(optmenu, ainfo->encoding);
9386 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9387 ainfo->content_type ? ainfo->content_type : "");
9388 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9389 ainfo->file ? ainfo->file : "");
9390 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9391 ainfo->name ? ainfo->name : "");
9394 const gchar *entry_text;
9396 gchar *cnttype = NULL;
9403 gtk_widget_hide(attach_prop.window);
9404 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9409 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9410 if (*entry_text != '\0') {
9413 text = g_strstrip(g_strdup(entry_text));
9414 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9415 cnttype = g_strdup(text);
9418 alertpanel_error(_("Invalid MIME type."));
9424 ainfo->encoding = combobox_get_active_data(optmenu);
9426 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9427 if (*entry_text != '\0') {
9428 if (is_file_exist(entry_text) &&
9429 (size = get_file_size(entry_text)) > 0)
9430 file = g_strdup(entry_text);
9433 (_("File doesn't exist or is empty."));
9439 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9440 if (*entry_text != '\0') {
9441 g_free(ainfo->name);
9442 ainfo->name = g_strdup(entry_text);
9446 g_free(ainfo->content_type);
9447 ainfo->content_type = cnttype;
9450 g_free(ainfo->file);
9454 ainfo->size = (goffset)size;
9456 /* update tree store */
9457 text = to_human_readable(ainfo->size);
9458 gtk_tree_model_get_iter(model, &iter, path);
9459 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9460 COL_MIMETYPE, ainfo->content_type,
9462 COL_NAME, ainfo->name,
9463 COL_CHARSET, ainfo->charset,
9469 gtk_tree_path_free(path);
9472 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9474 label = gtk_label_new(str); \
9475 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9476 GTK_FILL, 0, 0, 0); \
9477 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9479 entry = gtk_entry_new(); \
9480 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9481 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9484 static void compose_attach_property_create(gboolean *cancelled)
9490 GtkWidget *mimetype_entry;
9493 GtkListStore *optmenu_menu;
9494 GtkWidget *path_entry;
9495 GtkWidget *filename_entry;
9498 GtkWidget *cancel_btn;
9499 GList *mime_type_list, *strlist;
9502 debug_print("Creating attach_property window...\n");
9504 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9505 gtk_widget_set_size_request(window, 480, -1);
9506 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9507 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9508 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9509 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9510 g_signal_connect(G_OBJECT(window), "delete_event",
9511 G_CALLBACK(attach_property_delete_event),
9513 g_signal_connect(G_OBJECT(window), "key_press_event",
9514 G_CALLBACK(attach_property_key_pressed),
9517 vbox = gtk_vbox_new(FALSE, 8);
9518 gtk_container_add(GTK_CONTAINER(window), vbox);
9520 table = gtk_table_new(4, 2, FALSE);
9521 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9522 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9523 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9525 label = gtk_label_new(_("MIME type"));
9526 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9528 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9529 mimetype_entry = gtk_combo_box_text_new_with_entry();
9530 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9531 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9533 /* stuff with list */
9534 mime_type_list = procmime_get_mime_type_list();
9536 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9537 MimeType *type = (MimeType *) mime_type_list->data;
9540 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9542 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9545 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9546 (GCompareFunc)g_strcmp0);
9549 for (mime_type_list = strlist; mime_type_list != NULL;
9550 mime_type_list = mime_type_list->next) {
9551 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9552 g_free(mime_type_list->data);
9554 g_list_free(strlist);
9555 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9556 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9558 label = gtk_label_new(_("Encoding"));
9559 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9561 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9563 hbox = gtk_hbox_new(FALSE, 0);
9564 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9565 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9567 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9568 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9570 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9571 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9572 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9573 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9574 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9576 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9578 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9579 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9581 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9582 &ok_btn, GTK_STOCK_OK,
9584 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9585 gtk_widget_grab_default(ok_btn);
9587 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9588 G_CALLBACK(attach_property_ok),
9590 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9591 G_CALLBACK(attach_property_cancel),
9594 gtk_widget_show_all(vbox);
9596 attach_prop.window = window;
9597 attach_prop.mimetype_entry = mimetype_entry;
9598 attach_prop.encoding_optmenu = optmenu;
9599 attach_prop.path_entry = path_entry;
9600 attach_prop.filename_entry = filename_entry;
9601 attach_prop.ok_btn = ok_btn;
9602 attach_prop.cancel_btn = cancel_btn;
9605 #undef SET_LABEL_AND_ENTRY
9607 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9613 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9619 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9620 gboolean *cancelled)
9628 static gboolean attach_property_key_pressed(GtkWidget *widget,
9630 gboolean *cancelled)
9632 if (event && event->keyval == GDK_KEY_Escape) {
9636 if (event && event->keyval == GDK_KEY_Return) {
9644 static gboolean compose_can_autosave(Compose *compose)
9646 if (compose->privacy_system && compose->use_encryption)
9647 return prefs_common.autosave && prefs_common.autosave_encrypted;
9649 return prefs_common.autosave;
9653 * compose_exec_ext_editor:
9655 * Open (and optionally embed) external editor
9657 static void compose_exec_ext_editor(Compose *compose)
9662 GdkNativeWindow socket_wid = 0;
9664 #endif /* G_OS_WIN32 */
9666 GError *error = NULL;
9670 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9671 G_DIR_SEPARATOR, compose);
9673 if (compose_write_body_to_file(compose, tmp) < 0) {
9674 alertpanel_error(_("Could not write the body to file:\n%s"),
9680 if (compose_get_ext_editor_uses_socket()) {
9682 /* Only allow one socket */
9683 if (compose->exteditor_socket != NULL) {
9684 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9685 /* Move the focus off of the socket */
9686 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9691 /* Create the receiving GtkSocket */
9692 socket = gtk_socket_new ();
9693 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9694 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9696 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9697 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9698 /* Realize the socket so that we can use its ID */
9699 gtk_widget_realize(socket);
9700 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9701 compose->exteditor_socket = socket;
9702 #endif /* G_OS_WIN32 */
9705 if (compose_get_ext_editor_cmd_valid()) {
9706 if (compose_get_ext_editor_uses_socket()) {
9708 p = g_strdup(prefs_common_get_ext_editor_cmd());
9709 s = strstr(p, "%w");
9711 if (strstr(p, "%s") < s)
9712 cmd = g_strdup_printf(p, tmp, socket_wid);
9714 cmd = g_strdup_printf(p, socket_wid, tmp);
9716 #endif /* G_OS_WIN32 */
9718 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9721 if (prefs_common_get_ext_editor_cmd())
9722 g_warning("external editor command-line is invalid: '%s'",
9723 prefs_common_get_ext_editor_cmd());
9724 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9727 argv = strsplit_with_quote(cmd, " ", 0);
9729 if (!g_spawn_async(NULL, argv, NULL,
9730 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9731 NULL, NULL, &pid, &error)) {
9732 alertpanel_error(_("Could not spawn the following "
9733 "command:\n%s\n%s"),
9734 cmd, error ? error->message : _("Unknown error"));
9736 g_error_free(error);
9745 compose->exteditor_file = g_strdup(tmp);
9746 compose->exteditor_pid = pid;
9747 compose->exteditor_tag = g_child_watch_add(pid,
9748 compose_ext_editor_closed_cb,
9751 compose_set_ext_editor_sensitive(compose, FALSE);
9757 * compose_ext_editor_cb:
9759 * External editor has closed (called by g_child_watch)
9761 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9763 Compose *compose = (Compose *)data;
9764 GError *error = NULL;
9765 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9766 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9767 GtkTextIter start, end;
9770 if (!g_spawn_check_exit_status(exit_status, &error)) {
9772 _("External editor stopped with an error: %s"),
9773 error ? error->message : _("Unknown error"));
9775 g_error_free(error);
9777 g_spawn_close_pid(compose->exteditor_pid);
9779 gtk_text_buffer_set_text(buffer, "", -1);
9780 compose_insert_file(compose, compose->exteditor_file);
9781 compose_changed_cb(NULL, compose);
9783 /* Check if we should save the draft or not */
9784 if (compose_can_autosave(compose))
9785 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9787 if (claws_unlink(compose->exteditor_file) < 0)
9788 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9790 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9791 gtk_text_buffer_get_start_iter(buffer, &start);
9792 gtk_text_buffer_get_end_iter(buffer, &end);
9793 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9794 if (chars && strlen(chars) > 0)
9795 compose->modified = TRUE;
9798 compose_set_ext_editor_sensitive(compose, TRUE);
9800 g_free(compose->exteditor_file);
9801 compose->exteditor_file = NULL;
9802 compose->exteditor_pid = INVALID_PID;
9803 compose->exteditor_tag = -1;
9804 if (compose->exteditor_socket) {
9805 gtk_widget_destroy(compose->exteditor_socket);
9806 compose->exteditor_socket = NULL;
9811 static gboolean compose_get_ext_editor_cmd_valid()
9813 gboolean has_s = FALSE;
9814 gboolean has_w = FALSE;
9815 const gchar *p = prefs_common_get_ext_editor_cmd();
9818 while ((p = strchr(p, '%'))) {
9824 } else if (*p == 'w') {
9835 static gboolean compose_ext_editor_kill(Compose *compose)
9837 GPid pid = compose->exteditor_pid;
9843 msg = g_strdup_printf
9844 (_("The external editor is still working.\n"
9845 "Force terminating the process?\n"
9846 "process id: %" G_PID_FORMAT), pid);
9847 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO,
9848 GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST,
9849 FALSE, NULL, ALERT_WARNING);
9852 if (val == G_ALERTALTERNATE) {
9853 g_source_remove(compose->exteditor_tag);
9856 if (!TerminateProcess(compose->exteditor_pid, 0))
9857 perror("TerminateProcess");
9859 if (kill(pid, SIGTERM) < 0) perror("kill");
9860 waitpid(compose->exteditor_pid, NULL, 0);
9861 #endif /* G_OS_WIN32 */
9863 g_warning("terminated process id: %" G_PID_FORMAT ", "
9864 "temporary file: %s", pid, compose->exteditor_file);
9865 g_spawn_close_pid(compose->exteditor_pid);
9867 compose_set_ext_editor_sensitive(compose, TRUE);
9869 g_free(compose->exteditor_file);
9870 compose->exteditor_file = NULL;
9871 compose->exteditor_pid = INVALID_PID;
9872 compose->exteditor_tag = -1;
9880 static char *ext_editor_menu_entries[] = {
9881 "Menu/Message/Send",
9882 "Menu/Message/SendLater",
9883 "Menu/Message/InsertFile",
9884 "Menu/Message/InsertSig",
9885 "Menu/Message/ReplaceSig",
9886 "Menu/Message/Save",
9887 "Menu/Message/Print",
9892 "Menu/Tools/ShowRuler",
9893 "Menu/Tools/Actions",
9898 static void compose_set_ext_editor_sensitive(Compose *compose,
9903 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9904 cm_menu_set_sensitive_full(compose->ui_manager,
9905 ext_editor_menu_entries[i], sensitive);
9908 if (compose_get_ext_editor_uses_socket()) {
9910 if (compose->exteditor_socket)
9911 gtk_widget_hide(compose->exteditor_socket);
9912 gtk_widget_show(compose->scrolledwin);
9913 if (prefs_common.show_ruler)
9914 gtk_widget_show(compose->ruler_hbox);
9915 /* Fix the focus, as it doesn't go anywhere when the
9916 * socket is hidden or destroyed */
9917 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9919 g_assert (compose->exteditor_socket != NULL);
9920 /* Fix the focus, as it doesn't go anywhere when the
9921 * edit box is hidden */
9922 if (gtk_widget_is_focus(compose->text))
9923 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9924 gtk_widget_hide(compose->scrolledwin);
9925 gtk_widget_hide(compose->ruler_hbox);
9926 gtk_widget_show(compose->exteditor_socket);
9929 gtk_widget_set_sensitive(compose->text, sensitive);
9931 if (compose->toolbar->send_btn)
9932 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9933 if (compose->toolbar->sendl_btn)
9934 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9935 if (compose->toolbar->draft_btn)
9936 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9937 if (compose->toolbar->insert_btn)
9938 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9939 if (compose->toolbar->sig_btn)
9940 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9941 if (compose->toolbar->exteditor_btn)
9942 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9943 if (compose->toolbar->linewrap_current_btn)
9944 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9945 if (compose->toolbar->linewrap_all_btn)
9946 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9949 static gboolean compose_get_ext_editor_uses_socket()
9951 return (prefs_common_get_ext_editor_cmd() &&
9952 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9956 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9958 compose->exteditor_socket = NULL;
9959 /* returning FALSE allows destruction of the socket */
9962 #endif /* G_OS_WIN32 */
9965 * compose_undo_state_changed:
9967 * Change the sensivity of the menuentries undo and redo
9969 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9970 gint redo_state, gpointer data)
9972 Compose *compose = (Compose *)data;
9974 switch (undo_state) {
9975 case UNDO_STATE_TRUE:
9976 if (!undostruct->undo_state) {
9977 undostruct->undo_state = TRUE;
9978 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9981 case UNDO_STATE_FALSE:
9982 if (undostruct->undo_state) {
9983 undostruct->undo_state = FALSE;
9984 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9987 case UNDO_STATE_UNCHANGED:
9989 case UNDO_STATE_REFRESH:
9990 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9993 g_warning("undo state not recognized");
9997 switch (redo_state) {
9998 case UNDO_STATE_TRUE:
9999 if (!undostruct->redo_state) {
10000 undostruct->redo_state = TRUE;
10001 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
10004 case UNDO_STATE_FALSE:
10005 if (undostruct->redo_state) {
10006 undostruct->redo_state = FALSE;
10007 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
10010 case UNDO_STATE_UNCHANGED:
10012 case UNDO_STATE_REFRESH:
10013 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
10016 g_warning("redo state not recognized");
10021 /* callback functions */
10023 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10024 GtkAllocation *allocation,
10027 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10030 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10031 * includes "non-client" (windows-izm) in calculation, so this calculation
10032 * may not be accurate.
10034 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10035 GtkAllocation *allocation,
10036 GtkSHRuler *shruler)
10038 if (prefs_common.show_ruler) {
10039 gint char_width = 0, char_height = 0;
10040 gint line_width_in_chars;
10042 gtkut_get_font_size(GTK_WIDGET(widget),
10043 &char_width, &char_height);
10044 line_width_in_chars =
10045 (allocation->width - allocation->x) / char_width;
10047 /* got the maximum */
10048 gtk_shruler_set_range(GTK_SHRULER(shruler),
10049 0.0, line_width_in_chars, 0);
10058 ComposePrefType type;
10059 gboolean entry_marked;
10060 } HeaderEntryState;
10062 static void account_activated(GtkComboBox *optmenu, gpointer data)
10064 Compose *compose = (Compose *)data;
10067 gchar *folderidentifier;
10068 gint account_id = 0;
10069 GtkTreeModel *menu;
10071 GSList *list, *saved_list = NULL;
10072 HeaderEntryState *state;
10074 /* Get ID of active account in the combo box */
10075 menu = gtk_combo_box_get_model(optmenu);
10076 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10077 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10079 ac = account_find_from_id(account_id);
10080 cm_return_if_fail(ac != NULL);
10082 if (ac != compose->account) {
10083 compose_select_account(compose, ac, FALSE);
10085 for (list = compose->header_list; list; list = list->next) {
10086 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10088 if (hentry->type == PREF_ACCOUNT || !list->next) {
10089 compose_destroy_headerentry(compose, hentry);
10092 state = g_malloc0(sizeof(HeaderEntryState));
10093 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10094 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10095 state->entry = gtk_editable_get_chars(
10096 GTK_EDITABLE(hentry->entry), 0, -1);
10097 state->type = hentry->type;
10099 saved_list = g_slist_append(saved_list, state);
10100 compose_destroy_headerentry(compose, hentry);
10103 compose->header_last = NULL;
10104 g_slist_free(compose->header_list);
10105 compose->header_list = NULL;
10106 compose->header_nextrow = 1;
10107 compose_create_header_entry(compose);
10109 if (ac->set_autocc && ac->auto_cc)
10110 compose_entry_append(compose, ac->auto_cc,
10111 COMPOSE_CC, PREF_ACCOUNT);
10112 if (ac->set_autobcc && ac->auto_bcc)
10113 compose_entry_append(compose, ac->auto_bcc,
10114 COMPOSE_BCC, PREF_ACCOUNT);
10115 if (ac->set_autoreplyto && ac->auto_replyto)
10116 compose_entry_append(compose, ac->auto_replyto,
10117 COMPOSE_REPLYTO, PREF_ACCOUNT);
10119 for (list = saved_list; list; list = list->next) {
10120 state = (HeaderEntryState *) list->data;
10122 compose_add_header_entry(compose, state->header,
10123 state->entry, state->type);
10125 g_free(state->header);
10126 g_free(state->entry);
10129 g_slist_free(saved_list);
10131 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10132 (ac->protocol == A_NNTP) ?
10133 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10136 /* Set message save folder */
10137 compose_set_save_to(compose, NULL);
10138 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10139 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10140 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10141 folderidentifier = folder_item_get_identifier(compose->folder);
10142 compose_set_save_to(compose, folderidentifier);
10143 g_free(folderidentifier);
10144 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10145 if (compose->account->set_sent_folder || prefs_common.savemsg)
10146 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10148 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10149 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10150 folderidentifier = folder_item_get_identifier(account_get_special_folder
10151 (compose->account, F_OUTBOX));
10152 compose_set_save_to(compose, folderidentifier);
10153 g_free(folderidentifier);
10157 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10158 GtkTreeViewColumn *column, Compose *compose)
10160 compose_attach_property(NULL, compose);
10163 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10166 Compose *compose = (Compose *)data;
10167 GtkTreeSelection *attach_selection;
10168 gint attach_nr_selected;
10171 if (!event) return FALSE;
10173 if (event->button == 3) {
10174 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10175 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10177 /* If no rows, or just one row is selected, right-click should
10178 * open menu relevant to the row being right-clicked on. We
10179 * achieve that by selecting the clicked row first. If more
10180 * than one row is selected, we shouldn't modify the selection,
10181 * as user may want to remove selected rows (attachments). */
10182 if (attach_nr_selected < 2) {
10183 gtk_tree_selection_unselect_all(attach_selection);
10184 attach_nr_selected = 0;
10185 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10186 event->x, event->y, &path, NULL, NULL, NULL);
10187 if (path != NULL) {
10188 gtk_tree_selection_select_path(attach_selection, path);
10189 gtk_tree_path_free(path);
10190 attach_nr_selected++;
10194 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10195 /* Properties menu item makes no sense with more than one row
10196 * selected, the properties dialog can only edit one attachment. */
10197 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10199 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10200 NULL, NULL, event->button, event->time);
10207 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10210 Compose *compose = (Compose *)data;
10212 if (!event) return FALSE;
10214 switch (event->keyval) {
10215 case GDK_KEY_Delete:
10216 compose_attach_remove_selected(NULL, compose);
10222 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10224 toolbar_comp_set_sensitive(compose, allow);
10225 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10226 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10228 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10230 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10231 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10232 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10234 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10238 static void compose_send_cb(GtkAction *action, gpointer data)
10240 Compose *compose = (Compose *)data;
10243 if (compose->exteditor_tag != -1) {
10244 debug_print("ignoring send: external editor still open\n");
10248 if (prefs_common.work_offline &&
10249 !inc_offline_should_override(TRUE,
10250 _("Claws Mail needs network access in order "
10251 "to send this email.")))
10254 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10255 g_source_remove(compose->draft_timeout_tag);
10256 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10259 compose_send(compose);
10262 static void compose_send_later_cb(GtkAction *action, gpointer data)
10264 Compose *compose = (Compose *)data;
10265 ComposeQueueResult val;
10268 compose_allow_user_actions(compose, FALSE);
10269 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10270 compose_allow_user_actions(compose, TRUE);
10273 if (val == COMPOSE_QUEUE_SUCCESS) {
10274 compose_close(compose);
10276 _display_queue_error(val);
10279 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10282 #define DRAFTED_AT_EXIT "drafted_at_exit"
10283 static void compose_register_draft(MsgInfo *info)
10285 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10286 DRAFTED_AT_EXIT, NULL);
10287 FILE *fp = claws_fopen(filepath, "ab");
10290 gchar *name = folder_item_get_identifier(info->folder);
10291 fprintf(fp, "%s\t%d\n", name, info->msgnum);
10299 gboolean compose_draft (gpointer data, guint action)
10301 Compose *compose = (Compose *)data;
10303 FolderItemPrefs *prefs;
10307 MsgFlags flag = {0, 0};
10308 static gboolean lock = FALSE;
10309 MsgInfo *newmsginfo;
10311 gboolean target_locked = FALSE;
10312 gboolean err = FALSE;
10315 if (lock) return FALSE;
10317 if (compose->sending)
10320 draft = account_get_special_folder(compose->account, F_DRAFT);
10321 cm_return_val_if_fail(draft != NULL, FALSE);
10323 if (!g_mutex_trylock(&compose->mutex)) {
10324 /* we don't want to lock the mutex once it's available,
10325 * because as the only other part of compose.c locking
10326 * it is compose_close - which means once unlocked,
10327 * the compose struct will be freed */
10328 debug_print("couldn't lock mutex, probably sending\n");
10334 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10335 G_DIR_SEPARATOR, compose);
10336 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10337 FILE_OP_ERROR(tmp, "claws_fopen");
10341 /* chmod for security unless folder chmod is set */
10342 prefs = draft->prefs;
10343 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10344 filemode = prefs->folder_chmod;
10345 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10346 if (filemode & S_IROTH) filemode |= S_IWOTH;
10347 if (chmod(tmp, filemode) < 0)
10348 FILE_OP_ERROR(tmp, "chmod");
10349 } else if (change_file_mode_rw(fp, tmp) < 0) {
10350 FILE_OP_ERROR(tmp, "chmod");
10351 g_warning("can't change file mode");
10354 /* Save draft infos */
10355 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10356 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10358 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10359 gchar *savefolderid;
10361 savefolderid = compose_get_save_to(compose);
10362 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10363 g_free(savefolderid);
10365 if (compose->return_receipt) {
10366 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10368 if (compose->privacy_system) {
10369 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10370 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10371 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10374 /* Message-ID of message replying to */
10375 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10376 gchar *folderid = NULL;
10378 if (compose->replyinfo->folder)
10379 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10380 if (folderid == NULL)
10381 folderid = g_strdup("NULL");
10383 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10386 /* Message-ID of message forwarding to */
10387 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10388 gchar *folderid = NULL;
10390 if (compose->fwdinfo->folder)
10391 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10392 if (folderid == NULL)
10393 folderid = g_strdup("NULL");
10395 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10399 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10400 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10402 sheaders = compose_get_manual_headers_info(compose);
10403 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10406 /* end of headers */
10407 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10414 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10418 if (claws_safe_fclose(fp) == EOF) {
10422 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10423 if (compose->targetinfo) {
10424 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10426 flag.perm_flags |= MSG_LOCKED;
10428 flag.tmp_flags = MSG_DRAFT;
10430 folder_item_scan(draft);
10431 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10432 MsgInfo *tmpinfo = NULL;
10433 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10434 if (compose->msgid) {
10435 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10438 msgnum = tmpinfo->msgnum;
10439 procmsg_msginfo_free(&tmpinfo);
10440 debug_print("got draft msgnum %d from scanning\n", msgnum);
10442 debug_print("didn't get draft msgnum after scanning\n");
10445 debug_print("got draft msgnum %d from adding\n", msgnum);
10451 if (action != COMPOSE_AUTO_SAVE) {
10452 if (action != COMPOSE_DRAFT_FOR_EXIT)
10453 alertpanel_error(_("Could not save draft."));
10456 gtkut_window_popup(compose->window);
10457 val = alertpanel_full(_("Could not save draft"),
10458 _("Could not save draft.\n"
10459 "Do you want to cancel exit or discard this email?"),
10460 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10461 FALSE, NULL, ALERT_QUESTION);
10462 if (val == G_ALERTALTERNATE) {
10464 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10465 compose_close(compose);
10469 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10478 if (compose->mode == COMPOSE_REEDIT) {
10479 compose_remove_reedit_target(compose, TRUE);
10482 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10485 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10487 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10489 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10490 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10491 procmsg_msginfo_set_flags(newmsginfo, 0,
10492 MSG_HAS_ATTACHMENT);
10494 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10495 compose_register_draft(newmsginfo);
10497 procmsg_msginfo_free(&newmsginfo);
10500 folder_item_scan(draft);
10502 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10504 g_mutex_unlock(&compose->mutex); /* must be done before closing */
10505 compose_close(compose);
10512 GError *error = NULL;
10517 goffset size, mtime;
10519 path = folder_item_fetch_msg(draft, msgnum);
10520 if (path == NULL) {
10521 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10525 f = g_file_new_for_path(path);
10526 fi = g_file_query_info(f, "standard::size,time::modified",
10527 G_FILE_QUERY_INFO_NONE, NULL, &error);
10528 if (error != NULL) {
10529 debug_print("couldn't query file info for '%s': %s\n",
10530 path, error->message);
10531 g_error_free(error);
10536 size = g_file_info_get_size(fi);
10537 g_file_info_get_modification_time(fi, &tv);
10539 g_object_unref(fi);
10542 if (g_stat(path, &s) < 0) {
10543 FILE_OP_ERROR(path, "stat");
10548 mtime = s.st_mtime;
10552 procmsg_msginfo_free(&(compose->targetinfo));
10553 compose->targetinfo = procmsg_msginfo_new();
10554 compose->targetinfo->msgnum = msgnum;
10555 compose->targetinfo->size = size;
10556 compose->targetinfo->mtime = mtime;
10557 compose->targetinfo->folder = draft;
10559 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10560 compose->mode = COMPOSE_REEDIT;
10562 if (action == COMPOSE_AUTO_SAVE) {
10563 compose->autosaved_draft = compose->targetinfo;
10565 compose->modified = FALSE;
10566 compose_set_title(compose);
10570 g_mutex_unlock(&compose->mutex);
10574 void compose_clear_exit_drafts(void)
10576 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10577 DRAFTED_AT_EXIT, NULL);
10578 if (is_file_exist(filepath))
10579 claws_unlink(filepath);
10584 void compose_reopen_exit_drafts(void)
10586 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10587 DRAFTED_AT_EXIT, NULL);
10588 FILE *fp = claws_fopen(filepath, "rb");
10592 while (claws_fgets(buf, sizeof(buf), fp)) {
10593 gchar **parts = g_strsplit(buf, "\t", 2);
10594 const gchar *folder = parts[0];
10595 int msgnum = parts[1] ? atoi(parts[1]):-1;
10597 if (folder && *folder && msgnum > -1) {
10598 FolderItem *item = folder_find_item_from_identifier(folder);
10599 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10601 compose_reedit(info, FALSE);
10608 compose_clear_exit_drafts();
10611 static void compose_save_cb(GtkAction *action, gpointer data)
10613 Compose *compose = (Compose *)data;
10614 compose_draft(compose, COMPOSE_KEEP_EDITING);
10615 compose->rmode = COMPOSE_REEDIT;
10618 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10620 if (compose && file_list) {
10623 for ( tmp = file_list; tmp; tmp = tmp->next) {
10624 gchar *file = (gchar *) tmp->data;
10625 gchar *utf8_filename = conv_filename_to_utf8(file);
10626 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10627 compose_changed_cb(NULL, compose);
10632 g_free(utf8_filename);
10637 static void compose_attach_cb(GtkAction *action, gpointer data)
10639 Compose *compose = (Compose *)data;
10642 if (compose->redirect_filename != NULL)
10645 /* Set focus_window properly, in case we were called via popup menu,
10646 * which unsets it (via focus_out_event callback on compose window). */
10647 manage_window_focus_in(compose->window, NULL, NULL);
10649 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10652 compose_attach_from_list(compose, file_list, TRUE);
10653 g_list_free(file_list);
10657 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10659 Compose *compose = (Compose *)data;
10661 gint files_inserted = 0;
10663 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10668 for ( tmp = file_list; tmp; tmp = tmp->next) {
10669 gchar *file = (gchar *) tmp->data;
10670 gchar *filedup = g_strdup(file);
10671 gchar *shortfile = g_path_get_basename(filedup);
10672 ComposeInsertResult res;
10673 /* insert the file if the file is short or if the user confirmed that
10674 he/she wants to insert the large file */
10675 res = compose_insert_file(compose, file);
10676 if (res == COMPOSE_INSERT_READ_ERROR) {
10677 alertpanel_error(_("File '%s' could not be read."), shortfile);
10678 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10679 alertpanel_error(_("File '%s' contained invalid characters\n"
10680 "for the current encoding, insertion may be incorrect."),
10682 } else if (res == COMPOSE_INSERT_SUCCESS)
10689 g_list_free(file_list);
10693 if (files_inserted > 0 && compose->gtkaspell &&
10694 compose->gtkaspell->check_while_typing)
10695 gtkaspell_highlight_all(compose->gtkaspell);
10699 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10701 Compose *compose = (Compose *)data;
10703 compose_insert_sig(compose, FALSE);
10706 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10708 Compose *compose = (Compose *)data;
10710 compose_insert_sig(compose, TRUE);
10713 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10717 Compose *compose = (Compose *)data;
10719 gtkut_widget_get_uposition(widget, &x, &y);
10720 if (!compose->batch) {
10721 prefs_common.compose_x = x;
10722 prefs_common.compose_y = y;
10724 if (compose->sending || compose->updating)
10726 compose_close_cb(NULL, compose);
10730 void compose_close_toolbar(Compose *compose)
10732 compose_close_cb(NULL, compose);
10735 static void compose_close_cb(GtkAction *action, gpointer data)
10737 Compose *compose = (Compose *)data;
10740 if (compose->exteditor_tag != -1) {
10741 if (!compose_ext_editor_kill(compose))
10745 if (compose->modified) {
10746 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10747 if (!g_mutex_trylock(&compose->mutex)) {
10748 /* we don't want to lock the mutex once it's available,
10749 * because as the only other part of compose.c locking
10750 * it is compose_close - which means once unlocked,
10751 * the compose struct will be freed */
10752 debug_print("couldn't lock mutex, probably sending\n");
10755 if (!reedit || compose->folder->stype == F_DRAFT) {
10756 val = alertpanel(_("Discard message"),
10757 _("This message has been modified. Discard it?"),
10758 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10761 val = alertpanel(_("Save changes"),
10762 _("This message has been modified. Save the latest changes?"),
10763 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10764 ALERTFOCUS_SECOND);
10766 g_mutex_unlock(&compose->mutex);
10768 case G_ALERTDEFAULT:
10769 if (compose_can_autosave(compose) && !reedit)
10770 compose_remove_draft(compose);
10772 case G_ALERTALTERNATE:
10773 compose_draft(data, COMPOSE_QUIT_EDITING);
10780 compose_close(compose);
10783 static void compose_print_cb(GtkAction *action, gpointer data)
10785 Compose *compose = (Compose *) data;
10787 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10788 if (compose->targetinfo)
10789 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10792 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10794 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10795 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10796 Compose *compose = (Compose *) data;
10799 compose->out_encoding = (CharSet)value;
10802 static void compose_address_cb(GtkAction *action, gpointer data)
10804 Compose *compose = (Compose *)data;
10806 #ifndef USE_ALT_ADDRBOOK
10807 addressbook_open(compose);
10809 GError* error = NULL;
10810 addressbook_connect_signals(compose);
10811 addressbook_dbus_open(TRUE, &error);
10813 g_warning("%s", error->message);
10814 g_error_free(error);
10819 static void about_show_cb(GtkAction *action, gpointer data)
10824 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10826 Compose *compose = (Compose *)data;
10831 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10832 cm_return_if_fail(tmpl != NULL);
10834 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10836 val = alertpanel(_("Apply template"), msg,
10837 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10840 if (val == G_ALERTDEFAULT)
10841 compose_template_apply(compose, tmpl, TRUE);
10842 else if (val == G_ALERTALTERNATE)
10843 compose_template_apply(compose, tmpl, FALSE);
10846 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10848 Compose *compose = (Compose *)data;
10850 if (compose->exteditor_tag != -1) {
10851 debug_print("ignoring open external editor: external editor still open\n");
10854 compose_exec_ext_editor(compose);
10857 static void compose_undo_cb(GtkAction *action, gpointer data)
10859 Compose *compose = (Compose *)data;
10860 gboolean prev_autowrap = compose->autowrap;
10862 compose->autowrap = FALSE;
10863 undo_undo(compose->undostruct);
10864 compose->autowrap = prev_autowrap;
10867 static void compose_redo_cb(GtkAction *action, gpointer data)
10869 Compose *compose = (Compose *)data;
10870 gboolean prev_autowrap = compose->autowrap;
10872 compose->autowrap = FALSE;
10873 undo_redo(compose->undostruct);
10874 compose->autowrap = prev_autowrap;
10877 static void entry_cut_clipboard(GtkWidget *entry)
10879 if (GTK_IS_EDITABLE(entry))
10880 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10881 else if (GTK_IS_TEXT_VIEW(entry))
10882 gtk_text_buffer_cut_clipboard(
10883 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10884 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10888 static void entry_copy_clipboard(GtkWidget *entry)
10890 if (GTK_IS_EDITABLE(entry))
10891 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10892 else if (GTK_IS_TEXT_VIEW(entry))
10893 gtk_text_buffer_copy_clipboard(
10894 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10895 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10898 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10899 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10901 if (GTK_IS_TEXT_VIEW(entry)) {
10902 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10903 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10904 GtkTextIter start_iter, end_iter;
10906 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10908 if (contents == NULL)
10911 /* we shouldn't delete the selection when middle-click-pasting, or we
10912 * can't mid-click-paste our own selection */
10913 if (clip != GDK_SELECTION_PRIMARY) {
10914 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10915 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10918 if (insert_place == NULL) {
10919 /* if insert_place isn't specified, insert at the cursor.
10920 * used for Ctrl-V pasting */
10921 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10922 start = gtk_text_iter_get_offset(&start_iter);
10923 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10925 /* if insert_place is specified, paste here.
10926 * used for mid-click-pasting */
10927 start = gtk_text_iter_get_offset(insert_place);
10928 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10929 if (prefs_common.primary_paste_unselects)
10930 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10934 /* paste unwrapped: mark the paste so it's not wrapped later */
10935 end = start + strlen(contents);
10936 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10937 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10938 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10939 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10940 /* rewrap paragraph now (after a mid-click-paste) */
10941 mark_start = gtk_text_buffer_get_insert(buffer);
10942 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10943 gtk_text_iter_backward_char(&start_iter);
10944 compose_beautify_paragraph(compose, &start_iter, TRUE);
10946 } else if (GTK_IS_EDITABLE(entry))
10947 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10949 compose->modified = TRUE;
10952 static void entry_allsel(GtkWidget *entry)
10954 if (GTK_IS_EDITABLE(entry))
10955 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10956 else if (GTK_IS_TEXT_VIEW(entry)) {
10957 GtkTextIter startiter, enditer;
10958 GtkTextBuffer *textbuf;
10960 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10961 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10962 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10964 gtk_text_buffer_move_mark_by_name(textbuf,
10965 "selection_bound", &startiter);
10966 gtk_text_buffer_move_mark_by_name(textbuf,
10967 "insert", &enditer);
10971 static void compose_cut_cb(GtkAction *action, gpointer data)
10973 Compose *compose = (Compose *)data;
10974 if (compose->focused_editable
10975 #ifndef GENERIC_UMPC
10976 && gtk_widget_has_focus(compose->focused_editable)
10979 entry_cut_clipboard(compose->focused_editable);
10982 static void compose_copy_cb(GtkAction *action, gpointer data)
10984 Compose *compose = (Compose *)data;
10985 if (compose->focused_editable
10986 #ifndef GENERIC_UMPC
10987 && gtk_widget_has_focus(compose->focused_editable)
10990 entry_copy_clipboard(compose->focused_editable);
10993 static void compose_paste_cb(GtkAction *action, gpointer data)
10995 Compose *compose = (Compose *)data;
10996 gint prev_autowrap;
10997 GtkTextBuffer *buffer;
10999 if (compose->focused_editable
11000 #ifndef GENERIC_UMPC
11001 && gtk_widget_has_focus(compose->focused_editable)
11004 entry_paste_clipboard(compose, compose->focused_editable,
11005 prefs_common.linewrap_pastes,
11006 GDK_SELECTION_CLIPBOARD, NULL);
11011 #ifndef GENERIC_UMPC
11012 gtk_widget_has_focus(compose->text) &&
11014 compose->gtkaspell &&
11015 compose->gtkaspell->check_while_typing)
11016 gtkaspell_highlight_all(compose->gtkaspell);
11020 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
11022 Compose *compose = (Compose *)data;
11023 gint wrap_quote = prefs_common.linewrap_quote;
11024 if (compose->focused_editable
11025 #ifndef GENERIC_UMPC
11026 && gtk_widget_has_focus(compose->focused_editable)
11029 /* let text_insert() (called directly or at a later time
11030 * after the gtk_editable_paste_clipboard) know that
11031 * text is to be inserted as a quotation. implemented
11032 * by using a simple refcount... */
11033 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11034 G_OBJECT(compose->focused_editable),
11035 "paste_as_quotation"));
11036 g_object_set_data(G_OBJECT(compose->focused_editable),
11037 "paste_as_quotation",
11038 GINT_TO_POINTER(paste_as_quotation + 1));
11039 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11040 entry_paste_clipboard(compose, compose->focused_editable,
11041 prefs_common.linewrap_pastes,
11042 GDK_SELECTION_CLIPBOARD, NULL);
11043 prefs_common.linewrap_quote = wrap_quote;
11047 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11049 Compose *compose = (Compose *)data;
11050 gint prev_autowrap;
11051 GtkTextBuffer *buffer;
11053 if (compose->focused_editable
11054 #ifndef GENERIC_UMPC
11055 && gtk_widget_has_focus(compose->focused_editable)
11058 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11059 GDK_SELECTION_CLIPBOARD, NULL);
11064 #ifndef GENERIC_UMPC
11065 gtk_widget_has_focus(compose->text) &&
11067 compose->gtkaspell &&
11068 compose->gtkaspell->check_while_typing)
11069 gtkaspell_highlight_all(compose->gtkaspell);
11073 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11075 Compose *compose = (Compose *)data;
11076 gint prev_autowrap;
11077 GtkTextBuffer *buffer;
11079 if (compose->focused_editable
11080 #ifndef GENERIC_UMPC
11081 && gtk_widget_has_focus(compose->focused_editable)
11084 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11085 GDK_SELECTION_CLIPBOARD, NULL);
11090 #ifndef GENERIC_UMPC
11091 gtk_widget_has_focus(compose->text) &&
11093 compose->gtkaspell &&
11094 compose->gtkaspell->check_while_typing)
11095 gtkaspell_highlight_all(compose->gtkaspell);
11099 static void compose_allsel_cb(GtkAction *action, gpointer data)
11101 Compose *compose = (Compose *)data;
11102 if (compose->focused_editable
11103 #ifndef GENERIC_UMPC
11104 && gtk_widget_has_focus(compose->focused_editable)
11107 entry_allsel(compose->focused_editable);
11110 static void textview_move_beginning_of_line (GtkTextView *text)
11112 GtkTextBuffer *buffer;
11116 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11118 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11119 mark = gtk_text_buffer_get_insert(buffer);
11120 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11121 gtk_text_iter_set_line_offset(&ins, 0);
11122 gtk_text_buffer_place_cursor(buffer, &ins);
11125 static void textview_move_forward_character (GtkTextView *text)
11127 GtkTextBuffer *buffer;
11131 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11133 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11134 mark = gtk_text_buffer_get_insert(buffer);
11135 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11136 if (gtk_text_iter_forward_cursor_position(&ins))
11137 gtk_text_buffer_place_cursor(buffer, &ins);
11140 static void textview_move_backward_character (GtkTextView *text)
11142 GtkTextBuffer *buffer;
11146 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11148 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11149 mark = gtk_text_buffer_get_insert(buffer);
11150 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11151 if (gtk_text_iter_backward_cursor_position(&ins))
11152 gtk_text_buffer_place_cursor(buffer, &ins);
11155 static void textview_move_forward_word (GtkTextView *text)
11157 GtkTextBuffer *buffer;
11162 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11164 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11165 mark = gtk_text_buffer_get_insert(buffer);
11166 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11167 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11168 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11169 gtk_text_iter_backward_word_start(&ins);
11170 gtk_text_buffer_place_cursor(buffer, &ins);
11174 static void textview_move_backward_word (GtkTextView *text)
11176 GtkTextBuffer *buffer;
11180 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11182 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11183 mark = gtk_text_buffer_get_insert(buffer);
11184 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11185 if (gtk_text_iter_backward_word_starts(&ins, 1))
11186 gtk_text_buffer_place_cursor(buffer, &ins);
11189 static void textview_move_end_of_line (GtkTextView *text)
11191 GtkTextBuffer *buffer;
11195 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11197 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11198 mark = gtk_text_buffer_get_insert(buffer);
11199 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11200 if (gtk_text_iter_forward_to_line_end(&ins))
11201 gtk_text_buffer_place_cursor(buffer, &ins);
11204 static void textview_move_next_line (GtkTextView *text)
11206 GtkTextBuffer *buffer;
11211 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11213 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11214 mark = gtk_text_buffer_get_insert(buffer);
11215 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11216 offset = gtk_text_iter_get_line_offset(&ins);
11217 if (gtk_text_iter_forward_line(&ins)) {
11218 gtk_text_iter_set_line_offset(&ins, offset);
11219 gtk_text_buffer_place_cursor(buffer, &ins);
11223 static void textview_move_previous_line (GtkTextView *text)
11225 GtkTextBuffer *buffer;
11230 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11232 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11233 mark = gtk_text_buffer_get_insert(buffer);
11234 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11235 offset = gtk_text_iter_get_line_offset(&ins);
11236 if (gtk_text_iter_backward_line(&ins)) {
11237 gtk_text_iter_set_line_offset(&ins, offset);
11238 gtk_text_buffer_place_cursor(buffer, &ins);
11242 static void textview_delete_forward_character (GtkTextView *text)
11244 GtkTextBuffer *buffer;
11246 GtkTextIter ins, end_iter;
11248 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11250 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11251 mark = gtk_text_buffer_get_insert(buffer);
11252 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11254 if (gtk_text_iter_forward_char(&end_iter)) {
11255 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11259 static void textview_delete_backward_character (GtkTextView *text)
11261 GtkTextBuffer *buffer;
11263 GtkTextIter ins, end_iter;
11265 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11267 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11268 mark = gtk_text_buffer_get_insert(buffer);
11269 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11271 if (gtk_text_iter_backward_char(&end_iter)) {
11272 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11276 static void textview_delete_forward_word (GtkTextView *text)
11278 GtkTextBuffer *buffer;
11280 GtkTextIter ins, end_iter;
11282 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11284 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11285 mark = gtk_text_buffer_get_insert(buffer);
11286 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11288 if (gtk_text_iter_forward_word_end(&end_iter)) {
11289 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11293 static void textview_delete_backward_word (GtkTextView *text)
11295 GtkTextBuffer *buffer;
11297 GtkTextIter ins, end_iter;
11299 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11301 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11302 mark = gtk_text_buffer_get_insert(buffer);
11303 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11305 if (gtk_text_iter_backward_word_start(&end_iter)) {
11306 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11310 static void textview_delete_line (GtkTextView *text)
11312 GtkTextBuffer *buffer;
11314 GtkTextIter ins, start_iter, end_iter;
11316 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11318 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11319 mark = gtk_text_buffer_get_insert(buffer);
11320 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11323 gtk_text_iter_set_line_offset(&start_iter, 0);
11326 if (gtk_text_iter_ends_line(&end_iter)){
11327 if (!gtk_text_iter_forward_char(&end_iter))
11328 gtk_text_iter_backward_char(&start_iter);
11331 gtk_text_iter_forward_to_line_end(&end_iter);
11332 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11335 static void textview_delete_to_line_end (GtkTextView *text)
11337 GtkTextBuffer *buffer;
11339 GtkTextIter ins, end_iter;
11341 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11343 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11344 mark = gtk_text_buffer_get_insert(buffer);
11345 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11347 if (gtk_text_iter_ends_line(&end_iter))
11348 gtk_text_iter_forward_char(&end_iter);
11350 gtk_text_iter_forward_to_line_end(&end_iter);
11351 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11354 #define DO_ACTION(name, act) { \
11355 if(!strcmp(name, a_name)) { \
11359 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11361 const gchar *a_name = gtk_action_get_name(action);
11362 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11363 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11364 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11365 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11366 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11367 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11368 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11369 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11370 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11371 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11372 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11373 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11374 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11375 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11376 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11379 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11381 Compose *compose = (Compose *)data;
11382 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11383 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11385 action = compose_call_advanced_action_from_path(gaction);
11388 void (*do_action) (GtkTextView *text);
11389 } action_table[] = {
11390 {textview_move_beginning_of_line},
11391 {textview_move_forward_character},
11392 {textview_move_backward_character},
11393 {textview_move_forward_word},
11394 {textview_move_backward_word},
11395 {textview_move_end_of_line},
11396 {textview_move_next_line},
11397 {textview_move_previous_line},
11398 {textview_delete_forward_character},
11399 {textview_delete_backward_character},
11400 {textview_delete_forward_word},
11401 {textview_delete_backward_word},
11402 {textview_delete_line},
11403 {textview_delete_to_line_end}
11406 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11408 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11409 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11410 if (action_table[action].do_action)
11411 action_table[action].do_action(text);
11413 g_warning("not implemented yet");
11417 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11419 GtkAllocation allocation;
11423 if (GTK_IS_EDITABLE(widget)) {
11424 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11425 gtk_editable_set_position(GTK_EDITABLE(widget),
11428 if ((parent = gtk_widget_get_parent(widget))
11429 && (parent = gtk_widget_get_parent(parent))
11430 && (parent = gtk_widget_get_parent(parent))) {
11431 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11432 gtk_widget_get_allocation(widget, &allocation);
11433 gint y = allocation.y;
11434 gint height = allocation.height;
11435 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11436 (GTK_SCROLLED_WINDOW(parent));
11438 gfloat value = gtk_adjustment_get_value(shown);
11439 gfloat upper = gtk_adjustment_get_upper(shown);
11440 gfloat page_size = gtk_adjustment_get_page_size(shown);
11441 if (y < (int)value) {
11442 gtk_adjustment_set_value(shown, y - 1);
11444 if ((y + height) > ((int)value + (int)page_size)) {
11445 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11446 gtk_adjustment_set_value(shown,
11447 y + height - (int)page_size - 1);
11449 gtk_adjustment_set_value(shown,
11450 (int)upper - (int)page_size - 1);
11457 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11458 compose->focused_editable = widget;
11460 #ifdef GENERIC_UMPC
11461 if (GTK_IS_TEXT_VIEW(widget)
11462 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11463 g_object_ref(compose->notebook);
11464 g_object_ref(compose->edit_vbox);
11465 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11466 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11467 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11468 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11469 g_object_unref(compose->notebook);
11470 g_object_unref(compose->edit_vbox);
11471 g_signal_handlers_block_by_func(G_OBJECT(widget),
11472 G_CALLBACK(compose_grab_focus_cb),
11474 gtk_widget_grab_focus(widget);
11475 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11476 G_CALLBACK(compose_grab_focus_cb),
11478 } else if (!GTK_IS_TEXT_VIEW(widget)
11479 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11480 g_object_ref(compose->notebook);
11481 g_object_ref(compose->edit_vbox);
11482 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11483 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11484 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11485 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11486 g_object_unref(compose->notebook);
11487 g_object_unref(compose->edit_vbox);
11488 g_signal_handlers_block_by_func(G_OBJECT(widget),
11489 G_CALLBACK(compose_grab_focus_cb),
11491 gtk_widget_grab_focus(widget);
11492 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11493 G_CALLBACK(compose_grab_focus_cb),
11499 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11501 compose->modified = TRUE;
11502 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11503 #ifndef GENERIC_UMPC
11504 compose_set_title(compose);
11508 static void compose_wrap_cb(GtkAction *action, gpointer data)
11510 Compose *compose = (Compose *)data;
11511 compose_beautify_paragraph(compose, NULL, TRUE);
11514 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11516 Compose *compose = (Compose *)data;
11517 compose_wrap_all_full(compose, TRUE);
11520 static void compose_find_cb(GtkAction *action, gpointer data)
11522 Compose *compose = (Compose *)data;
11524 message_search_compose(compose);
11527 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11530 Compose *compose = (Compose *)data;
11531 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11532 if (compose->autowrap)
11533 compose_wrap_all_full(compose, TRUE);
11534 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11537 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11540 Compose *compose = (Compose *)data;
11541 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11544 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11546 Compose *compose = (Compose *)data;
11548 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11549 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11552 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11554 Compose *compose = (Compose *)data;
11556 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11557 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11560 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11562 g_free(compose->privacy_system);
11563 g_free(compose->encdata);
11565 compose->privacy_system = g_strdup(account->default_privacy_system);
11566 compose_update_privacy_system_menu_item(compose, warn);
11569 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11571 if (folder_item != NULL) {
11572 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11573 privacy_system_can_sign(compose->privacy_system)) {
11574 compose_use_signing(compose,
11575 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11577 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11578 privacy_system_can_encrypt(compose->privacy_system)) {
11579 compose_use_encryption(compose,
11580 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11585 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11587 Compose *compose = (Compose *)data;
11589 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11590 gtk_widget_show(compose->ruler_hbox);
11591 prefs_common.show_ruler = TRUE;
11593 gtk_widget_hide(compose->ruler_hbox);
11594 gtk_widget_queue_resize(compose->edit_vbox);
11595 prefs_common.show_ruler = FALSE;
11599 static void compose_attach_drag_received_cb (GtkWidget *widget,
11600 GdkDragContext *context,
11603 GtkSelectionData *data,
11606 gpointer user_data)
11608 Compose *compose = (Compose *)user_data;
11612 type = gtk_selection_data_get_data_type(data);
11613 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11614 && gtk_drag_get_source_widget(context) !=
11615 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11616 list = uri_list_extract_filenames(
11617 (const gchar *)gtk_selection_data_get_data(data));
11618 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11619 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11620 compose_attach_append
11621 (compose, (const gchar *)tmp->data,
11622 utf8_filename, NULL, NULL);
11623 g_free(utf8_filename);
11626 compose_changed_cb(NULL, compose);
11627 list_free_strings_full(list);
11628 } else if (gtk_drag_get_source_widget(context)
11629 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11630 /* comes from our summaryview */
11631 SummaryView * summaryview = NULL;
11632 GSList * list = NULL, *cur = NULL;
11634 if (mainwindow_get_mainwindow())
11635 summaryview = mainwindow_get_mainwindow()->summaryview;
11638 list = summary_get_selected_msg_list(summaryview);
11640 for (cur = list; cur; cur = cur->next) {
11641 MsgInfo *msginfo = (MsgInfo *)cur->data;
11642 gchar *file = NULL;
11644 file = procmsg_get_message_file_full(msginfo,
11647 compose_attach_append(compose, (const gchar *)file,
11648 (const gchar *)file, "message/rfc822", NULL);
11652 g_slist_free(list);
11656 static gboolean compose_drag_drop(GtkWidget *widget,
11657 GdkDragContext *drag_context,
11659 guint time, gpointer user_data)
11661 /* not handling this signal makes compose_insert_drag_received_cb
11666 static gboolean completion_set_focus_to_subject
11667 (GtkWidget *widget,
11668 GdkEventKey *event,
11671 cm_return_val_if_fail(compose != NULL, FALSE);
11673 /* make backtab move to subject field */
11674 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11675 gtk_widget_grab_focus(compose->subject_entry);
11681 static void compose_insert_drag_received_cb (GtkWidget *widget,
11682 GdkDragContext *drag_context,
11685 GtkSelectionData *data,
11688 gpointer user_data)
11690 Compose *compose = (Compose *)user_data;
11696 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11698 type = gtk_selection_data_get_data_type(data);
11699 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11700 AlertValue val = G_ALERTDEFAULT;
11701 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11703 list = uri_list_extract_filenames(ddata);
11704 num_files = g_list_length(list);
11705 if (list == NULL && strstr(ddata, "://")) {
11706 /* Assume a list of no files, and data has ://, is a remote link */
11707 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11708 gchar *tmpfile = get_tmp_file();
11709 str_write_to_file(tmpdata, tmpfile, TRUE);
11711 compose_insert_file(compose, tmpfile);
11712 claws_unlink(tmpfile);
11714 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11715 compose_beautify_paragraph(compose, NULL, TRUE);
11718 switch (prefs_common.compose_dnd_mode) {
11719 case COMPOSE_DND_ASK:
11720 msg = g_strdup_printf(
11722 "Do you want to insert the contents of the file "
11723 "into the message body, or attach it to the email?",
11724 "Do you want to insert the contents of the %d files "
11725 "into the message body, or attach them to the email?",
11728 val = alertpanel_full(_("Insert or attach?"), msg,
11729 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11731 TRUE, NULL, ALERT_QUESTION);
11734 case COMPOSE_DND_INSERT:
11735 val = G_ALERTALTERNATE;
11737 case COMPOSE_DND_ATTACH:
11738 val = G_ALERTOTHER;
11741 /* unexpected case */
11742 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11745 if (val & G_ALERTDISABLE) {
11746 val &= ~G_ALERTDISABLE;
11747 /* remember what action to perform by default, only if we don't click Cancel */
11748 if (val == G_ALERTALTERNATE)
11749 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11750 else if (val == G_ALERTOTHER)
11751 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11754 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11755 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11756 list_free_strings_full(list);
11758 } else if (val == G_ALERTOTHER) {
11759 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11760 list_free_strings_full(list);
11764 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11765 compose_insert_file(compose, (const gchar *)tmp->data);
11767 list_free_strings_full(list);
11768 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11773 static void compose_header_drag_received_cb (GtkWidget *widget,
11774 GdkDragContext *drag_context,
11777 GtkSelectionData *data,
11780 gpointer user_data)
11782 GtkEditable *entry = (GtkEditable *)user_data;
11783 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11785 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11788 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11789 gchar *decoded=g_new(gchar, strlen(email));
11792 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11793 gtk_editable_delete_text(entry, 0, -1);
11794 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11795 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11799 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11802 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11804 Compose *compose = (Compose *)data;
11806 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11807 compose->return_receipt = TRUE;
11809 compose->return_receipt = FALSE;
11812 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11814 Compose *compose = (Compose *)data;
11816 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11817 compose->remove_references = TRUE;
11819 compose->remove_references = FALSE;
11822 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11823 ComposeHeaderEntry *headerentry)
11825 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11826 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11827 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11831 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11832 GdkEventKey *event,
11833 ComposeHeaderEntry *headerentry)
11835 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11836 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11837 !(event->state & GDK_MODIFIER_MASK) &&
11838 (event->keyval == GDK_KEY_BackSpace) &&
11839 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11840 gtk_container_remove
11841 (GTK_CONTAINER(headerentry->compose->header_table),
11842 headerentry->combo);
11843 gtk_container_remove
11844 (GTK_CONTAINER(headerentry->compose->header_table),
11845 headerentry->entry);
11846 headerentry->compose->header_list =
11847 g_slist_remove(headerentry->compose->header_list,
11849 g_free(headerentry);
11850 } else if (event->keyval == GDK_KEY_Tab) {
11851 if (headerentry->compose->header_last == headerentry) {
11852 /* Override default next focus, and give it to subject_entry
11853 * instead of notebook tabs
11855 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11856 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11863 static gboolean scroll_postpone(gpointer data)
11865 Compose *compose = (Compose *)data;
11867 if (compose->batch)
11870 GTK_EVENTS_FLUSH();
11871 compose_show_first_last_header(compose, FALSE);
11875 static void compose_headerentry_changed_cb(GtkWidget *entry,
11876 ComposeHeaderEntry *headerentry)
11878 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11879 compose_create_header_entry(headerentry->compose);
11880 g_signal_handlers_disconnect_matched
11881 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11882 0, 0, NULL, NULL, headerentry);
11884 if (!headerentry->compose->batch)
11885 g_timeout_add(0, scroll_postpone, headerentry->compose);
11889 static gboolean compose_defer_auto_save_draft(Compose *compose)
11891 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11892 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11896 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11898 GtkAdjustment *vadj;
11900 cm_return_if_fail(compose);
11905 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11906 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11907 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11908 gtk_widget_get_parent(compose->header_table)));
11909 gtk_adjustment_set_value(vadj, (show_first ?
11910 gtk_adjustment_get_lower(vadj) :
11911 (gtk_adjustment_get_upper(vadj) -
11912 gtk_adjustment_get_page_size(vadj))));
11913 gtk_adjustment_changed(vadj);
11916 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11917 const gchar *text, gint len, Compose *compose)
11919 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11920 (G_OBJECT(compose->text), "paste_as_quotation"));
11923 cm_return_if_fail(text != NULL);
11925 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11926 G_CALLBACK(text_inserted),
11928 if (paste_as_quotation) {
11930 const gchar *qmark;
11932 GtkTextIter start_iter;
11935 len = strlen(text);
11937 new_text = g_strndup(text, len);
11939 qmark = compose_quote_char_from_context(compose);
11941 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11942 gtk_text_buffer_place_cursor(buffer, iter);
11944 pos = gtk_text_iter_get_offset(iter);
11946 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11947 _("Quote format error at line %d."));
11948 quote_fmt_reset_vartable();
11950 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11951 GINT_TO_POINTER(paste_as_quotation - 1));
11953 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11954 gtk_text_buffer_place_cursor(buffer, iter);
11955 gtk_text_buffer_delete_mark(buffer, mark);
11957 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11958 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11959 compose_beautify_paragraph(compose, &start_iter, FALSE);
11960 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11961 gtk_text_buffer_delete_mark(buffer, mark);
11963 if (strcmp(text, "\n") || compose->automatic_break
11964 || gtk_text_iter_starts_line(iter)) {
11965 GtkTextIter before_ins;
11966 gtk_text_buffer_insert(buffer, iter, text, len);
11967 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11968 before_ins = *iter;
11969 gtk_text_iter_backward_chars(&before_ins, len);
11970 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11973 /* check if the preceding is just whitespace or quote */
11974 GtkTextIter start_line;
11975 gchar *tmp = NULL, *quote = NULL;
11976 gint quote_len = 0, is_normal = 0;
11977 start_line = *iter;
11978 gtk_text_iter_set_line_offset(&start_line, 0);
11979 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11982 if (*tmp == '\0') {
11985 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11993 gtk_text_buffer_insert(buffer, iter, text, len);
11995 gtk_text_buffer_insert_with_tags_by_name(buffer,
11996 iter, text, len, "no_join", NULL);
12001 if (!paste_as_quotation) {
12002 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
12003 compose_beautify_paragraph(compose, iter, FALSE);
12004 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
12005 gtk_text_buffer_delete_mark(buffer, mark);
12008 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
12009 G_CALLBACK(text_inserted),
12011 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
12013 if (compose_can_autosave(compose) &&
12014 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
12015 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
12016 compose->draft_timeout_tag = g_timeout_add
12017 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
12021 static void compose_check_all(GtkAction *action, gpointer data)
12023 Compose *compose = (Compose *)data;
12024 if (!compose->gtkaspell)
12027 if (gtk_widget_has_focus(compose->subject_entry))
12028 claws_spell_entry_check_all(
12029 CLAWS_SPELL_ENTRY(compose->subject_entry));
12031 gtkaspell_check_all(compose->gtkaspell);
12034 static void compose_highlight_all(GtkAction *action, gpointer data)
12036 Compose *compose = (Compose *)data;
12037 if (compose->gtkaspell) {
12038 claws_spell_entry_recheck_all(
12039 CLAWS_SPELL_ENTRY(compose->subject_entry));
12040 gtkaspell_highlight_all(compose->gtkaspell);
12044 static void compose_check_backwards(GtkAction *action, gpointer data)
12046 Compose *compose = (Compose *)data;
12047 if (!compose->gtkaspell) {
12048 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12052 if (gtk_widget_has_focus(compose->subject_entry))
12053 claws_spell_entry_check_backwards(
12054 CLAWS_SPELL_ENTRY(compose->subject_entry));
12056 gtkaspell_check_backwards(compose->gtkaspell);
12059 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12061 Compose *compose = (Compose *)data;
12062 if (!compose->gtkaspell) {
12063 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12067 if (gtk_widget_has_focus(compose->subject_entry))
12068 claws_spell_entry_check_forwards_go(
12069 CLAWS_SPELL_ENTRY(compose->subject_entry));
12071 gtkaspell_check_forwards_go(compose->gtkaspell);
12076 *\brief Guess originating forward account from MsgInfo and several
12077 * "common preference" settings. Return NULL if no guess.
12079 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12081 PrefsAccount *account = NULL;
12083 cm_return_val_if_fail(msginfo, NULL);
12084 cm_return_val_if_fail(msginfo->folder, NULL);
12085 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12087 if (msginfo->folder->prefs->enable_default_account)
12088 account = account_find_from_id(msginfo->folder->prefs->default_account);
12090 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12092 Xstrdup_a(to, msginfo->to, return NULL);
12093 extract_address(to);
12094 account = account_find_from_address(to, FALSE);
12097 if (!account && prefs_common.forward_account_autosel) {
12099 if (!procheader_get_header_from_msginfo
12100 (msginfo, &cc, "Cc:")) {
12101 gchar *buf = cc + strlen("Cc:");
12102 extract_address(buf);
12103 account = account_find_from_address(buf, FALSE);
12108 if (!account && prefs_common.forward_account_autosel) {
12109 gchar *deliveredto = NULL;
12110 if (!procheader_get_header_from_msginfo
12111 (msginfo, &deliveredto, "Delivered-To:")) {
12112 gchar *buf = deliveredto + strlen("Delivered-To:");
12113 extract_address(buf);
12114 account = account_find_from_address(buf, FALSE);
12115 g_free(deliveredto);
12120 account = msginfo->folder->folder->account;
12125 gboolean compose_close(Compose *compose)
12129 cm_return_val_if_fail(compose, FALSE);
12131 if (!g_mutex_trylock(&compose->mutex)) {
12132 /* we have to wait for the (possibly deferred by auto-save)
12133 * drafting to be done, before destroying the compose under
12135 debug_print("waiting for drafting to finish...\n");
12136 compose_allow_user_actions(compose, FALSE);
12137 if (compose->close_timeout_tag == 0) {
12138 compose->close_timeout_tag =
12139 g_timeout_add (500, (GSourceFunc) compose_close,
12145 if (compose->draft_timeout_tag >= 0) {
12146 g_source_remove(compose->draft_timeout_tag);
12147 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12150 gtkut_widget_get_uposition(compose->window, &x, &y);
12151 if (!compose->batch) {
12152 prefs_common.compose_x = x;
12153 prefs_common.compose_y = y;
12155 g_mutex_unlock(&compose->mutex);
12156 compose_destroy(compose);
12161 * Add entry field for each address in list.
12162 * \param compose E-Mail composition object.
12163 * \param listAddress List of (formatted) E-Mail addresses.
12165 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12168 node = listAddress;
12170 addr = ( gchar * ) node->data;
12171 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12172 node = g_list_next( node );
12176 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12177 guint action, gboolean opening_multiple)
12179 gchar *body = NULL;
12180 GSList *new_msglist = NULL;
12181 MsgInfo *tmp_msginfo = NULL;
12182 gboolean originally_enc = FALSE;
12183 gboolean originally_sig = FALSE;
12184 Compose *compose = NULL;
12185 gchar *s_system = NULL;
12187 cm_return_if_fail(msginfo_list != NULL);
12189 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12190 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12191 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12193 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12194 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12195 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12196 orig_msginfo, mimeinfo);
12197 if (tmp_msginfo != NULL) {
12198 new_msglist = g_slist_append(NULL, tmp_msginfo);
12200 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12201 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12202 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12204 tmp_msginfo->folder = orig_msginfo->folder;
12205 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12206 if (orig_msginfo->tags) {
12207 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12208 tmp_msginfo->folder->tags_dirty = TRUE;
12214 if (!opening_multiple && msgview != NULL)
12215 body = messageview_get_selection(msgview);
12218 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12219 procmsg_msginfo_free(&tmp_msginfo);
12220 g_slist_free(new_msglist);
12222 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12224 if (compose && originally_enc) {
12225 compose_force_encryption(compose, compose->account, FALSE, s_system);
12228 if (compose && originally_sig && compose->account->default_sign_reply) {
12229 compose_force_signing(compose, compose->account, s_system);
12233 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12236 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12239 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12240 && msginfo_list != NULL
12241 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12242 GSList *cur = msginfo_list;
12243 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12244 "messages. Opening the windows "
12245 "could take some time. Do you "
12246 "want to continue?"),
12247 g_slist_length(msginfo_list));
12248 if (g_slist_length(msginfo_list) > 9
12249 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12250 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12255 /* We'll open multiple compose windows */
12256 /* let the WM place the next windows */
12257 compose_force_window_origin = FALSE;
12258 for (; cur; cur = cur->next) {
12260 tmplist.data = cur->data;
12261 tmplist.next = NULL;
12262 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12264 compose_force_window_origin = TRUE;
12266 /* forwarding multiple mails as attachments is done via a
12267 * single compose window */
12268 if (msginfo_list != NULL) {
12269 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12270 } else if (msgview != NULL) {
12272 tmplist.data = msgview->msginfo;
12273 tmplist.next = NULL;
12274 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12276 debug_print("Nothing to reply to\n");
12281 void compose_check_for_email_account(Compose *compose)
12283 PrefsAccount *ac = NULL, *curr = NULL;
12289 if (compose->account && compose->account->protocol == A_NNTP) {
12290 ac = account_get_cur_account();
12291 if (ac->protocol == A_NNTP) {
12292 list = account_get_list();
12294 for( ; list != NULL ; list = g_list_next(list)) {
12295 curr = (PrefsAccount *) list->data;
12296 if (curr->protocol != A_NNTP) {
12302 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12307 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12308 const gchar *address)
12310 GSList *msginfo_list = NULL;
12311 gchar *body = messageview_get_selection(msgview);
12314 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12316 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12317 compose_check_for_email_account(compose);
12318 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12319 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12320 compose_reply_set_subject(compose, msginfo);
12323 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12326 void compose_set_position(Compose *compose, gint pos)
12328 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12330 gtkut_text_view_set_position(text, pos);
12333 gboolean compose_search_string(Compose *compose,
12334 const gchar *str, gboolean case_sens)
12336 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12338 return gtkut_text_view_search_string(text, str, case_sens);
12341 gboolean compose_search_string_backward(Compose *compose,
12342 const gchar *str, gboolean case_sens)
12344 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12346 return gtkut_text_view_search_string_backward(text, str, case_sens);
12349 /* allocate a msginfo structure and populate its data from a compose data structure */
12350 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12352 MsgInfo *newmsginfo;
12354 gchar date[RFC822_DATE_BUFFSIZE];
12356 cm_return_val_if_fail( compose != NULL, NULL );
12358 newmsginfo = procmsg_msginfo_new();
12361 get_rfc822_date(date, sizeof(date));
12362 newmsginfo->date = g_strdup(date);
12365 if (compose->from_name) {
12366 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12367 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12371 if (compose->subject_entry)
12372 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12374 /* to, cc, reply-to, newsgroups */
12375 for (list = compose->header_list; list; list = list->next) {
12376 gchar *header = gtk_editable_get_chars(
12378 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12379 gchar *entry = gtk_editable_get_chars(
12380 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12382 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12383 if ( newmsginfo->to == NULL ) {
12384 newmsginfo->to = g_strdup(entry);
12385 } else if (entry && *entry) {
12386 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12387 g_free(newmsginfo->to);
12388 newmsginfo->to = tmp;
12391 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12392 if ( newmsginfo->cc == NULL ) {
12393 newmsginfo->cc = g_strdup(entry);
12394 } else if (entry && *entry) {
12395 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12396 g_free(newmsginfo->cc);
12397 newmsginfo->cc = tmp;
12400 if ( strcasecmp(header,
12401 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12402 if ( newmsginfo->newsgroups == NULL ) {
12403 newmsginfo->newsgroups = g_strdup(entry);
12404 } else if (entry && *entry) {
12405 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12406 g_free(newmsginfo->newsgroups);
12407 newmsginfo->newsgroups = tmp;
12415 /* other data is unset */
12421 /* update compose's dictionaries from folder dict settings */
12422 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12423 FolderItem *folder_item)
12425 cm_return_if_fail(compose != NULL);
12427 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12428 FolderItemPrefs *prefs = folder_item->prefs;
12430 if (prefs->enable_default_dictionary)
12431 gtkaspell_change_dict(compose->gtkaspell,
12432 prefs->default_dictionary, FALSE);
12433 if (folder_item->prefs->enable_default_alt_dictionary)
12434 gtkaspell_change_alt_dict(compose->gtkaspell,
12435 prefs->default_alt_dictionary);
12436 if (prefs->enable_default_dictionary
12437 || prefs->enable_default_alt_dictionary)
12438 compose_spell_menu_changed(compose);
12443 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12445 Compose *compose = (Compose *)data;
12447 cm_return_if_fail(compose != NULL);
12449 gtk_widget_grab_focus(compose->text);
12452 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12454 gtk_combo_box_popup(GTK_COMBO_BOX(data));