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]);
2349 g_free(queueheader_buf);
2351 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2353 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2354 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2355 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2356 if (orig_item != NULL) {
2357 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2361 g_free(queueheader_buf);
2363 /* Get manual headers */
2364 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2365 "X-Claws-Manual-Headers:")) {
2366 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2367 if (listmh && *listmh != '\0') {
2368 debug_print("Got manual headers: %s\n", listmh);
2369 manual_headers = procheader_entries_from_str(listmh);
2372 g_free(queueheader_buf);
2375 account = msginfo->folder->folder->account;
2378 if (!account && prefs_common.reedit_account_autosel) {
2380 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2381 extract_address(from);
2382 account = account_find_from_address(from, FALSE);
2387 account = cur_account;
2389 cm_return_val_if_fail(account != NULL, NULL);
2391 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2393 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2394 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2395 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2396 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2397 compose->autowrap = autowrap;
2398 compose->replyinfo = replyinfo;
2399 compose->fwdinfo = fwdinfo;
2401 compose->updating = TRUE;
2402 compose->priority = priority;
2404 if (privacy_system != NULL) {
2405 compose->privacy_system = privacy_system;
2406 compose_use_signing(compose, use_signing);
2407 compose_use_encryption(compose, use_encryption);
2408 compose_update_privacy_system_menu_item(compose, FALSE);
2410 compose_activate_privacy_system(compose, account, FALSE);
2412 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2413 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2414 (account->default_encrypt || account->default_sign))
2415 COMPOSE_PRIVACY_WARNING();
2417 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2418 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2420 compose_extract_original_charset(compose);
2422 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2423 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2424 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2425 gchar *queueheader_buf = NULL;
2427 /* Set message save folder */
2428 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2429 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2430 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2431 compose_set_save_to(compose, &queueheader_buf[4]);
2432 g_free(queueheader_buf);
2434 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2435 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2437 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2439 g_free(queueheader_buf);
2443 if (compose_parse_header(compose, msginfo) < 0) {
2444 compose->updating = FALSE;
2445 compose_destroy(compose);
2448 compose_reedit_set_entry(compose, msginfo);
2450 textview = GTK_TEXT_VIEW(compose->text);
2451 textbuf = gtk_text_view_get_buffer(textview);
2452 compose_create_tags(textview, compose);
2454 mark = gtk_text_buffer_get_insert(textbuf);
2455 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2457 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2458 G_CALLBACK(compose_changed_cb),
2461 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2462 fp = procmime_get_first_encrypted_text_content(msginfo);
2464 compose_force_encryption(compose, account, TRUE, NULL);
2467 fp = procmime_get_first_text_content(msginfo);
2470 g_warning("can't get text part");
2474 gchar buf[BUFFSIZE];
2475 gboolean prev_autowrap;
2476 GtkTextBuffer *buffer;
2478 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2480 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2486 compose_attach_parts(compose, msginfo);
2488 compose_colorize_signature(compose);
2490 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2491 G_CALLBACK(compose_changed_cb),
2494 if (manual_headers != NULL) {
2495 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2496 procheader_entries_free(manual_headers);
2497 compose->updating = FALSE;
2498 compose_destroy(compose);
2501 procheader_entries_free(manual_headers);
2504 gtk_widget_grab_focus(compose->text);
2506 if (prefs_common.auto_exteditor) {
2507 compose_exec_ext_editor(compose);
2509 compose->modified = FALSE;
2510 compose_set_title(compose);
2512 compose->updating = FALSE;
2513 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2514 SCROLL_TO_CURSOR(compose);
2516 if (compose->deferred_destroy) {
2517 compose_destroy(compose);
2521 compose->sig_str = account_get_signature_str(compose->account);
2523 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2528 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2535 cm_return_val_if_fail(msginfo != NULL, NULL);
2538 account = account_get_reply_account(msginfo,
2539 prefs_common.reply_account_autosel);
2540 cm_return_val_if_fail(account != NULL, NULL);
2542 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2544 compose->updating = TRUE;
2546 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2547 compose->replyinfo = NULL;
2548 compose->fwdinfo = NULL;
2550 compose_show_first_last_header(compose, TRUE);
2552 gtk_widget_grab_focus(compose->header_last->entry);
2554 filename = procmsg_get_message_file(msginfo);
2556 if (filename == NULL) {
2557 compose->updating = FALSE;
2558 compose_destroy(compose);
2563 compose->redirect_filename = filename;
2565 /* Set save folder */
2566 item = msginfo->folder;
2567 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2568 gchar *folderidentifier;
2570 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2571 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2572 folderidentifier = folder_item_get_identifier(item);
2573 compose_set_save_to(compose, folderidentifier);
2574 g_free(folderidentifier);
2577 compose_attach_parts(compose, msginfo);
2579 if (msginfo->subject)
2580 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2582 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2584 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2585 _("The body of the \"Redirect\" template has an error at line %d."));
2586 quote_fmt_reset_vartable();
2587 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2589 compose_colorize_signature(compose);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2598 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2599 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2600 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2601 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2602 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2603 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2604 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2606 if (compose->toolbar->draft_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2608 if (compose->toolbar->insert_btn)
2609 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2610 if (compose->toolbar->attach_btn)
2611 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2612 if (compose->toolbar->sig_btn)
2613 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2614 if (compose->toolbar->exteditor_btn)
2615 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2616 if (compose->toolbar->linewrap_current_btn)
2617 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2618 if (compose->toolbar->linewrap_all_btn)
2619 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2620 if (compose->toolbar->privacy_sign_btn)
2621 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2622 if (compose->toolbar->privacy_encrypt_btn)
2623 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2625 compose->modified = FALSE;
2626 compose_set_title(compose);
2627 compose->updating = FALSE;
2628 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2629 SCROLL_TO_CURSOR(compose);
2631 if (compose->deferred_destroy) {
2632 compose_destroy(compose);
2636 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2641 const GList *compose_get_compose_list(void)
2643 return compose_list;
2646 void compose_entry_append(Compose *compose, const gchar *address,
2647 ComposeEntryType type, ComposePrefType pref_type)
2649 const gchar *header;
2651 gboolean in_quote = FALSE;
2652 if (!address || *address == '\0') return;
2659 header = N_("Bcc:");
2661 case COMPOSE_REPLYTO:
2662 header = N_("Reply-To:");
2664 case COMPOSE_NEWSGROUPS:
2665 header = N_("Newsgroups:");
2667 case COMPOSE_FOLLOWUPTO:
2668 header = N_( "Followup-To:");
2670 case COMPOSE_INREPLYTO:
2671 header = N_( "In-Reply-To:");
2678 header = prefs_common_translated_header_name(header);
2680 cur = begin = (gchar *)address;
2682 /* we separate the line by commas, but not if we're inside a quoted
2684 while (*cur != '\0') {
2686 in_quote = !in_quote;
2687 if (*cur == ',' && !in_quote) {
2688 gchar *tmp = g_strdup(begin);
2690 tmp[cur-begin]='\0';
2693 while (*tmp == ' ' || *tmp == '\t')
2695 compose_add_header_entry(compose, header, tmp, pref_type);
2696 compose_entry_indicate(compose, tmp);
2703 gchar *tmp = g_strdup(begin);
2705 tmp[cur-begin]='\0';
2706 while (*tmp == ' ' || *tmp == '\t')
2708 compose_add_header_entry(compose, header, tmp, pref_type);
2709 compose_entry_indicate(compose, tmp);
2714 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2719 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2720 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2721 if (gtk_entry_get_text(entry) &&
2722 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2723 gtk_widget_modify_base(
2724 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2725 GTK_STATE_NORMAL, &default_header_bgcolor);
2726 gtk_widget_modify_text(
2727 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2728 GTK_STATE_NORMAL, &default_header_color);
2733 void compose_toolbar_cb(gint action, gpointer data)
2735 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2736 Compose *compose = (Compose*)toolbar_item->parent;
2738 cm_return_if_fail(compose != NULL);
2742 compose_send_cb(NULL, compose);
2745 compose_send_later_cb(NULL, compose);
2748 compose_draft(compose, COMPOSE_QUIT_EDITING);
2751 compose_insert_file_cb(NULL, compose);
2754 compose_attach_cb(NULL, compose);
2757 compose_insert_sig(compose, FALSE);
2760 compose_insert_sig(compose, TRUE);
2763 compose_ext_editor_cb(NULL, compose);
2765 case A_LINEWRAP_CURRENT:
2766 compose_beautify_paragraph(compose, NULL, TRUE);
2768 case A_LINEWRAP_ALL:
2769 compose_wrap_all_full(compose, TRUE);
2772 compose_address_cb(NULL, compose);
2775 case A_CHECK_SPELLING:
2776 compose_check_all(NULL, compose);
2779 case A_PRIVACY_SIGN:
2781 case A_PRIVACY_ENCRYPT:
2788 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2793 gchar *subject = NULL;
2797 gchar **attach = NULL;
2798 gchar *inreplyto = NULL;
2799 MailField mfield = NO_FIELD_PRESENT;
2801 /* get mailto parts but skip from */
2802 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2805 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2806 mfield = TO_FIELD_PRESENT;
2809 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2811 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2813 if (!g_utf8_validate (subject, -1, NULL)) {
2814 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2815 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2818 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2820 mfield = SUBJECT_FIELD_PRESENT;
2823 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2824 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2827 gboolean prev_autowrap = compose->autowrap;
2829 compose->autowrap = FALSE;
2831 mark = gtk_text_buffer_get_insert(buffer);
2832 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2834 if (!g_utf8_validate (body, -1, NULL)) {
2835 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2836 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2839 gtk_text_buffer_insert(buffer, &iter, body, -1);
2841 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2843 compose->autowrap = prev_autowrap;
2844 if (compose->autowrap)
2845 compose_wrap_all(compose);
2846 mfield = BODY_FIELD_PRESENT;
2850 gint i = 0, att = 0;
2851 gchar *warn_files = NULL;
2852 while (attach[i] != NULL) {
2853 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2854 if (utf8_filename) {
2855 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2856 gchar *tmp = g_strdup_printf("%s%s\n",
2857 warn_files?warn_files:"",
2863 g_free(utf8_filename);
2865 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2870 alertpanel_notice(ngettext(
2871 "The following file has been attached: \n%s",
2872 "The following files have been attached: \n%s", att), warn_files);
2877 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2890 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2892 static HeaderEntry hentry[] = {
2893 {"Reply-To:", NULL, TRUE },
2894 {"Cc:", NULL, TRUE },
2895 {"References:", NULL, FALSE },
2896 {"Bcc:", NULL, TRUE },
2897 {"Newsgroups:", NULL, TRUE },
2898 {"Followup-To:", NULL, TRUE },
2899 {"List-Post:", NULL, FALSE },
2900 {"X-Priority:", NULL, FALSE },
2901 {NULL, NULL, FALSE }
2918 cm_return_val_if_fail(msginfo != NULL, -1);
2920 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2921 procheader_get_header_fields(fp, hentry);
2924 if (hentry[H_REPLY_TO].body != NULL) {
2925 if (hentry[H_REPLY_TO].body[0] != '\0') {
2927 conv_unmime_header(hentry[H_REPLY_TO].body,
2930 g_free(hentry[H_REPLY_TO].body);
2931 hentry[H_REPLY_TO].body = NULL;
2933 if (hentry[H_CC].body != NULL) {
2934 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2935 g_free(hentry[H_CC].body);
2936 hentry[H_CC].body = NULL;
2938 if (hentry[H_REFERENCES].body != NULL) {
2939 if (compose->mode == COMPOSE_REEDIT)
2940 compose->references = hentry[H_REFERENCES].body;
2942 compose->references = compose_parse_references
2943 (hentry[H_REFERENCES].body, msginfo->msgid);
2944 g_free(hentry[H_REFERENCES].body);
2946 hentry[H_REFERENCES].body = NULL;
2948 if (hentry[H_BCC].body != NULL) {
2949 if (compose->mode == COMPOSE_REEDIT)
2951 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2952 g_free(hentry[H_BCC].body);
2953 hentry[H_BCC].body = NULL;
2955 if (hentry[H_NEWSGROUPS].body != NULL) {
2956 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2957 hentry[H_NEWSGROUPS].body = NULL;
2959 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2960 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2961 compose->followup_to =
2962 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2965 g_free(hentry[H_FOLLOWUP_TO].body);
2966 hentry[H_FOLLOWUP_TO].body = NULL;
2968 if (hentry[H_LIST_POST].body != NULL) {
2969 gchar *to = NULL, *start = NULL;
2971 extract_address(hentry[H_LIST_POST].body);
2972 if (hentry[H_LIST_POST].body[0] != '\0') {
2973 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2975 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2976 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2979 g_free(compose->ml_post);
2980 compose->ml_post = to;
2983 g_free(hentry[H_LIST_POST].body);
2984 hentry[H_LIST_POST].body = NULL;
2987 /* CLAWS - X-Priority */
2988 if (compose->mode == COMPOSE_REEDIT)
2989 if (hentry[H_X_PRIORITY].body != NULL) {
2992 priority = atoi(hentry[H_X_PRIORITY].body);
2993 g_free(hentry[H_X_PRIORITY].body);
2995 hentry[H_X_PRIORITY].body = NULL;
2997 if (priority < PRIORITY_HIGHEST ||
2998 priority > PRIORITY_LOWEST)
2999 priority = PRIORITY_NORMAL;
3001 compose->priority = priority;
3004 if (compose->mode == COMPOSE_REEDIT) {
3005 if (msginfo->inreplyto && *msginfo->inreplyto)
3006 compose->inreplyto = g_strdup(msginfo->inreplyto);
3008 if (msginfo->msgid && *msginfo->msgid &&
3009 compose->folder != NULL &&
3010 compose->folder->stype == F_DRAFT)
3011 compose->msgid = g_strdup(msginfo->msgid);
3013 if (msginfo->msgid && *msginfo->msgid)
3014 compose->inreplyto = g_strdup(msginfo->msgid);
3016 if (!compose->references) {
3017 if (msginfo->msgid && *msginfo->msgid) {
3018 if (msginfo->inreplyto && *msginfo->inreplyto)
3019 compose->references =
3020 g_strdup_printf("<%s>\n\t<%s>",
3024 compose->references =
3025 g_strconcat("<", msginfo->msgid, ">",
3027 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3028 compose->references =
3029 g_strconcat("<", msginfo->inreplyto, ">",
3038 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3043 cm_return_val_if_fail(msginfo != NULL, -1);
3045 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3046 procheader_get_header_fields(fp, entries);
3050 while (he != NULL && he->name != NULL) {
3052 GtkListStore *model = NULL;
3054 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3055 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3056 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3057 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3058 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3065 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3067 GSList *ref_id_list, *cur;
3071 ref_id_list = references_list_append(NULL, ref);
3072 if (!ref_id_list) return NULL;
3073 if (msgid && *msgid)
3074 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3079 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3080 /* "<" + Message-ID + ">" + CR+LF+TAB */
3081 len += strlen((gchar *)cur->data) + 5;
3083 if (len > MAX_REFERENCES_LEN) {
3084 /* remove second message-ID */
3085 if (ref_id_list && ref_id_list->next &&
3086 ref_id_list->next->next) {
3087 g_free(ref_id_list->next->data);
3088 ref_id_list = g_slist_remove
3089 (ref_id_list, ref_id_list->next->data);
3091 slist_free_strings_full(ref_id_list);
3098 new_ref = g_string_new("");
3099 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3100 if (new_ref->len > 0)
3101 g_string_append(new_ref, "\n\t");
3102 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3105 slist_free_strings_full(ref_id_list);
3107 new_ref_str = new_ref->str;
3108 g_string_free(new_ref, FALSE);
3113 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3114 const gchar *fmt, const gchar *qmark,
3115 const gchar *body, gboolean rewrap,
3116 gboolean need_unescape,
3117 const gchar *err_msg)
3119 MsgInfo* dummyinfo = NULL;
3120 gchar *quote_str = NULL;
3122 gboolean prev_autowrap;
3123 const gchar *trimmed_body = body;
3124 gint cursor_pos = -1;
3125 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3126 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3131 SIGNAL_BLOCK(buffer);
3134 dummyinfo = compose_msginfo_new_from_compose(compose);
3135 msginfo = dummyinfo;
3138 if (qmark != NULL) {
3140 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3141 compose->gtkaspell);
3143 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3145 quote_fmt_scan_string(qmark);
3148 buf = quote_fmt_get_buffer();
3151 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3153 Xstrdup_a(quote_str, buf, goto error)
3156 if (fmt && *fmt != '\0') {
3159 while (*trimmed_body == '\n')
3163 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3164 compose->gtkaspell);
3166 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3168 if (need_unescape) {
3171 /* decode \-escape sequences in the internal representation of the quote format */
3172 tmp = g_malloc(strlen(fmt)+1);
3173 pref_get_unescaped_pref(tmp, fmt);
3174 quote_fmt_scan_string(tmp);
3178 quote_fmt_scan_string(fmt);
3182 buf = quote_fmt_get_buffer();
3185 gint line = quote_fmt_get_line();
3186 alertpanel_error(err_msg, line);
3194 prev_autowrap = compose->autowrap;
3195 compose->autowrap = FALSE;
3197 mark = gtk_text_buffer_get_insert(buffer);
3198 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3199 if (g_utf8_validate(buf, -1, NULL)) {
3200 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3202 gchar *tmpout = NULL;
3203 tmpout = conv_codeset_strdup
3204 (buf, conv_get_locale_charset_str_no_utf8(),
3206 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3208 tmpout = g_malloc(strlen(buf)*2+1);
3209 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3211 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3215 cursor_pos = quote_fmt_get_cursor_pos();
3216 if (cursor_pos == -1)
3217 cursor_pos = gtk_text_iter_get_offset(&iter);
3218 compose->set_cursor_pos = cursor_pos;
3220 gtk_text_buffer_get_start_iter(buffer, &iter);
3221 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3222 gtk_text_buffer_place_cursor(buffer, &iter);
3224 compose->autowrap = prev_autowrap;
3225 if (compose->autowrap && rewrap)
3226 compose_wrap_all(compose);
3233 SIGNAL_UNBLOCK(buffer);
3235 procmsg_msginfo_free( &dummyinfo );
3240 /* if ml_post is of type addr@host and from is of type
3241 * addr-anything@host, return TRUE
3243 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3245 gchar *left_ml = NULL;
3246 gchar *right_ml = NULL;
3247 gchar *left_from = NULL;
3248 gchar *right_from = NULL;
3249 gboolean result = FALSE;
3251 if (!ml_post || !from)
3254 left_ml = g_strdup(ml_post);
3255 if (strstr(left_ml, "@")) {
3256 right_ml = strstr(left_ml, "@")+1;
3257 *(strstr(left_ml, "@")) = '\0';
3260 left_from = g_strdup(from);
3261 if (strstr(left_from, "@")) {
3262 right_from = strstr(left_from, "@")+1;
3263 *(strstr(left_from, "@")) = '\0';
3266 if (right_ml && right_from
3267 && !strncmp(left_from, left_ml, strlen(left_ml))
3268 && !strcmp(right_from, right_ml)) {
3277 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3278 gboolean respect_default_to)
3282 if (!folder || !folder->prefs)
3285 if (respect_default_to && folder->prefs->enable_default_to) {
3286 compose_entry_append(compose, folder->prefs->default_to,
3287 COMPOSE_TO, PREF_FOLDER);
3288 compose_entry_indicate(compose, folder->prefs->default_to);
3290 if (folder->prefs->enable_default_cc) {
3291 compose_entry_append(compose, folder->prefs->default_cc,
3292 COMPOSE_CC, PREF_FOLDER);
3293 compose_entry_indicate(compose, folder->prefs->default_cc);
3295 if (folder->prefs->enable_default_bcc) {
3296 compose_entry_append(compose, folder->prefs->default_bcc,
3297 COMPOSE_BCC, PREF_FOLDER);
3298 compose_entry_indicate(compose, folder->prefs->default_bcc);
3300 if (folder->prefs->enable_default_replyto) {
3301 compose_entry_append(compose, folder->prefs->default_replyto,
3302 COMPOSE_REPLYTO, PREF_FOLDER);
3303 compose_entry_indicate(compose, folder->prefs->default_replyto);
3307 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3312 if (!compose || !msginfo)
3315 if (msginfo->subject && *msginfo->subject) {
3316 buf = p = g_strdup(msginfo->subject);
3317 p += subject_get_prefix_length(p);
3318 memmove(buf, p, strlen(p) + 1);
3320 buf2 = g_strdup_printf("Re: %s", buf);
3321 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3326 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3329 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3330 gboolean to_all, gboolean to_ml,
3332 gboolean followup_and_reply_to)
3334 GSList *cc_list = NULL;
3337 gchar *replyto = NULL;
3338 gchar *ac_email = NULL;
3340 gboolean reply_to_ml = FALSE;
3341 gboolean default_reply_to = FALSE;
3343 cm_return_if_fail(compose->account != NULL);
3344 cm_return_if_fail(msginfo != NULL);
3346 reply_to_ml = to_ml && compose->ml_post;
3348 default_reply_to = msginfo->folder &&
3349 msginfo->folder->prefs->enable_default_reply_to;
3351 if (compose->account->protocol != A_NNTP) {
3352 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3354 if (reply_to_ml && !default_reply_to) {
3356 gboolean is_subscr = is_subscription(compose->ml_post,
3359 /* normal answer to ml post with a reply-to */
3360 compose_entry_append(compose,
3362 COMPOSE_TO, PREF_ML);
3363 if (compose->replyto)
3364 compose_entry_append(compose,
3366 COMPOSE_CC, PREF_ML);
3368 /* answer to subscription confirmation */
3369 if (compose->replyto)
3370 compose_entry_append(compose,
3372 COMPOSE_TO, PREF_ML);
3373 else if (msginfo->from)
3374 compose_entry_append(compose,
3376 COMPOSE_TO, PREF_ML);
3379 else if (!(to_all || to_sender) && default_reply_to) {
3380 compose_entry_append(compose,
3381 msginfo->folder->prefs->default_reply_to,
3382 COMPOSE_TO, PREF_FOLDER);
3383 compose_entry_indicate(compose,
3384 msginfo->folder->prefs->default_reply_to);
3390 compose_entry_append(compose, msginfo->from,
3391 COMPOSE_TO, PREF_NONE);
3393 Xstrdup_a(tmp1, msginfo->from, return);
3394 extract_address(tmp1);
3395 compose_entry_append(compose,
3396 (!account_find_from_address(tmp1, FALSE))
3399 COMPOSE_TO, PREF_NONE);
3400 if (compose->replyto)
3401 compose_entry_append(compose,
3403 COMPOSE_CC, PREF_NONE);
3405 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3406 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3407 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3408 if (compose->replyto) {
3409 compose_entry_append(compose,
3411 COMPOSE_TO, PREF_NONE);
3413 compose_entry_append(compose,
3414 msginfo->from ? msginfo->from : "",
3415 COMPOSE_TO, PREF_NONE);
3418 /* replying to own mail, use original recp */
3419 compose_entry_append(compose,
3420 msginfo->to ? msginfo->to : "",
3421 COMPOSE_TO, PREF_NONE);
3422 compose_entry_append(compose,
3423 msginfo->cc ? msginfo->cc : "",
3424 COMPOSE_CC, PREF_NONE);
3429 if (to_sender || (compose->followup_to &&
3430 !strncmp(compose->followup_to, "poster", 6)))
3431 compose_entry_append
3433 (compose->replyto ? compose->replyto :
3434 msginfo->from ? msginfo->from : ""),
3435 COMPOSE_TO, PREF_NONE);
3437 else if (followup_and_reply_to || to_all) {
3438 compose_entry_append
3440 (compose->replyto ? compose->replyto :
3441 msginfo->from ? msginfo->from : ""),
3442 COMPOSE_TO, PREF_NONE);
3444 compose_entry_append
3446 compose->followup_to ? compose->followup_to :
3447 compose->newsgroups ? compose->newsgroups : "",
3448 COMPOSE_NEWSGROUPS, PREF_NONE);
3450 compose_entry_append
3452 msginfo->cc ? msginfo->cc : "",
3453 COMPOSE_CC, PREF_NONE);
3456 compose_entry_append
3458 compose->followup_to ? compose->followup_to :
3459 compose->newsgroups ? compose->newsgroups : "",
3460 COMPOSE_NEWSGROUPS, PREF_NONE);
3462 compose_reply_set_subject(compose, msginfo);
3464 if (to_ml && compose->ml_post) return;
3465 if (!to_all || compose->account->protocol == A_NNTP) return;
3467 if (compose->replyto) {
3468 Xstrdup_a(replyto, compose->replyto, return);
3469 extract_address(replyto);
3471 if (msginfo->from) {
3472 Xstrdup_a(from, msginfo->from, return);
3473 extract_address(from);
3476 if (replyto && from)
3477 cc_list = address_list_append_with_comments(cc_list, from);
3478 if (to_all && msginfo->folder &&
3479 msginfo->folder->prefs->enable_default_reply_to)
3480 cc_list = address_list_append_with_comments(cc_list,
3481 msginfo->folder->prefs->default_reply_to);
3482 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3483 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3485 ac_email = g_utf8_strdown(compose->account->address, -1);
3488 for (cur = cc_list; cur != NULL; cur = cur->next) {
3489 gchar *addr = g_utf8_strdown(cur->data, -1);
3490 extract_address(addr);
3492 if (strcmp(ac_email, addr))
3493 compose_entry_append(compose, (gchar *)cur->data,
3494 COMPOSE_CC, PREF_NONE);
3496 debug_print("Cc address same as compose account's, ignoring\n");
3501 slist_free_strings_full(cc_list);
3507 #define SET_ENTRY(entry, str) \
3510 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3513 #define SET_ADDRESS(type, str) \
3516 compose_entry_append(compose, str, type, PREF_NONE); \
3519 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3521 cm_return_if_fail(msginfo != NULL);
3523 SET_ENTRY(subject_entry, msginfo->subject);
3524 SET_ENTRY(from_name, msginfo->from);
3525 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3526 SET_ADDRESS(COMPOSE_CC, compose->cc);
3527 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3528 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3529 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3530 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3532 compose_update_priority_menu_item(compose);
3533 compose_update_privacy_system_menu_item(compose, FALSE);
3534 compose_show_first_last_header(compose, TRUE);
3540 static void compose_insert_sig(Compose *compose, gboolean replace)
3542 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3543 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3545 GtkTextIter iter, iter_end;
3546 gint cur_pos, ins_pos;
3547 gboolean prev_autowrap;
3548 gboolean found = FALSE;
3549 gboolean exists = FALSE;
3551 cm_return_if_fail(compose->account != NULL);
3555 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3556 G_CALLBACK(compose_changed_cb),
3559 mark = gtk_text_buffer_get_insert(buffer);
3560 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3561 cur_pos = gtk_text_iter_get_offset (&iter);
3564 gtk_text_buffer_get_end_iter(buffer, &iter);
3566 exists = (compose->sig_str != NULL);
3569 GtkTextIter first_iter, start_iter, end_iter;
3571 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3573 if (!exists || compose->sig_str[0] == '\0')
3576 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3577 compose->signature_tag);
3580 /* include previous \n\n */
3581 gtk_text_iter_backward_chars(&first_iter, 1);
3582 start_iter = first_iter;
3583 end_iter = first_iter;
3585 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3586 compose->signature_tag);
3587 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3588 compose->signature_tag);
3590 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3596 g_free(compose->sig_str);
3597 compose->sig_str = account_get_signature_str(compose->account);
3599 cur_pos = gtk_text_iter_get_offset(&iter);
3601 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3602 g_free(compose->sig_str);
3603 compose->sig_str = NULL;
3605 if (compose->sig_inserted == FALSE)
3606 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3607 compose->sig_inserted = TRUE;
3609 cur_pos = gtk_text_iter_get_offset(&iter);
3610 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3612 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3613 gtk_text_iter_forward_chars(&iter, 1);
3614 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3615 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3617 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3618 cur_pos = gtk_text_buffer_get_char_count (buffer);
3621 /* put the cursor where it should be
3622 * either where the quote_fmt says, either where it was */
3623 if (compose->set_cursor_pos < 0)
3624 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3626 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3627 compose->set_cursor_pos);
3629 compose->set_cursor_pos = -1;
3630 gtk_text_buffer_place_cursor(buffer, &iter);
3631 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3632 G_CALLBACK(compose_changed_cb),
3638 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3641 GtkTextBuffer *buffer;
3644 const gchar *cur_encoding;
3645 gchar buf[BUFFSIZE];
3648 gboolean prev_autowrap;
3652 GError *error = NULL;
3658 GString *file_contents = NULL;
3659 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3661 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3663 /* get the size of the file we are about to insert */
3665 f = g_file_new_for_path(file);
3666 fi = g_file_query_info(f, "standard::size",
3667 G_FILE_QUERY_INFO_NONE, NULL, &error);
3669 if (error != NULL) {
3670 g_warning(error->message);
3672 g_error_free(error);
3676 ret = g_stat(file, &file_stat);
3679 gchar *shortfile = g_path_get_basename(file);
3680 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3682 return COMPOSE_INSERT_NO_FILE;
3683 } else if (prefs_common.warn_large_insert == TRUE) {
3685 size = g_file_info_get_size(fi);
3689 size = file_stat.st_size;
3692 /* ask user for confirmation if the file is large */
3693 if (prefs_common.warn_large_insert_size < 0 ||
3694 size > ((goffset) prefs_common.warn_large_insert_size * 1024)) {
3698 msg = g_strdup_printf(_("You are about to insert a file of %s "
3699 "in the message body. Are you sure you want to do that?"),
3700 to_human_readable(size));
3701 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3702 _("_Insert"), NULL, ALERTFOCUS_SECOND, TRUE,
3703 NULL, ALERT_QUESTION);
3706 /* do we ask for confirmation next time? */
3707 if (aval & G_ALERTDISABLE) {
3708 /* no confirmation next time, disable feature in preferences */
3709 aval &= ~G_ALERTDISABLE;
3710 prefs_common.warn_large_insert = FALSE;
3713 /* abort file insertion if user canceled action */
3714 if (aval != G_ALERTALTERNATE) {
3715 return COMPOSE_INSERT_NO_FILE;
3721 if ((fp = claws_fopen(file, "rb")) == NULL) {
3722 FILE_OP_ERROR(file, "claws_fopen");
3723 return COMPOSE_INSERT_READ_ERROR;
3726 prev_autowrap = compose->autowrap;
3727 compose->autowrap = FALSE;
3729 text = GTK_TEXT_VIEW(compose->text);
3730 buffer = gtk_text_view_get_buffer(text);
3731 mark = gtk_text_buffer_get_insert(buffer);
3732 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3734 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3735 G_CALLBACK(text_inserted),
3738 cur_encoding = conv_get_locale_charset_str_no_utf8();
3740 file_contents = g_string_new("");
3741 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
3744 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3745 str = g_strdup(buf);
3747 codeconv_set_strict(TRUE);
3748 str = conv_codeset_strdup
3749 (buf, cur_encoding, CS_INTERNAL);
3750 codeconv_set_strict(FALSE);
3753 result = COMPOSE_INSERT_INVALID_CHARACTER;
3759 /* strip <CR> if DOS/Windows file,
3760 replace <CR> with <LF> if Macintosh file. */
3763 if (len > 0 && str[len - 1] != '\n') {
3765 if (str[len] == '\r') str[len] = '\n';
3768 file_contents = g_string_append(file_contents, str);
3772 if (result == COMPOSE_INSERT_SUCCESS) {
3773 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3775 compose_changed_cb(NULL, compose);
3776 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3777 G_CALLBACK(text_inserted),
3779 compose->autowrap = prev_autowrap;
3780 if (compose->autowrap)
3781 compose_wrap_all(compose);
3784 g_string_free(file_contents, TRUE);
3790 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3791 const gchar *filename,
3792 const gchar *content_type,
3793 const gchar *charset)
3801 GtkListStore *store;
3803 gboolean has_binary = FALSE;
3805 if (!is_file_exist(file)) {
3806 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3807 gboolean result = FALSE;
3808 if (file_from_uri && is_file_exist(file_from_uri)) {
3809 result = compose_attach_append(
3810 compose, file_from_uri,
3811 filename, content_type,
3814 g_free(file_from_uri);
3817 alertpanel_error("File %s doesn't exist or permission denied\n", filename);
3820 if ((size = get_file_size(file)) < 0) {
3821 alertpanel_error("Can't get file size of %s\n", filename);
3825 /* In batch mode, we allow 0-length files to be attached no questions asked */
3826 if (size == 0 && !compose->batch) {
3827 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3828 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3829 GTK_STOCK_CANCEL, _("_Attach anyway"), NULL,
3830 ALERTFOCUS_SECOND, FALSE, NULL, ALERT_WARNING);
3833 if (aval != G_ALERTALTERNATE) {
3837 if ((fp = claws_fopen(file, "rb")) == NULL) {
3838 alertpanel_error(_("Can't read %s."), filename);
3843 ainfo = g_new0(AttachInfo, 1);
3844 auto_ainfo = g_auto_pointer_new_with_free
3845 (ainfo, (GFreeFunc) compose_attach_info_free);
3846 ainfo->file = g_strdup(file);
3849 ainfo->content_type = g_strdup(content_type);
3850 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3852 MsgFlags flags = {0, 0};
3854 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3855 ainfo->encoding = ENC_7BIT;
3857 ainfo->encoding = ENC_8BIT;
3859 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3860 if (msginfo && msginfo->subject)
3861 name = g_strdup(msginfo->subject);
3863 name = g_path_get_basename(filename ? filename : file);
3865 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3867 procmsg_msginfo_free(&msginfo);
3869 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3870 ainfo->charset = g_strdup(charset);
3871 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3873 ainfo->encoding = ENC_BASE64;
3875 name = g_path_get_basename(filename ? filename : file);
3876 ainfo->name = g_strdup(name);
3880 ainfo->content_type = procmime_get_mime_type(file);
3881 if (!ainfo->content_type) {
3882 ainfo->content_type =
3883 g_strdup("application/octet-stream");
3884 ainfo->encoding = ENC_BASE64;
3885 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3887 procmime_get_encoding_for_text_file(file, &has_binary);
3889 ainfo->encoding = ENC_BASE64;
3890 name = g_path_get_basename(filename ? filename : file);
3891 ainfo->name = g_strdup(name);
3895 if (ainfo->name != NULL
3896 && !strcmp(ainfo->name, ".")) {
3897 g_free(ainfo->name);
3901 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3902 g_free(ainfo->content_type);
3903 ainfo->content_type = g_strdup("application/octet-stream");
3904 g_free(ainfo->charset);
3905 ainfo->charset = NULL;
3908 ainfo->size = (goffset)size;
3909 size_text = to_human_readable((goffset)size);
3911 store = GTK_LIST_STORE(gtk_tree_view_get_model
3912 (GTK_TREE_VIEW(compose->attach_clist)));
3914 gtk_list_store_append(store, &iter);
3915 gtk_list_store_set(store, &iter,
3916 COL_MIMETYPE, ainfo->content_type,
3917 COL_SIZE, size_text,
3918 COL_NAME, ainfo->name,
3919 COL_CHARSET, ainfo->charset,
3921 COL_AUTODATA, auto_ainfo,
3924 g_auto_pointer_free(auto_ainfo);
3925 compose_attach_update_label(compose);
3929 void compose_use_signing(Compose *compose, gboolean use_signing)
3931 compose->use_signing = use_signing;
3932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3935 void compose_use_encryption(Compose *compose, gboolean use_encryption)
3937 compose->use_encryption = use_encryption;
3938 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3941 #define NEXT_PART_NOT_CHILD(info) \
3943 node = info->node; \
3944 while (node->children) \
3945 node = g_node_last_child(node); \
3946 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3949 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3953 MimeInfo *firsttext = NULL;
3954 MimeInfo *encrypted = NULL;
3957 const gchar *partname = NULL;
3959 mimeinfo = procmime_scan_message(msginfo);
3960 if (!mimeinfo) return;
3962 if (mimeinfo->node->children == NULL) {
3963 procmime_mimeinfo_free_all(&mimeinfo);
3967 /* find first content part */
3968 child = (MimeInfo *) mimeinfo->node->children->data;
3969 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3970 child = (MimeInfo *)child->node->children->data;
3973 if (child->type == MIMETYPE_TEXT) {
3975 debug_print("First text part found\n");
3976 } else if (compose->mode == COMPOSE_REEDIT &&
3977 child->type == MIMETYPE_APPLICATION &&
3978 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3979 encrypted = (MimeInfo *)child->node->parent->data;
3982 child = (MimeInfo *) mimeinfo->node->children->data;
3983 while (child != NULL) {
3986 if (child == encrypted) {
3987 /* skip this part of tree */
3988 NEXT_PART_NOT_CHILD(child);
3992 if (child->type == MIMETYPE_MULTIPART) {
3993 /* get the actual content */
3994 child = procmime_mimeinfo_next(child);
3998 if (child == firsttext) {
3999 child = procmime_mimeinfo_next(child);
4003 outfile = procmime_get_tmp_file_name(child);
4004 if ((err = procmime_get_part(outfile, child)) < 0)
4005 g_warning("can't get the part of multipart message. (%s)", g_strerror(-err));
4007 gchar *content_type;
4009 content_type = procmime_get_content_type_str(child->type, child->subtype);
4011 /* if we meet a pgp signature, we don't attach it, but
4012 * we force signing. */
4013 if ((strcmp(content_type, "application/pgp-signature") &&
4014 strcmp(content_type, "application/pkcs7-signature") &&
4015 strcmp(content_type, "application/x-pkcs7-signature"))
4016 || compose->mode == COMPOSE_REDIRECT) {
4017 partname = procmime_mimeinfo_get_parameter(child, "filename");
4018 if (partname == NULL)
4019 partname = procmime_mimeinfo_get_parameter(child, "name");
4020 if (partname == NULL)
4022 compose_attach_append(compose, outfile,
4023 partname, content_type,
4024 procmime_mimeinfo_get_parameter(child, "charset"));
4026 compose_force_signing(compose, compose->account, NULL);
4028 g_free(content_type);
4031 NEXT_PART_NOT_CHILD(child);
4033 procmime_mimeinfo_free_all(&mimeinfo);
4036 #undef NEXT_PART_NOT_CHILD
4041 WAIT_FOR_INDENT_CHAR,
4042 WAIT_FOR_INDENT_CHAR_OR_SPACE,
4045 /* return indent length, we allow:
4046 indent characters followed by indent characters or spaces/tabs,
4047 alphabets and numbers immediately followed by indent characters,
4048 and the repeating sequences of the above
4049 If quote ends with multiple spaces, only the first one is included. */
4050 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
4051 const GtkTextIter *start, gint *len)
4053 GtkTextIter iter = *start;
4057 IndentState state = WAIT_FOR_INDENT_CHAR;
4060 gint alnum_count = 0;
4061 gint space_count = 0;
4064 if (prefs_common.quote_chars == NULL) {
4068 while (!gtk_text_iter_ends_line(&iter)) {
4069 wc = gtk_text_iter_get_char(&iter);
4070 if (g_unichar_iswide(wc))
4072 clen = g_unichar_to_utf8(wc, ch);
4076 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
4077 is_space = g_unichar_isspace(wc);
4079 if (state == WAIT_FOR_INDENT_CHAR) {
4080 if (!is_indent && !g_unichar_isalnum(wc))
4083 quote_len += alnum_count + space_count + 1;
4084 alnum_count = space_count = 0;
4085 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4088 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4089 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4093 else if (is_indent) {
4094 quote_len += alnum_count + space_count + 1;
4095 alnum_count = space_count = 0;
4098 state = WAIT_FOR_INDENT_CHAR;
4102 gtk_text_iter_forward_char(&iter);
4105 if (quote_len > 0 && space_count > 0)
4111 if (quote_len > 0) {
4113 gtk_text_iter_forward_chars(&iter, quote_len);
4114 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4120 /* return >0 if the line is itemized */
4121 static int compose_itemized_length(GtkTextBuffer *buffer,
4122 const GtkTextIter *start)
4124 GtkTextIter iter = *start;
4129 if (gtk_text_iter_ends_line(&iter))
4134 wc = gtk_text_iter_get_char(&iter);
4135 if (!g_unichar_isspace(wc))
4137 gtk_text_iter_forward_char(&iter);
4138 if (gtk_text_iter_ends_line(&iter))
4142 clen = g_unichar_to_utf8(wc, ch);
4143 if (!((clen == 1 && strchr("*-+", ch[0])) ||
4145 wc == 0x2022 || /* BULLET */
4146 wc == 0x2023 || /* TRIANGULAR BULLET */
4147 wc == 0x2043 || /* HYPHEN BULLET */
4148 wc == 0x204c || /* BLACK LEFTWARDS BULLET */
4149 wc == 0x204d || /* BLACK RIGHTWARDS BULLET */
4150 wc == 0x2219 || /* BULLET OPERATOR */
4151 wc == 0x25d8 || /* INVERSE BULLET */
4152 wc == 0x25e6 || /* WHITE BULLET */
4153 wc == 0x2619 || /* REVERSED ROTATED FLORAL HEART BULLET */
4154 wc == 0x2765 || /* ROTATED HEAVY BLACK HEART BULLET */
4155 wc == 0x2767 || /* ROTATED FLORAL HEART BULLET */
4156 wc == 0x29be || /* CIRCLED WHITE BULLET */
4157 wc == 0x29bf /* CIRCLED BULLET */
4161 gtk_text_iter_forward_char(&iter);
4162 if (gtk_text_iter_ends_line(&iter))
4164 wc = gtk_text_iter_get_char(&iter);
4165 if (g_unichar_isspace(wc)) {
4171 /* return the string at the start of the itemization */
4172 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4173 const GtkTextIter *start)
4175 GtkTextIter iter = *start;
4178 GString *item_chars = g_string_new("");
4181 if (gtk_text_iter_ends_line(&iter))
4186 wc = gtk_text_iter_get_char(&iter);
4187 if (!g_unichar_isspace(wc))
4189 gtk_text_iter_forward_char(&iter);
4190 if (gtk_text_iter_ends_line(&iter))
4192 g_string_append_unichar(item_chars, wc);
4195 str = item_chars->str;
4196 g_string_free(item_chars, FALSE);
4200 /* return the number of spaces at a line's start */
4201 static int compose_left_offset_length(GtkTextBuffer *buffer,
4202 const GtkTextIter *start)
4204 GtkTextIter iter = *start;
4207 if (gtk_text_iter_ends_line(&iter))
4211 wc = gtk_text_iter_get_char(&iter);
4212 if (!g_unichar_isspace(wc))
4215 gtk_text_iter_forward_char(&iter);
4216 if (gtk_text_iter_ends_line(&iter))
4220 gtk_text_iter_forward_char(&iter);
4221 if (gtk_text_iter_ends_line(&iter))
4226 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4227 const GtkTextIter *start,
4228 GtkTextIter *break_pos,
4232 GtkTextIter iter = *start, line_end = *start;
4233 PangoLogAttr *attrs;
4240 gboolean can_break = FALSE;
4241 gboolean do_break = FALSE;
4242 gboolean was_white = FALSE;
4243 gboolean prev_dont_break = FALSE;
4245 gtk_text_iter_forward_to_line_end(&line_end);
4246 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4247 len = g_utf8_strlen(str, -1);
4251 g_warning("compose_get_line_break_pos: len = 0!");
4255 /* g_print("breaking line: %d: %s (len = %d)\n",
4256 gtk_text_iter_get_line(&iter), str, len); */
4258 attrs = g_new(PangoLogAttr, len + 1);
4260 pango_default_break(str, -1, NULL, attrs, len + 1);
4264 /* skip quote and leading spaces */
4265 for (i = 0; *p != '\0' && i < len; i++) {
4268 wc = g_utf8_get_char(p);
4269 if (i >= quote_len && !g_unichar_isspace(wc))
4271 if (g_unichar_iswide(wc))
4273 else if (*p == '\t')
4277 p = g_utf8_next_char(p);
4280 for (; *p != '\0' && i < len; i++) {
4281 PangoLogAttr *attr = attrs + i;
4282 gunichar wc = g_utf8_get_char(p);
4285 /* attr->is_line_break will be false for some characters that
4286 * we want to break a line before, like '/' or ':', so we
4287 * also allow breaking on any non-wide character. The
4288 * mentioned pango attribute is still useful to decide on
4289 * line breaks when wide characters are involved. */
4290 if ((!g_unichar_iswide(wc) || attr->is_line_break)
4291 && can_break && was_white && !prev_dont_break)
4294 was_white = attr->is_white;
4296 /* don't wrap URI */
4297 if ((uri_len = get_uri_len(p)) > 0) {
4299 if (pos > 0 && col > max_col) {
4309 if (g_unichar_iswide(wc)) {
4311 if (prev_dont_break && can_break && attr->is_line_break)
4313 } else if (*p == '\t')
4317 if (pos > 0 && col > max_col) {
4322 if (*p == '-' || *p == '/')
4323 prev_dont_break = TRUE;
4325 prev_dont_break = FALSE;
4327 p = g_utf8_next_char(p);
4331 /* debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col); */
4336 *break_pos = *start;
4337 gtk_text_iter_set_line_offset(break_pos, pos);
4342 static gboolean compose_join_next_line(Compose *compose,
4343 GtkTextBuffer *buffer,
4345 const gchar *quote_str)
4347 GtkTextIter iter_ = *iter, cur, prev, next, end;
4348 PangoLogAttr attrs[3];
4350 gchar *next_quote_str;
4353 gboolean keep_cursor = FALSE;
4355 if (!gtk_text_iter_forward_line(&iter_) ||
4356 gtk_text_iter_ends_line(&iter_)) {
4359 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4361 if ((quote_str || next_quote_str) &&
4362 g_strcmp0(quote_str, next_quote_str) != 0) {
4363 g_free(next_quote_str);
4366 g_free(next_quote_str);
4369 if (quote_len > 0) {
4370 gtk_text_iter_forward_chars(&end, quote_len);
4371 if (gtk_text_iter_ends_line(&end)) {
4376 /* don't join itemized lines */
4377 if (compose_itemized_length(buffer, &end) > 0) {
4381 /* don't join signature separator */
4382 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4385 /* delete quote str */
4387 gtk_text_buffer_delete(buffer, &iter_, &end);
4389 /* don't join line breaks put by the user */
4391 gtk_text_iter_backward_char(&cur);
4392 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4393 gtk_text_iter_forward_char(&cur);
4397 gtk_text_iter_forward_char(&cur);
4398 /* delete linebreak and extra spaces */
4399 while (gtk_text_iter_backward_char(&cur)) {
4400 wc1 = gtk_text_iter_get_char(&cur);
4401 if (!g_unichar_isspace(wc1))
4406 while (!gtk_text_iter_ends_line(&cur)) {
4407 wc1 = gtk_text_iter_get_char(&cur);
4408 if (!g_unichar_isspace(wc1))
4410 gtk_text_iter_forward_char(&cur);
4413 if (!gtk_text_iter_equal(&prev, &next)) {
4416 mark = gtk_text_buffer_get_insert(buffer);
4417 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4418 if (gtk_text_iter_equal(&prev, &cur))
4420 gtk_text_buffer_delete(buffer, &prev, &next);
4424 /* insert space if required */
4425 gtk_text_iter_backward_char(&prev);
4426 wc1 = gtk_text_iter_get_char(&prev);
4427 wc2 = gtk_text_iter_get_char(&next);
4428 gtk_text_iter_forward_char(&next);
4429 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4430 pango_default_break(str, -1, NULL, attrs, 3);
4431 if (!attrs[1].is_line_break ||
4432 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4433 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4435 gtk_text_iter_backward_char(&iter_);
4436 gtk_text_buffer_place_cursor(buffer, &iter_);
4445 #define ADD_TXT_POS(bp_, ep_, pti_) \
4446 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4447 last = last->next; \
4448 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4449 last->next = NULL; \
4451 g_warning("alloc error scanning URIs"); \
4454 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4456 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4457 GtkTextBuffer *buffer;
4458 GtkTextIter iter, break_pos, end_of_line;
4459 gchar *quote_str = NULL;
4461 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4462 gboolean prev_autowrap = compose->autowrap;
4463 gint startq_offset = -1, noq_offset = -1;
4464 gint uri_start = -1, uri_stop = -1;
4465 gint nouri_start = -1, nouri_stop = -1;
4466 gint num_blocks = 0;
4467 gint quotelevel = -1;
4468 gboolean modified = force;
4469 gboolean removed = FALSE;
4470 gboolean modified_before_remove = FALSE;
4472 gboolean start = TRUE;
4473 gint itemized_len = 0, rem_item_len = 0;
4474 gchar *itemized_chars = NULL;
4475 gboolean item_continuation = FALSE;
4480 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4484 compose->autowrap = FALSE;
4486 buffer = gtk_text_view_get_buffer(text);
4487 undo_wrapping(compose->undostruct, TRUE);
4492 mark = gtk_text_buffer_get_insert(buffer);
4493 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4497 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4498 if (gtk_text_iter_ends_line(&iter)) {
4499 while (gtk_text_iter_ends_line(&iter) &&
4500 gtk_text_iter_forward_line(&iter))
4503 while (gtk_text_iter_backward_line(&iter)) {
4504 if (gtk_text_iter_ends_line(&iter)) {
4505 gtk_text_iter_forward_line(&iter);
4511 /* move to line start */
4512 gtk_text_iter_set_line_offset(&iter, 0);
4515 itemized_len = compose_itemized_length(buffer, &iter);
4517 if (!itemized_len) {
4518 itemized_len = compose_left_offset_length(buffer, &iter);
4519 item_continuation = TRUE;
4523 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4525 /* go until paragraph end (empty line) */
4526 while (start || !gtk_text_iter_ends_line(&iter)) {
4527 gchar *scanpos = NULL;
4528 /* parse table - in order of priority */
4530 const gchar *needle; /* token */
4532 /* token search function */
4533 gchar *(*search) (const gchar *haystack,
4534 const gchar *needle);
4535 /* part parsing function */
4536 gboolean (*parse) (const gchar *start,
4537 const gchar *scanpos,
4541 /* part to URI function */
4542 gchar *(*build_uri) (const gchar *bp,
4546 static struct table parser[] = {
4547 {"http://", strcasestr, get_uri_part, make_uri_string},
4548 {"https://", strcasestr, get_uri_part, make_uri_string},
4549 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4550 {"ftps://", strcasestr, get_uri_part, make_uri_string},
4551 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4552 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4553 {"www.", strcasestr, get_uri_part, make_http_string},
4554 {"webcal://",strcasestr, get_uri_part, make_uri_string},
4555 {"webcals://",strcasestr, get_uri_part, make_uri_string},
4556 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4557 {"@", strcasestr, get_email_part, make_email_string}
4559 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4560 gint last_index = PARSE_ELEMS;
4562 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4566 if (!prev_autowrap && num_blocks == 0) {
4568 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4569 G_CALLBACK(text_inserted),
4572 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4575 uri_start = uri_stop = -1;
4577 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4580 /* debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str); */
4581 if (startq_offset == -1)
4582 startq_offset = gtk_text_iter_get_offset(&iter);
4583 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4584 if (quotelevel > 2) {
4585 /* recycle colors */
4586 if (prefs_common.recycle_quote_colors)
4595 if (startq_offset == -1)
4596 noq_offset = gtk_text_iter_get_offset(&iter);
4600 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4603 if (gtk_text_iter_ends_line(&iter)) {
4605 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4606 prefs_common.linewrap_len,
4608 GtkTextIter prev, next, cur;
4609 if (prev_autowrap != FALSE || force) {
4610 compose->automatic_break = TRUE;
4612 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4613 compose->automatic_break = FALSE;
4614 if (itemized_len && compose->autoindent) {
4615 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4616 if (!item_continuation)
4617 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4619 } else if (quote_str && wrap_quote) {
4620 compose->automatic_break = TRUE;
4622 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4623 compose->automatic_break = FALSE;
4624 if (itemized_len && compose->autoindent) {
4625 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4626 if (!item_continuation)
4627 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4631 /* remove trailing spaces */
4633 rem_item_len = itemized_len;
4634 while (compose->autoindent && rem_item_len-- > 0)
4635 gtk_text_iter_backward_char(&cur);
4636 gtk_text_iter_backward_char(&cur);
4639 while (!gtk_text_iter_starts_line(&cur)) {
4642 gtk_text_iter_backward_char(&cur);
4643 wc = gtk_text_iter_get_char(&cur);
4644 if (!g_unichar_isspace(wc))
4648 if (!gtk_text_iter_equal(&prev, &next)) {
4649 gtk_text_buffer_delete(buffer, &prev, &next);
4651 gtk_text_iter_forward_char(&break_pos);
4655 gtk_text_buffer_insert(buffer, &break_pos,
4659 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4661 /* move iter to current line start */
4662 gtk_text_iter_set_line_offset(&iter, 0);
4669 /* move iter to next line start */
4675 if (!prev_autowrap && num_blocks > 0) {
4677 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4678 G_CALLBACK(text_inserted),
4682 while (!gtk_text_iter_ends_line(&end_of_line)) {
4683 gtk_text_iter_forward_char(&end_of_line);
4685 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4687 nouri_start = gtk_text_iter_get_offset(&iter);
4688 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4690 walk_pos = gtk_text_iter_get_offset(&iter);
4691 /* FIXME: this looks phony. scanning for anything in the parse table */
4692 for (n = 0; n < PARSE_ELEMS; n++) {
4695 tmp = parser[n].search(walk, parser[n].needle);
4697 if (scanpos == NULL || tmp < scanpos) {
4706 /* check if URI can be parsed */
4707 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4708 (const gchar **)&ep, FALSE)
4709 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4713 strlen(parser[last_index].needle);
4716 uri_start = walk_pos + (bp - o_walk);
4717 uri_stop = walk_pos + (ep - o_walk);
4721 gtk_text_iter_forward_line(&iter);
4724 if (startq_offset != -1) {
4725 GtkTextIter startquote, endquote;
4726 gtk_text_buffer_get_iter_at_offset(
4727 buffer, &startquote, startq_offset);
4730 switch (quotelevel) {
4732 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4733 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4734 gtk_text_buffer_apply_tag_by_name(
4735 buffer, "quote0", &startquote, &endquote);
4736 gtk_text_buffer_remove_tag_by_name(
4737 buffer, "quote1", &startquote, &endquote);
4738 gtk_text_buffer_remove_tag_by_name(
4739 buffer, "quote2", &startquote, &endquote);
4744 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4745 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4746 gtk_text_buffer_apply_tag_by_name(
4747 buffer, "quote1", &startquote, &endquote);
4748 gtk_text_buffer_remove_tag_by_name(
4749 buffer, "quote0", &startquote, &endquote);
4750 gtk_text_buffer_remove_tag_by_name(
4751 buffer, "quote2", &startquote, &endquote);
4756 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4757 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4758 gtk_text_buffer_apply_tag_by_name(
4759 buffer, "quote2", &startquote, &endquote);
4760 gtk_text_buffer_remove_tag_by_name(
4761 buffer, "quote0", &startquote, &endquote);
4762 gtk_text_buffer_remove_tag_by_name(
4763 buffer, "quote1", &startquote, &endquote);
4769 } else if (noq_offset != -1) {
4770 GtkTextIter startnoquote, endnoquote;
4771 gtk_text_buffer_get_iter_at_offset(
4772 buffer, &startnoquote, noq_offset);
4775 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4776 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4777 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4778 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4779 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4780 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4781 gtk_text_buffer_remove_tag_by_name(
4782 buffer, "quote0", &startnoquote, &endnoquote);
4783 gtk_text_buffer_remove_tag_by_name(
4784 buffer, "quote1", &startnoquote, &endnoquote);
4785 gtk_text_buffer_remove_tag_by_name(
4786 buffer, "quote2", &startnoquote, &endnoquote);
4792 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4793 GtkTextIter nouri_start_iter, nouri_end_iter;
4794 gtk_text_buffer_get_iter_at_offset(
4795 buffer, &nouri_start_iter, nouri_start);
4796 gtk_text_buffer_get_iter_at_offset(
4797 buffer, &nouri_end_iter, nouri_stop);
4798 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4799 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4800 gtk_text_buffer_remove_tag_by_name(
4801 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4802 modified_before_remove = modified;
4807 if (uri_start >= 0 && uri_stop > 0) {
4808 GtkTextIter uri_start_iter, uri_end_iter, back;
4809 gtk_text_buffer_get_iter_at_offset(
4810 buffer, &uri_start_iter, uri_start);
4811 gtk_text_buffer_get_iter_at_offset(
4812 buffer, &uri_end_iter, uri_stop);
4813 back = uri_end_iter;
4814 gtk_text_iter_backward_char(&back);
4815 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4816 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4817 gtk_text_buffer_apply_tag_by_name(
4818 buffer, "link", &uri_start_iter, &uri_end_iter);
4820 if (removed && !modified_before_remove) {
4826 /* debug_print("not modified, out after %d lines\n", lines); */
4830 /* debug_print("modified, out after %d lines\n", lines); */
4832 g_free(itemized_chars);
4835 undo_wrapping(compose->undostruct, FALSE);
4836 compose->autowrap = prev_autowrap;
4841 void compose_action_cb(void *data)
4843 Compose *compose = (Compose *)data;
4844 compose_wrap_all(compose);
4847 static void compose_wrap_all(Compose *compose)
4849 compose_wrap_all_full(compose, FALSE);
4852 static void compose_wrap_all_full(Compose *compose, gboolean force)
4854 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4855 GtkTextBuffer *buffer;
4857 gboolean modified = TRUE;
4859 buffer = gtk_text_view_get_buffer(text);
4861 gtk_text_buffer_get_start_iter(buffer, &iter);
4863 undo_wrapping(compose->undostruct, TRUE);
4865 while (!gtk_text_iter_is_end(&iter) && modified)
4866 modified = compose_beautify_paragraph(compose, &iter, force);
4868 undo_wrapping(compose->undostruct, FALSE);
4872 static void compose_set_title(Compose *compose)
4878 edited = compose->modified ? _(" [Edited]") : "";
4880 subject = gtk_editable_get_chars(
4881 GTK_EDITABLE(compose->subject_entry), 0, -1);
4883 #ifndef GENERIC_UMPC
4884 if (subject && strlen(subject))
4885 str = g_strdup_printf(_("%s - Compose message%s"),
4888 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4890 str = g_strdup(_("Compose message"));
4893 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4899 * compose_current_mail_account:
4901 * Find a current mail account (the currently selected account, or the
4902 * default account, if a news account is currently selected). If a
4903 * mail account cannot be found, display an error message.
4905 * Return value: Mail account, or NULL if not found.
4907 static PrefsAccount *
4908 compose_current_mail_account(void)
4912 if (cur_account && cur_account->protocol != A_NNTP)
4915 ac = account_get_default();
4916 if (!ac || ac->protocol == A_NNTP) {
4917 alertpanel_error(_("Account for sending mail is not specified.\n"
4918 "Please select a mail account before sending."));
4925 #define QUOTE_IF_REQUIRED(out, str) \
4927 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4931 len = strlen(str) + 3; \
4932 if ((__tmp = alloca(len)) == NULL) { \
4933 g_warning("can't allocate memory"); \
4934 g_string_free(header, TRUE); \
4937 g_snprintf(__tmp, len, "\"%s\"", str); \
4942 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4943 g_warning("can't allocate memory"); \
4944 g_string_free(header, TRUE); \
4947 strcpy(__tmp, str); \
4953 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4955 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4959 len = strlen(str) + 3; \
4960 if ((__tmp = alloca(len)) == NULL) { \
4961 g_warning("can't allocate memory"); \
4964 g_snprintf(__tmp, len, "\"%s\"", str); \
4969 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4970 g_warning("can't allocate memory"); \
4973 strcpy(__tmp, str); \
4979 static void compose_select_account(Compose *compose, PrefsAccount *account,
4982 gchar *from = NULL, *header = NULL;
4983 ComposeHeaderEntry *header_entry;
4986 cm_return_if_fail(account != NULL);
4988 compose->account = account;
4989 if (account->name && *account->name) {
4991 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4992 qbuf = escape_internal_quotes(buf, '"');
4993 from = g_strdup_printf("%s <%s>",
4994 qbuf, account->address);
4997 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4999 from = g_strdup_printf("<%s>",
5001 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
5006 compose_set_title(compose);
5008 compose_activate_privacy_system(compose, account, FALSE);
5010 if (account->default_sign && privacy_system_can_sign(compose->privacy_system) &&
5011 compose->mode != COMPOSE_REDIRECT)
5012 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
5014 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
5015 if (account->default_encrypt && privacy_system_can_encrypt(compose->privacy_system) &&
5016 compose->mode != COMPOSE_REDIRECT)
5017 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
5019 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
5021 if (!init && compose->mode != COMPOSE_REDIRECT) {
5022 undo_block(compose->undostruct);
5023 compose_insert_sig(compose, TRUE);
5024 undo_unblock(compose->undostruct);
5027 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
5028 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
5029 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
5030 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
5032 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
5033 if (account->protocol == A_NNTP) {
5034 if (!strcmp(header, _("To:")))
5035 combobox_select_by_text(
5036 GTK_COMBO_BOX(header_entry->combo),
5039 if (!strcmp(header, _("Newsgroups:")))
5040 combobox_select_by_text(
5041 GTK_COMBO_BOX(header_entry->combo),
5049 /* use account's dict info if set */
5050 if (compose->gtkaspell) {
5051 if (account->enable_default_dictionary)
5052 gtkaspell_change_dict(compose->gtkaspell,
5053 account->default_dictionary, FALSE);
5054 if (account->enable_default_alt_dictionary)
5055 gtkaspell_change_alt_dict(compose->gtkaspell,
5056 account->default_alt_dictionary);
5057 if (account->enable_default_dictionary
5058 || account->enable_default_alt_dictionary)
5059 compose_spell_menu_changed(compose);
5064 gboolean compose_check_for_valid_recipient(Compose *compose) {
5065 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
5066 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
5067 gboolean recipient_found = FALSE;
5071 /* free to and newsgroup list */
5072 slist_free_strings_full(compose->to_list);
5073 compose->to_list = NULL;
5075 slist_free_strings_full(compose->newsgroup_list);
5076 compose->newsgroup_list = NULL;
5078 /* search header entries for to and newsgroup entries */
5079 for (list = compose->header_list; list; list = list->next) {
5082 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5083 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5086 if (entry[0] != '\0') {
5087 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
5088 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5089 compose->to_list = address_list_append(compose->to_list, entry);
5090 recipient_found = TRUE;
5093 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5094 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5095 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5096 recipient_found = TRUE;
5103 return recipient_found;
5106 static gboolean compose_check_for_set_recipients(Compose *compose)
5108 if (compose->account->set_autocc && compose->account->auto_cc) {
5109 gboolean found_other = FALSE;
5111 /* search header entries for to and newsgroup entries */
5112 for (list = compose->header_list; list; list = list->next) {
5115 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5116 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5119 if (strcmp(entry, compose->account->auto_cc)
5120 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5131 if (compose->batch) {
5132 gtk_widget_show_all(compose->window);
5134 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5135 prefs_common_translated_header_name("Cc"));
5136 aval = alertpanel(_("Send"),
5138 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5140 if (aval != G_ALERTALTERNATE)
5144 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5145 gboolean found_other = FALSE;
5147 /* search header entries for to and newsgroup entries */
5148 for (list = compose->header_list; list; list = list->next) {
5151 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5152 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5155 if (strcmp(entry, compose->account->auto_bcc)
5156 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5168 if (compose->batch) {
5169 gtk_widget_show_all(compose->window);
5171 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5172 prefs_common_translated_header_name("Bcc"));
5173 aval = alertpanel(_("Send"),
5175 GTK_STOCK_CANCEL, _("_Send"), NULL, ALERTFOCUS_SECOND);
5177 if (aval != G_ALERTALTERNATE)
5184 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5188 if (compose_check_for_valid_recipient(compose) == FALSE) {
5189 if (compose->batch) {
5190 gtk_widget_show_all(compose->window);
5192 alertpanel_error(_("Recipient is not specified."));
5196 if (compose_check_for_set_recipients(compose) == FALSE) {
5200 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5201 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5202 if (*str == '\0' && check_everything == TRUE &&
5203 compose->mode != COMPOSE_REDIRECT) {
5207 message = g_strdup_printf(_("Subject is empty. %s"),
5208 compose->sending?_("Send it anyway?"):
5209 _("Queue it anyway?"));
5211 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5212 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5213 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5215 if (aval & G_ALERTDISABLE) {
5216 aval &= ~G_ALERTDISABLE;
5217 prefs_common.warn_empty_subj = FALSE;
5219 if (aval != G_ALERTALTERNATE)
5224 if (!compose->batch && prefs_common.warn_sending_many_recipients_num > 0
5225 && check_everything == TRUE) {
5229 /* count To and Cc recipients */
5230 for (list = compose->header_list; list; list = list->next) {
5234 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5235 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5238 if ((entry[0] != '\0') &&
5239 (!strcmp(header, prefs_common_translated_header_name("To:")) ||
5240 !strcmp(header, prefs_common_translated_header_name("Cc:")))) {
5246 if (cnt > prefs_common.warn_sending_many_recipients_num) {
5250 message = g_strdup_printf(_("Sending to %d recipients. %s"), cnt,
5251 compose->sending?_("Send it anyway?"):
5252 _("Queue it anyway?"));
5254 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5255 GTK_STOCK_CANCEL, compose->sending?_("_Send"):_("_Queue"), NULL,
5256 ALERTFOCUS_FIRST, TRUE, NULL, ALERT_QUESTION);
5258 if (aval & G_ALERTDISABLE) {
5259 aval &= ~G_ALERTDISABLE;
5260 prefs_common.warn_sending_many_recipients_num = 0;
5262 if (aval != G_ALERTALTERNATE)
5267 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5273 static void _display_queue_error(ComposeQueueResult val)
5276 case COMPOSE_QUEUE_SUCCESS:
5278 case COMPOSE_QUEUE_ERROR_NO_MSG:
5279 alertpanel_error(_("Could not queue message."));
5281 case COMPOSE_QUEUE_ERROR_WITH_ERRNO:
5282 alertpanel_error(_("Could not queue message:\n\n%s."),
5285 case COMPOSE_QUEUE_ERROR_SIGNING_FAILED:
5286 alertpanel_error(_("Could not queue message for sending:\n\n"
5287 "Signature failed: %s"),
5288 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5290 case COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED:
5291 alertpanel_error(_("Could not queue message for sending:\n\n"
5292 "Encryption failed: %s"),
5293 privacy_peek_error() ? privacy_get_error() : _("Unknown error"));
5295 case COMPOSE_QUEUE_ERROR_CHAR_CONVERSION:
5296 alertpanel_error(_("Could not queue message for sending:\n\n"
5297 "Charset conversion failed."));
5299 case COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY:
5300 alertpanel_error(_("Could not queue message for sending:\n\n"
5301 "Couldn't get recipient encryption key."));
5303 case COMPOSE_QUEUE_SIGNING_CANCELLED:
5304 debug_print("signing cancelled\n");
5307 /* unhandled error */
5308 debug_print("oops, unhandled compose_queue() return value %d\n",
5314 gint compose_send(Compose *compose)
5317 FolderItem *folder = NULL;
5318 ComposeQueueResult val = COMPOSE_QUEUE_ERROR_NO_MSG;
5319 gchar *msgpath = NULL;
5320 gboolean discard_window = FALSE;
5321 gchar *errstr = NULL;
5322 gchar *tmsgid = NULL;
5323 MainWindow *mainwin = mainwindow_get_mainwindow();
5324 gboolean queued_removed = FALSE;
5326 if (prefs_common.send_dialog_invisible
5327 || compose->batch == TRUE)
5328 discard_window = TRUE;
5330 compose_allow_user_actions (compose, FALSE);
5331 compose->sending = TRUE;
5333 if (compose_check_entries(compose, TRUE) == FALSE) {
5334 if (compose->batch) {
5335 gtk_widget_show_all(compose->window);
5341 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5343 if (val != COMPOSE_QUEUE_SUCCESS) {
5344 if (compose->batch) {
5345 gtk_widget_show_all(compose->window);
5348 _display_queue_error(val);
5353 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5354 if (discard_window) {
5355 compose->sending = FALSE;
5356 compose_close(compose);
5357 /* No more compose access in the normal codepath
5358 * after this point! */
5363 alertpanel_error(_("The message was queued but could not be "
5364 "sent.\nUse \"Send queued messages\" from "
5365 "the main window to retry."));
5366 if (!discard_window) {
5373 if (msgpath == NULL) {
5374 msgpath = folder_item_fetch_msg(folder, msgnum);
5375 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5378 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5379 claws_unlink(msgpath);
5382 if (!discard_window) {
5384 if (!queued_removed)
5385 folder_item_remove_msg(folder, msgnum);
5386 folder_item_scan(folder);
5388 /* make sure we delete that */
5389 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5391 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5392 folder_item_remove_msg(folder, tmp->msgnum);
5393 procmsg_msginfo_free(&tmp);
5400 if (!queued_removed)
5401 folder_item_remove_msg(folder, msgnum);
5402 folder_item_scan(folder);
5404 /* make sure we delete that */
5405 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5407 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5408 folder_item_remove_msg(folder, tmp->msgnum);
5409 procmsg_msginfo_free(&tmp);
5412 if (!discard_window) {
5413 compose->sending = FALSE;
5414 compose_allow_user_actions (compose, TRUE);
5415 compose_close(compose);
5419 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5420 "the main window to retry."), errstr);
5423 alertpanel_error_log(_("The message was queued but could not be "
5424 "sent.\nUse \"Send queued messages\" from "
5425 "the main window to retry."));
5427 if (!discard_window) {
5436 toolbar_main_set_sensitive(mainwin);
5437 main_window_set_menu_sensitive(mainwin);
5443 compose_allow_user_actions (compose, TRUE);
5444 compose->sending = FALSE;
5445 compose->modified = TRUE;
5446 toolbar_main_set_sensitive(mainwin);
5447 main_window_set_menu_sensitive(mainwin);
5452 static gboolean compose_use_attach(Compose *compose)
5454 GtkTreeModel *model = gtk_tree_view_get_model
5455 (GTK_TREE_VIEW(compose->attach_clist));
5456 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5459 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5462 gchar buf[BUFFSIZE];
5464 gboolean first_to_address;
5465 gboolean first_cc_address;
5467 ComposeHeaderEntry *headerentry;
5468 const gchar *headerentryname;
5469 const gchar *cc_hdr;
5470 const gchar *to_hdr;
5471 gboolean err = FALSE;
5473 debug_print("Writing redirect header\n");
5475 cc_hdr = prefs_common_translated_header_name("Cc:");
5476 to_hdr = prefs_common_translated_header_name("To:");
5478 first_to_address = TRUE;
5479 for (list = compose->header_list; list; list = list->next) {
5480 headerentry = ((ComposeHeaderEntry *)list->data);
5481 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5483 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5484 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5485 Xstrdup_a(str, entstr, return -1);
5487 if (str[0] != '\0') {
5488 compose_convert_header
5489 (compose, buf, sizeof(buf), str,
5490 strlen("Resent-To") + 2, TRUE);
5492 if (first_to_address) {
5493 err |= (fprintf(fp, "Resent-To: ") < 0);
5494 first_to_address = FALSE;
5496 err |= (fprintf(fp, ",") < 0);
5498 err |= (fprintf(fp, "%s", buf) < 0);
5502 if (!first_to_address) {
5503 err |= (fprintf(fp, "\n") < 0);
5506 first_cc_address = TRUE;
5507 for (list = compose->header_list; list; list = list->next) {
5508 headerentry = ((ComposeHeaderEntry *)list->data);
5509 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5511 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5512 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5513 Xstrdup_a(str, strg, return -1);
5515 if (str[0] != '\0') {
5516 compose_convert_header
5517 (compose, buf, sizeof(buf), str,
5518 strlen("Resent-Cc") + 2, TRUE);
5520 if (first_cc_address) {
5521 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5522 first_cc_address = FALSE;
5524 err |= (fprintf(fp, ",") < 0);
5526 err |= (fprintf(fp, "%s", buf) < 0);
5530 if (!first_cc_address) {
5531 err |= (fprintf(fp, "\n") < 0);
5534 return (err ? -1:0);
5537 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5539 gchar date[RFC822_DATE_BUFFSIZE];
5540 gchar buf[BUFFSIZE];
5542 const gchar *entstr;
5543 /* struct utsname utsbuf; */
5544 gboolean err = FALSE;
5546 cm_return_val_if_fail(fp != NULL, -1);
5547 cm_return_val_if_fail(compose->account != NULL, -1);
5548 cm_return_val_if_fail(compose->account->address != NULL, -1);
5551 if (prefs_common.hide_timezone)
5552 get_rfc822_date_hide_tz(date, sizeof(date));
5554 get_rfc822_date(date, sizeof(date));
5555 err |= (fprintf(fp, "Resent-Date: %s\n", date) < 0);
5558 if (compose->account->name && *compose->account->name) {
5559 compose_convert_header
5560 (compose, buf, sizeof(buf), compose->account->name,
5561 strlen("From: "), TRUE);
5562 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5563 buf, compose->account->address) < 0);
5565 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5568 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5569 if (*entstr != '\0') {
5570 Xstrdup_a(str, entstr, return -1);
5573 compose_convert_header(compose, buf, sizeof(buf), str,
5574 strlen("Subject: "), FALSE);
5575 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5579 /* Resent-Message-ID */
5580 if (compose->account->gen_msgid) {
5581 gchar *addr = prefs_account_generate_msgid(compose->account);
5582 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5584 g_free(compose->msgid);
5585 compose->msgid = addr;
5587 compose->msgid = NULL;
5590 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5593 /* separator between header and body */
5594 err |= (claws_fputs("\n", fp) == EOF);
5596 return (err ? -1:0);
5599 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5604 gchar rewrite_buf[BUFFSIZE];
5606 gboolean skip = FALSE;
5607 gboolean err = FALSE;
5608 gchar *not_included[]={
5609 "Return-Path:", "Delivered-To:", "Received:",
5610 "Subject:", "X-UIDL:", "AF:",
5611 "NF:", "PS:", "SRH:",
5612 "SFN:", "DSR:", "MID:",
5613 "CFG:", "PT:", "S:",
5614 "RQ:", "SSV:", "NSV:",
5615 "SSH:", "R:", "MAID:",
5616 "NAID:", "RMID:", "FMID:",
5617 "SCF:", "RRCPT:", "NG:",
5618 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5619 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5620 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5621 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5622 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5627 if ((fp = claws_fopen(compose->redirect_filename, "rb")) == NULL) {
5628 FILE_OP_ERROR(compose->redirect_filename, "claws_fopen");
5632 while ((ret = procheader_get_one_field_asis(&buf, fp)) != -1) {
5634 for (i = 0; not_included[i] != NULL; i++) {
5635 if (g_ascii_strncasecmp(buf, not_included[i],
5636 strlen(not_included[i])) == 0) {
5646 if (claws_fputs(buf, fdest) == -1) {
5652 if (!prefs_common.redirect_keep_from) {
5653 if (g_ascii_strncasecmp(buf, "From:",
5654 strlen("From:")) == 0) {
5655 err |= (claws_fputs(" (by way of ", fdest) == EOF);
5656 if (compose->account->name
5657 && *compose->account->name) {
5658 gchar buffer[BUFFSIZE];
5660 compose_convert_header
5661 (compose, buffer, sizeof(buffer),
5662 compose->account->name,
5665 err |= (fprintf(fdest, "%s <%s>",
5667 compose->account->address) < 0);
5669 err |= (fprintf(fdest, "%s",
5670 compose->account->address) < 0);
5671 err |= (claws_fputs(")", fdest) == EOF);
5677 if (claws_fputs("\n", fdest) == -1)
5684 if (compose_redirect_write_headers(compose, fdest))
5687 while ((len = claws_fread(rewrite_buf, sizeof(gchar), sizeof(rewrite_buf), fp)) > 0) {
5688 if (claws_fwrite(rewrite_buf, sizeof(gchar), len, fdest) != len)
5702 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5704 GtkTextBuffer *buffer;
5705 GtkTextIter start, end, tmp;
5706 gchar *chars, *tmp_enc_file, *content;
5708 const gchar *out_codeset;
5709 EncodingType encoding = ENC_UNKNOWN;
5710 MimeInfo *mimemsg, *mimetext;
5712 const gchar *src_codeset = CS_INTERNAL;
5713 gchar *from_addr = NULL;
5714 gchar *from_name = NULL;
5717 if (action == COMPOSE_WRITE_FOR_SEND) {
5718 attach_parts = TRUE;
5720 /* We're sending the message, generate a Message-ID
5722 if (compose->msgid == NULL &&
5723 compose->account->gen_msgid) {
5724 compose->msgid = prefs_account_generate_msgid(compose->account);
5728 /* create message MimeInfo */
5729 mimemsg = procmime_mimeinfo_new();
5730 mimemsg->type = MIMETYPE_MESSAGE;
5731 mimemsg->subtype = g_strdup("rfc822");
5732 mimemsg->content = MIMECONTENT_MEM;
5733 mimemsg->tmp = TRUE; /* must free content later */
5734 mimemsg->data.mem = compose_get_header(compose);
5736 /* Create text part MimeInfo */
5737 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5738 gtk_text_buffer_get_end_iter(buffer, &end);
5741 /* We make sure that there is a newline at the end. */
5742 if (action == COMPOSE_WRITE_FOR_SEND && gtk_text_iter_backward_char(&tmp)) {
5743 chars = gtk_text_buffer_get_text(buffer, &tmp, &end, FALSE);
5744 if (*chars != '\n') {
5745 gtk_text_buffer_insert(buffer, &end, "\n", 1);
5750 /* get all composed text */
5751 gtk_text_buffer_get_start_iter(buffer, &start);
5752 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5754 out_codeset = conv_get_charset_str(compose->out_encoding);
5756 if (!out_codeset && is_ascii_str(chars)) {
5757 out_codeset = CS_US_ASCII;
5758 } else if (prefs_common.outgoing_fallback_to_ascii &&
5759 is_ascii_str(chars)) {
5760 out_codeset = CS_US_ASCII;
5761 encoding = ENC_7BIT;
5765 gchar *test_conv_global_out = NULL;
5766 gchar *test_conv_reply = NULL;
5768 /* automatic mode. be automatic. */
5769 codeconv_set_strict(TRUE);
5771 out_codeset = conv_get_outgoing_charset_str();
5773 debug_print("trying to convert to %s\n", out_codeset);
5774 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5777 if (!test_conv_global_out && compose->orig_charset
5778 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5779 out_codeset = compose->orig_charset;
5780 debug_print("failure; trying to convert to %s\n", out_codeset);
5781 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5784 if (!test_conv_global_out && !test_conv_reply) {
5786 out_codeset = CS_INTERNAL;
5787 debug_print("failure; finally using %s\n", out_codeset);
5789 g_free(test_conv_global_out);
5790 g_free(test_conv_reply);
5791 codeconv_set_strict(FALSE);
5794 if (encoding == ENC_UNKNOWN) {
5795 if (prefs_common.encoding_method == CTE_BASE64)
5796 encoding = ENC_BASE64;
5797 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5798 encoding = ENC_QUOTED_PRINTABLE;
5799 else if (prefs_common.encoding_method == CTE_8BIT)
5800 encoding = ENC_8BIT;
5802 encoding = procmime_get_encoding_for_charset(out_codeset);
5805 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5806 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5808 if (action == COMPOSE_WRITE_FOR_SEND) {
5809 codeconv_set_strict(TRUE);
5810 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5811 codeconv_set_strict(FALSE);
5816 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5817 "to the specified %s charset.\n"
5818 "Send it as %s?"), out_codeset, src_codeset);
5819 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5820 _("_Send"), NULL, ALERTFOCUS_SECOND, FALSE,
5824 if (aval != G_ALERTALTERNATE) {
5826 return COMPOSE_QUEUE_ERROR_CHAR_CONVERSION;
5829 out_codeset = src_codeset;
5835 out_codeset = src_codeset;
5840 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5841 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5842 strstr(buf, "\nFrom ") != NULL) {
5843 encoding = ENC_QUOTED_PRINTABLE;
5847 mimetext = procmime_mimeinfo_new();
5848 mimetext->content = MIMECONTENT_MEM;
5849 mimetext->tmp = TRUE; /* must free content later */
5850 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5851 * and free the data, which we need later. */
5852 mimetext->data.mem = g_strdup(buf);
5853 mimetext->type = MIMETYPE_TEXT;
5854 mimetext->subtype = g_strdup("plain");
5855 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5856 g_strdup(out_codeset));
5858 /* protect trailing spaces when signing message */
5859 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5860 privacy_system_can_sign(compose->privacy_system)) {
5861 encoding = ENC_QUOTED_PRINTABLE;
5864 debug_print("main text: %" G_GSIZE_FORMAT " bytes encoded as %s in %d\n",
5865 strlen(buf), out_codeset, encoding);
5867 /* check for line length limit */
5868 if (action == COMPOSE_WRITE_FOR_SEND &&
5869 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5870 check_line_length(buf, 1000, &line) < 0) {
5873 msg = g_strdup_printf
5874 (_("Line %d exceeds the line length limit (998 bytes).\n"
5875 "The contents of the message might be broken on the way to the delivery.\n"
5877 "Send it anyway?"), line + 1);
5878 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL,
5881 if (aval != G_ALERTALTERNATE) {
5883 return COMPOSE_QUEUE_ERROR_NO_MSG;
5887 if (encoding != ENC_UNKNOWN)
5888 procmime_encode_content(mimetext, encoding);
5890 /* append attachment parts */
5891 if (compose_use_attach(compose) && attach_parts) {
5892 MimeInfo *mimempart;
5893 gchar *boundary = NULL;
5894 mimempart = procmime_mimeinfo_new();
5895 mimempart->content = MIMECONTENT_EMPTY;
5896 mimempart->type = MIMETYPE_MULTIPART;
5897 mimempart->subtype = g_strdup("mixed");
5901 boundary = generate_mime_boundary(NULL);
5902 } while (strstr(buf, boundary) != NULL);
5904 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5907 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5909 g_node_append(mimempart->node, mimetext->node);
5910 g_node_append(mimemsg->node, mimempart->node);
5912 if (compose_add_attachments(compose, mimempart) < 0)
5913 return COMPOSE_QUEUE_ERROR_NO_MSG;
5915 g_node_append(mimemsg->node, mimetext->node);
5919 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5920 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5921 /* extract name and address */
5922 if (strstr(spec, " <") && strstr(spec, ">")) {
5923 from_addr = g_strdup(strrchr(spec, '<')+1);
5924 *(strrchr(from_addr, '>')) = '\0';
5925 from_name = g_strdup(spec);
5926 *(strrchr(from_name, '<')) = '\0';
5933 /* sign message if sending */
5934 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5935 privacy_system_can_sign(compose->privacy_system))
5936 if (!privacy_sign(compose->privacy_system, mimemsg,
5937 compose->account, from_addr)) {
5940 if (!privacy_peek_error())
5941 return COMPOSE_QUEUE_SIGNING_CANCELLED;
5943 return COMPOSE_QUEUE_ERROR_SIGNING_FAILED;
5948 if (compose->use_encryption) {
5949 if (compose->encdata != NULL &&
5950 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5952 /* First, write an unencrypted copy and save it to outbox, if
5953 * user wants that. */
5954 if (compose->account->save_encrypted_as_clear_text) {
5955 debug_print("saving sent message unencrypted...\n");
5956 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5958 claws_fclose(tmpfp);
5960 /* fp now points to a file with headers written,
5961 * let's make a copy. */
5963 content = file_read_stream_to_str(fp);
5965 str_write_to_file(content, tmp_enc_file, TRUE);
5968 /* Now write the unencrypted body. */
5969 if ((tmpfp = claws_fopen(tmp_enc_file, "a")) != NULL) {
5970 procmime_write_mimeinfo(mimemsg, tmpfp);
5971 claws_fclose(tmpfp);
5973 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5975 outbox = folder_get_default_outbox();
5977 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5978 claws_unlink(tmp_enc_file);
5980 g_warning("can't open file '%s'", tmp_enc_file);
5983 g_warning("couldn't get tempfile");
5986 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5987 debug_print("Couldn't encrypt mime structure: %s.\n",
5988 privacy_get_error());
5989 return COMPOSE_QUEUE_ERROR_ENCRYPT_FAILED;
5994 procmime_write_mimeinfo(mimemsg, fp);
5996 procmime_mimeinfo_free_all(&mimemsg);
6001 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
6003 GtkTextBuffer *buffer;
6004 GtkTextIter start, end;
6009 if ((fp = claws_fopen(file, "wb")) == NULL) {
6010 FILE_OP_ERROR(file, "claws_fopen");
6014 /* chmod for security */
6015 if (change_file_mode_rw(fp, file) < 0) {
6016 FILE_OP_ERROR(file, "chmod");
6017 g_warning("can't change file mode");
6020 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
6021 gtk_text_buffer_get_start_iter(buffer, &start);
6022 gtk_text_buffer_get_end_iter(buffer, &end);
6023 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
6025 chars = conv_codeset_strdup
6026 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
6035 len = strlen(chars);
6036 if (claws_fwrite(chars, sizeof(gchar), len, fp) != len) {
6037 FILE_OP_ERROR(file, "claws_fwrite");
6046 if (claws_safe_fclose(fp) == EOF) {
6047 FILE_OP_ERROR(file, "claws_fclose");
6054 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
6057 MsgInfo *msginfo = compose->targetinfo;
6059 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
6060 if (!msginfo) return -1;
6062 if (!force && MSG_IS_LOCKED(msginfo->flags))
6065 item = msginfo->folder;
6066 cm_return_val_if_fail(item != NULL, -1);
6068 if (procmsg_msg_exist(msginfo) &&
6069 (folder_has_parent_of_type(item, F_QUEUE) ||
6070 folder_has_parent_of_type(item, F_DRAFT)
6071 || msginfo == compose->autosaved_draft)) {
6072 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
6073 g_warning("can't remove the old message");
6076 debug_print("removed reedit target %d\n", msginfo->msgnum);
6083 static void compose_remove_draft(Compose *compose)
6086 MsgInfo *msginfo = compose->targetinfo;
6087 drafts = account_get_special_folder(compose->account, F_DRAFT);
6089 if (procmsg_msg_exist(msginfo)) {
6090 folder_item_remove_msg(drafts, msginfo->msgnum);
6095 ComposeQueueResult compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
6096 gboolean remove_reedit_target)
6098 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
6101 static gboolean compose_warn_encryption(Compose *compose)
6103 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
6104 AlertValue val = G_ALERTALTERNATE;
6106 if (warning == NULL)
6109 val = alertpanel_full(_("Encryption warning"), warning,
6110 GTK_STOCK_CANCEL, _("C_ontinue"), NULL, ALERTFOCUS_SECOND,
6111 TRUE, NULL, ALERT_WARNING);
6112 if (val & G_ALERTDISABLE) {
6113 val &= ~G_ALERTDISABLE;
6114 if (val == G_ALERTALTERNATE)
6115 privacy_inhibit_encrypt_warning(compose->privacy_system,
6119 if (val == G_ALERTALTERNATE) {
6126 static ComposeQueueResult compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
6127 gchar **msgpath, gboolean perform_checks,
6128 gboolean remove_reedit_target)
6135 PrefsAccount *mailac = NULL, *newsac = NULL;
6136 gboolean err = FALSE;
6138 debug_print("queueing message...\n");
6139 cm_return_val_if_fail(compose->account != NULL, -1);
6141 if (compose_check_entries(compose, perform_checks) == FALSE) {
6142 if (compose->batch) {
6143 gtk_widget_show_all(compose->window);
6145 return COMPOSE_QUEUE_ERROR_NO_MSG;
6148 if (!compose->to_list && !compose->newsgroup_list) {
6149 g_warning("can't get recipient list");
6150 return COMPOSE_QUEUE_ERROR_NO_MSG;
6153 if (compose->to_list) {
6154 if (compose->account->protocol != A_NNTP)
6155 mailac = compose->account;
6156 else if (cur_account && cur_account->protocol != A_NNTP)
6157 mailac = cur_account;
6158 else if (!(mailac = compose_current_mail_account())) {
6159 alertpanel_error(_("No account for sending mails available!"));
6160 return COMPOSE_QUEUE_ERROR_NO_MSG;
6164 if (compose->newsgroup_list) {
6165 if (compose->account->protocol == A_NNTP)
6166 newsac = compose->account;
6168 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
6169 return COMPOSE_QUEUE_ERROR_NO_MSG;
6173 /* write queue header */
6174 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
6175 G_DIR_SEPARATOR, compose, (guint) rand());
6176 debug_print("queuing to %s\n", tmp);
6177 if ((fp = claws_fopen(tmp, "w+b")) == NULL) {
6178 FILE_OP_ERROR(tmp, "claws_fopen");
6180 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6183 if (change_file_mode_rw(fp, tmp) < 0) {
6184 FILE_OP_ERROR(tmp, "chmod");
6185 g_warning("can't change file mode");
6188 /* queueing variables */
6189 err |= (fprintf(fp, "AF:\n") < 0);
6190 err |= (fprintf(fp, "NF:0\n") < 0);
6191 err |= (fprintf(fp, "PS:10\n") < 0);
6192 err |= (fprintf(fp, "SRH:1\n") < 0);
6193 err |= (fprintf(fp, "SFN:\n") < 0);
6194 err |= (fprintf(fp, "DSR:\n") < 0);
6196 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6198 err |= (fprintf(fp, "MID:\n") < 0);
6199 err |= (fprintf(fp, "CFG:\n") < 0);
6200 err |= (fprintf(fp, "PT:0\n") < 0);
6201 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6202 err |= (fprintf(fp, "RQ:\n") < 0);
6204 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6206 err |= (fprintf(fp, "SSV:\n") < 0);
6208 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6210 err |= (fprintf(fp, "NSV:\n") < 0);
6211 err |= (fprintf(fp, "SSH:\n") < 0);
6212 /* write recipient list */
6213 if (compose->to_list) {
6214 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6215 for (cur = compose->to_list->next; cur != NULL;
6217 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6218 err |= (fprintf(fp, "\n") < 0);
6220 /* write newsgroup list */
6221 if (compose->newsgroup_list) {
6222 err |= (fprintf(fp, "NG:") < 0);
6223 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6224 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6225 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6226 err |= (fprintf(fp, "\n") < 0);
6230 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6232 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6235 if (compose->privacy_system != NULL) {
6236 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6237 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6238 if (compose->use_encryption) {
6239 if (!compose_warn_encryption(compose)) {
6243 return COMPOSE_QUEUE_ERROR_NO_MSG;
6245 if (mailac && mailac->encrypt_to_self) {
6246 GSList *tmp_list = g_slist_copy(compose->to_list);
6247 tmp_list = g_slist_append(tmp_list, compose->account->address);
6248 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6249 g_slist_free(tmp_list);
6251 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6253 if (compose->encdata != NULL) {
6254 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6255 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6256 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6257 compose->encdata) < 0);
6258 } /* else we finally dont want to encrypt */
6260 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6261 /* and if encdata was null, it means there's been a problem in
6264 g_warning("failed to write queue message");
6268 return COMPOSE_QUEUE_ERROR_NO_ENCRYPTION_KEY;
6273 /* Save copy folder */
6274 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6275 gchar *savefolderid;
6277 savefolderid = compose_get_save_to(compose);
6278 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6279 g_free(savefolderid);
6281 /* Save copy folder */
6282 if (compose->return_receipt) {
6283 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6285 /* Message-ID of message replying to */
6286 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6287 gchar *folderid = NULL;
6289 if (compose->replyinfo->folder)
6290 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6291 if (folderid == NULL)
6292 folderid = g_strdup("NULL");
6294 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6297 /* Message-ID of message forwarding to */
6298 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6299 gchar *folderid = NULL;
6301 if (compose->fwdinfo->folder)
6302 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6303 if (folderid == NULL)
6304 folderid = g_strdup("NULL");
6306 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6310 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6311 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6313 /* end of headers */
6314 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6316 if (compose->redirect_filename != NULL) {
6317 if (compose_redirect_write_to_file(compose, fp) < 0) {
6321 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6325 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6333 g_warning("failed to write queue message");
6337 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6339 if (claws_safe_fclose(fp) == EOF) {
6340 FILE_OP_ERROR(tmp, "claws_fclose");
6343 return COMPOSE_QUEUE_ERROR_WITH_ERRNO;
6346 if (item && *item) {
6349 queue = account_get_special_folder(compose->account, F_QUEUE);
6352 g_warning("can't find queue folder");
6355 return COMPOSE_QUEUE_ERROR_NO_MSG;
6357 folder_item_scan(queue);
6358 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6359 g_warning("can't queue the message");
6362 return COMPOSE_QUEUE_ERROR_NO_MSG;
6365 if (msgpath == NULL) {
6371 if (compose->mode == COMPOSE_REEDIT && compose->targetinfo) {
6372 MsgInfo *mi = folder_item_get_msginfo(queue, num);
6374 procmsg_msginfo_change_flags(mi,
6375 compose->targetinfo->flags.perm_flags,
6376 compose->targetinfo->flags.tmp_flags & ~(MSG_COPY | MSG_MOVE | MSG_MOVE_DONE),
6379 g_slist_free(mi->tags);
6380 mi->tags = g_slist_copy(compose->targetinfo->tags);
6381 procmsg_msginfo_free(&mi);
6385 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6386 compose_remove_reedit_target(compose, FALSE);
6389 if ((msgnum != NULL) && (item != NULL)) {
6394 return COMPOSE_QUEUE_SUCCESS;
6397 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6400 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6405 GError *error = NULL;
6410 gchar *type, *subtype;
6411 GtkTreeModel *model;
6414 model = gtk_tree_view_get_model(tree_view);
6416 if (!gtk_tree_model_get_iter_first(model, &iter))
6419 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
6421 if (!is_file_exist(ainfo->file)) {
6422 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6423 AlertValue val = alertpanel_full(_("Warning"), msg,
6424 _("Cancel sending"), _("Ignore attachment"), NULL,
6425 ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
6427 if (val == G_ALERTDEFAULT) {
6433 f = g_file_new_for_path(ainfo->file);
6434 fi = g_file_query_info(f, "standard::size",
6435 G_FILE_QUERY_INFO_NONE, NULL, &error);
6436 if (error != NULL) {
6437 g_warning(error->message);
6438 g_error_free(error);
6442 size = g_file_info_get_size(fi);
6446 if (g_stat(ainfo->file, &statbuf) < 0)
6448 size = statbuf.st_size;
6451 mimepart = procmime_mimeinfo_new();
6452 mimepart->content = MIMECONTENT_FILE;
6453 mimepart->data.filename = g_strdup(ainfo->file);
6454 mimepart->tmp = FALSE; /* or we destroy our attachment */
6455 mimepart->offset = 0;
6456 mimepart->length = size;
6458 type = g_strdup(ainfo->content_type);
6460 if (!strchr(type, '/')) {
6462 type = g_strdup("application/octet-stream");
6465 subtype = strchr(type, '/') + 1;
6466 *(subtype - 1) = '\0';
6467 mimepart->type = procmime_get_media_type(type);
6468 mimepart->subtype = g_strdup(subtype);
6471 if (mimepart->type == MIMETYPE_MESSAGE &&
6472 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6473 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6474 } else if (mimepart->type == MIMETYPE_TEXT) {
6475 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6476 /* Text parts with no name come from multipart/alternative
6477 * forwards. Make sure the recipient won't look at the
6478 * original HTML part by mistake. */
6479 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6480 ainfo->name = g_strdup_printf(_("Original %s part"),
6484 g_hash_table_insert(mimepart->typeparameters,
6485 g_strdup("charset"), g_strdup(ainfo->charset));
6487 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6488 if (mimepart->type == MIMETYPE_APPLICATION &&
6489 !g_strcmp0(mimepart->subtype, "octet-stream"))
6490 g_hash_table_insert(mimepart->typeparameters,
6491 g_strdup("name"), g_strdup(ainfo->name));
6492 g_hash_table_insert(mimepart->dispositionparameters,
6493 g_strdup("filename"), g_strdup(ainfo->name));
6494 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6497 if (mimepart->type == MIMETYPE_MESSAGE
6498 || mimepart->type == MIMETYPE_MULTIPART)
6499 ainfo->encoding = ENC_BINARY;
6500 else if (compose->use_signing || compose->fwdinfo != NULL) {
6501 if (ainfo->encoding == ENC_7BIT)
6502 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6503 else if (ainfo->encoding == ENC_8BIT)
6504 ainfo->encoding = ENC_BASE64;
6507 procmime_encode_content(mimepart, ainfo->encoding);
6509 g_node_append(parent->node, mimepart->node);
6510 } while (gtk_tree_model_iter_next(model, &iter));
6515 static gchar *compose_quote_list_of_addresses(gchar *str)
6517 GSList *list = NULL, *item = NULL;
6518 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6520 list = address_list_append_with_comments(list, str);
6521 for (item = list; item != NULL; item = item->next) {
6522 gchar *spec = item->data;
6523 gchar *endofname = strstr(spec, " <");
6524 if (endofname != NULL) {
6527 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6528 qqname = escape_internal_quotes(qname, '"');
6530 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6531 gchar *addr = g_strdup(endofname);
6532 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6533 faddr = g_strconcat(name, addr, NULL);
6536 debug_print("new auto-quoted address: '%s'\n", faddr);
6540 result = g_strdup((faddr != NULL)? faddr: spec);
6542 result = g_strconcat(result,
6544 (faddr != NULL)? faddr: spec,
6547 if (faddr != NULL) {
6552 slist_free_strings_full(list);
6557 #define IS_IN_CUSTOM_HEADER(header) \
6558 (compose->account->add_customhdr && \
6559 custom_header_find(compose->account->customhdr_list, header) != NULL)
6561 static const gchar *compose_untranslated_header_name(gchar *header_name)
6563 /* return the untranslated header name, if header_name is a known
6564 header name, in either its translated or untranslated form, with
6565 or without trailing colon. otherwise, returns header_name. */
6566 gchar *translated_header_name;
6567 gchar *translated_header_name_wcolon;
6568 const gchar *untranslated_header_name;
6569 const gchar *untranslated_header_name_wcolon;
6572 cm_return_val_if_fail(header_name != NULL, NULL);
6574 for (i = 0; HEADERS[i].header_name != NULL; i++) {
6575 untranslated_header_name = HEADERS[i].header_name;
6576 untranslated_header_name_wcolon = HEADERS[i].header_name_w_colon;
6578 translated_header_name = gettext(untranslated_header_name);
6579 translated_header_name_wcolon = gettext(untranslated_header_name_wcolon);
6581 if (!strcmp(header_name, untranslated_header_name) ||
6582 !strcmp(header_name, translated_header_name)) {
6583 return untranslated_header_name;
6585 if (!strcmp(header_name, untranslated_header_name_wcolon) ||
6586 !strcmp(header_name, translated_header_name_wcolon)) {
6587 return untranslated_header_name_wcolon;
6591 debug_print("compose_untranslated_header_name: unknown header '%s'\n", header_name);
6595 static void compose_add_headerfield_from_headerlist(Compose *compose,
6597 const gchar *fieldname,
6598 const gchar *seperator)
6600 gchar *str, *fieldname_w_colon;
6601 gboolean add_field = FALSE;
6603 ComposeHeaderEntry *headerentry;
6604 const gchar *headerentryname;
6605 const gchar *trans_fieldname;
6608 if (IS_IN_CUSTOM_HEADER(fieldname))
6611 debug_print("Adding %s-fields\n", fieldname);
6613 fieldstr = g_string_sized_new(64);
6615 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6616 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6618 for (list = compose->header_list; list; list = list->next) {
6619 headerentry = ((ComposeHeaderEntry *)list->data);
6620 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6622 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6623 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6625 str = compose_quote_list_of_addresses(ustr);
6627 if (str != NULL && str[0] != '\0') {
6629 g_string_append(fieldstr, seperator);
6630 g_string_append(fieldstr, str);
6639 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6640 compose_convert_header
6641 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6642 strlen(fieldname) + 2, TRUE);
6643 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6647 g_free(fieldname_w_colon);
6648 g_string_free(fieldstr, TRUE);
6653 static gchar *compose_get_manual_headers_info(Compose *compose)
6655 GString *sh_header = g_string_new(" ");
6657 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6659 for (list = compose->header_list; list; list = list->next) {
6660 ComposeHeaderEntry *headerentry;
6663 gchar *headername_wcolon;
6664 const gchar *headername_trans;
6666 gboolean standard_header = FALSE;
6668 headerentry = ((ComposeHeaderEntry *)list->data);
6670 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6672 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6677 if (!strstr(tmp, ":")) {
6678 headername_wcolon = g_strconcat(tmp, ":", NULL);
6679 headername = g_strdup(tmp);
6681 headername_wcolon = g_strdup(tmp);
6682 headername = g_strdup(strtok(tmp, ":"));
6686 string = std_headers;
6687 while (*string != NULL) {
6688 headername_trans = prefs_common_translated_header_name(*string);
6689 if (!strcmp(headername_trans, headername_wcolon))
6690 standard_header = TRUE;
6693 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6694 g_string_append_printf(sh_header, "%s ", headername);
6696 g_free(headername_wcolon);
6698 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6699 return g_string_free(sh_header, FALSE);
6702 static gchar *compose_get_header(Compose *compose)
6704 gchar date[RFC822_DATE_BUFFSIZE];
6705 gchar buf[BUFFSIZE];
6706 const gchar *entry_str;
6710 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6712 gchar *from_name = NULL, *from_address = NULL;
6715 cm_return_val_if_fail(compose->account != NULL, NULL);
6716 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6718 header = g_string_sized_new(64);
6721 if (prefs_common.hide_timezone)
6722 get_rfc822_date_hide_tz(date, sizeof(date));
6724 get_rfc822_date(date, sizeof(date));
6725 g_string_append_printf(header, "Date: %s\n", date);
6729 if (compose->account->name && *compose->account->name) {
6731 QUOTE_IF_REQUIRED(buf, compose->account->name);
6732 tmp = g_strdup_printf("%s <%s>",
6733 buf, compose->account->address);
6735 tmp = g_strdup_printf("%s",
6736 compose->account->address);
6738 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6739 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6741 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6742 from_address = g_strdup(compose->account->address);
6744 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6745 /* extract name and address */
6746 if (strstr(spec, " <") && strstr(spec, ">")) {
6747 from_address = g_strdup(strrchr(spec, '<')+1);
6748 *(strrchr(from_address, '>')) = '\0';
6749 from_name = g_strdup(spec);
6750 *(strrchr(from_name, '<')) = '\0';
6753 from_address = g_strdup(spec);
6760 if (from_name && *from_name) {
6762 compose_convert_header
6763 (compose, buf, sizeof(buf), from_name,
6764 strlen("From: "), TRUE);
6765 QUOTE_IF_REQUIRED(name, buf);
6766 qname = escape_internal_quotes(name, '"');
6768 g_string_append_printf(header, "From: %s <%s>\n",
6769 qname, from_address);
6770 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6771 compose->return_receipt) {
6772 compose_convert_header(compose, buf, sizeof(buf), from_name,
6773 strlen("Disposition-Notification-To: "),
6775 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6780 g_string_append_printf(header, "From: %s\n", from_address);
6781 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6782 compose->return_receipt)
6783 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6787 g_free(from_address);
6790 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6793 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6796 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6800 * If this account is a NNTP account remove Bcc header from
6801 * message body since it otherwise will be publicly shown
6803 if (compose->account->protocol != A_NNTP)
6804 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6807 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6809 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6812 compose_convert_header(compose, buf, sizeof(buf), str,
6813 strlen("Subject: "), FALSE);
6814 g_string_append_printf(header, "Subject: %s\n", buf);
6820 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6821 g_string_append_printf(header, "Message-ID: <%s>\n",
6825 if (compose->remove_references == FALSE) {
6827 if (compose->inreplyto && compose->to_list)
6828 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6831 if (compose->references)
6832 g_string_append_printf(header, "References: %s\n", compose->references);
6836 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6839 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6842 if (compose->account->organization &&
6843 strlen(compose->account->organization) &&
6844 !IS_IN_CUSTOM_HEADER("Organization")) {
6845 compose_convert_header(compose, buf, sizeof(buf),
6846 compose->account->organization,
6847 strlen("Organization: "), FALSE);
6848 g_string_append_printf(header, "Organization: %s\n", buf);
6851 /* Program version and system info */
6852 if (compose->account->gen_xmailer &&
6853 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6854 !compose->newsgroup_list) {
6855 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6857 gtk_major_version, gtk_minor_version, gtk_micro_version,
6860 if (compose->account->gen_xmailer &&
6861 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6862 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6864 gtk_major_version, gtk_minor_version, gtk_micro_version,
6868 /* custom headers */
6869 if (compose->account->add_customhdr) {
6872 for (cur = compose->account->customhdr_list; cur != NULL;
6874 CustomHeader *chdr = (CustomHeader *)cur->data;
6876 if (custom_header_is_allowed(chdr->name)
6877 && chdr->value != NULL
6878 && *(chdr->value) != '\0') {
6879 compose_convert_header
6880 (compose, buf, sizeof(buf),
6882 strlen(chdr->name) + 2, FALSE);
6883 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6888 /* Automatic Faces and X-Faces */
6889 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6890 g_string_append_printf(header, "X-Face: %s\n", buf);
6892 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6893 g_string_append_printf(header, "X-Face: %s\n", buf);
6895 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6896 g_string_append_printf(header, "Face: %s\n", buf);
6898 else if (get_default_face (buf, sizeof(buf)) == 0) {
6899 g_string_append_printf(header, "Face: %s\n", buf);
6903 switch (compose->priority) {
6904 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6905 "X-Priority: 1 (Highest)\n");
6907 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6908 "X-Priority: 2 (High)\n");
6910 case PRIORITY_NORMAL: break;
6911 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6912 "X-Priority: 4 (Low)\n");
6914 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6915 "X-Priority: 5 (Lowest)\n");
6917 default: debug_print("compose: priority unknown : %d\n",
6921 /* get special headers */
6922 for (list = compose->header_list; list; list = list->next) {
6923 ComposeHeaderEntry *headerentry;
6926 gchar *headername_wcolon;
6927 const gchar *headername_trans;
6930 gboolean standard_header = FALSE;
6932 headerentry = ((ComposeHeaderEntry *)list->data);
6934 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6936 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6941 if (!strstr(tmp, ":")) {
6942 headername_wcolon = g_strconcat(tmp, ":", NULL);
6943 headername = g_strdup(tmp);
6945 headername_wcolon = g_strdup(tmp);
6946 headername = g_strdup(strtok(tmp, ":"));
6950 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6951 Xstrdup_a(headervalue, entry_str, return NULL);
6952 subst_char(headervalue, '\r', ' ');
6953 subst_char(headervalue, '\n', ' ');
6954 g_strstrip(headervalue);
6955 if (*headervalue != '\0') {
6956 string = std_headers;
6957 while (*string != NULL && !standard_header) {
6958 headername_trans = prefs_common_translated_header_name(*string);
6959 /* support mixed translated and untranslated headers */
6960 if (!strcmp(headername_trans, headername_wcolon) || !strcmp(*string, headername_wcolon))
6961 standard_header = TRUE;
6964 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername)) {
6965 /* store untranslated header name */
6966 g_string_append_printf(header, "%s %s\n",
6967 compose_untranslated_header_name(headername_wcolon), headervalue);
6971 g_free(headername_wcolon);
6975 g_string_free(header, FALSE);
6980 #undef IS_IN_CUSTOM_HEADER
6982 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6983 gint header_len, gboolean addr_field)
6985 gchar *tmpstr = NULL;
6986 const gchar *out_codeset = NULL;
6988 cm_return_if_fail(src != NULL);
6989 cm_return_if_fail(dest != NULL);
6991 if (len < 1) return;
6993 tmpstr = g_strdup(src);
6995 subst_char(tmpstr, '\n', ' ');
6996 subst_char(tmpstr, '\r', ' ');
6999 if (!g_utf8_validate(tmpstr, -1, NULL)) {
7000 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
7001 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
7006 codeconv_set_strict(TRUE);
7007 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7008 conv_get_charset_str(compose->out_encoding));
7009 codeconv_set_strict(FALSE);
7011 if (!dest || *dest == '\0') {
7012 gchar *test_conv_global_out = NULL;
7013 gchar *test_conv_reply = NULL;
7015 /* automatic mode. be automatic. */
7016 codeconv_set_strict(TRUE);
7018 out_codeset = conv_get_outgoing_charset_str();
7020 debug_print("trying to convert to %s\n", out_codeset);
7021 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7024 if (!test_conv_global_out && compose->orig_charset
7025 && strcmp(compose->orig_charset, CS_US_ASCII)) {
7026 out_codeset = compose->orig_charset;
7027 debug_print("failure; trying to convert to %s\n", out_codeset);
7028 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
7031 if (!test_conv_global_out && !test_conv_reply) {
7033 out_codeset = CS_INTERNAL;
7034 debug_print("finally using %s\n", out_codeset);
7036 g_free(test_conv_global_out);
7037 g_free(test_conv_reply);
7038 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
7040 codeconv_set_strict(FALSE);
7045 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
7049 cm_return_if_fail(user_data != NULL);
7051 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
7052 g_strstrip(address);
7053 if (*address != '\0') {
7054 gchar *name = procheader_get_fromname(address);
7055 extract_address(address);
7056 #ifndef USE_ALT_ADDRBOOK
7057 addressbook_add_contact(name, address, NULL, NULL);
7059 debug_print("%s: %s\n", name, address);
7060 if (addressadd_selection(name, address, NULL, NULL)) {
7061 debug_print( "addressbook_add_contact - added\n" );
7068 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
7070 GtkWidget *menuitem;
7073 cm_return_if_fail(menu != NULL);
7074 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
7076 menuitem = gtk_separator_menu_item_new();
7077 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7078 gtk_widget_show(menuitem);
7080 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
7081 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
7083 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
7084 g_strstrip(address);
7085 if (*address == '\0') {
7086 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
7089 g_signal_connect(G_OBJECT(menuitem), "activate",
7090 G_CALLBACK(compose_add_to_addressbook_cb), entry);
7091 gtk_widget_show(menuitem);
7094 void compose_add_extra_header(gchar *header, GtkListStore *model)
7097 if (strcmp(header, "")) {
7098 COMBOBOX_ADD(model, header, COMPOSE_TO);
7102 void compose_add_extra_header_entries(GtkListStore *model)
7106 gchar buf[BUFFSIZE];
7109 if (extra_headers == NULL) {
7110 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
7111 if ((exh = claws_fopen(exhrc, "rb")) == NULL) {
7112 debug_print("extra headers file not found\n");
7113 goto extra_headers_done;
7115 while (claws_fgets(buf, BUFFSIZE, exh) != NULL) {
7116 lastc = strlen(buf) - 1; /* remove trailing control chars */
7117 while (lastc >= 0 && buf[lastc] != ':')
7118 buf[lastc--] = '\0';
7119 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
7120 buf[lastc] = '\0'; /* remove trailing : for comparison */
7121 if (custom_header_is_allowed(buf)) {
7123 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
7126 g_message("disallowed extra header line: %s\n", buf);
7130 g_message("invalid extra header line: %s\n", buf);
7136 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
7137 extra_headers = g_slist_reverse(extra_headers);
7139 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
7143 static void _ldap_srv_func(gpointer data, gpointer user_data)
7145 LdapServer *server = (LdapServer *)data;
7146 gboolean *enable = (gboolean *)user_data;
7148 debug_print("%s server '%s'\n", (*enable == TRUE ? "enabling" : "disabling"), server->control->hostName);
7149 server->searchFlag = *enable;
7153 static void compose_create_header_entry(Compose *compose)
7155 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
7162 const gchar *header = NULL;
7163 ComposeHeaderEntry *headerentry;
7164 gboolean standard_header = FALSE;
7165 GtkListStore *model;
7168 headerentry = g_new0(ComposeHeaderEntry, 1);
7170 /* Combo box model */
7171 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
7172 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
7174 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
7176 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
7178 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
7179 COMPOSE_NEWSGROUPS);
7180 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
7182 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
7183 COMPOSE_FOLLOWUPTO);
7184 compose_add_extra_header_entries(model);
7187 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
7188 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
7189 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
7190 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
7191 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
7192 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
7193 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
7194 G_CALLBACK(compose_grab_focus_cb), compose);
7195 gtk_widget_show(combo);
7197 /* Putting only the combobox child into focus chain of its parent causes
7198 * the parent to be skipped when changing focus via Tab or Shift+Tab.
7199 * This eliminates need to pres Tab twice in order to really get from the
7200 * combobox to next widget. */
7202 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
7203 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
7206 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
7207 compose->header_nextrow, compose->header_nextrow+1,
7208 GTK_SHRINK, GTK_FILL, 0, 0);
7209 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
7210 const gchar *last_header_entry = gtk_entry_get_text(
7211 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7213 while (*string != NULL) {
7214 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
7215 standard_header = TRUE;
7218 if (standard_header)
7219 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
7221 if (!compose->header_last || !standard_header) {
7222 switch(compose->account->protocol) {
7224 header = prefs_common_translated_header_name("Newsgroups:");
7227 header = prefs_common_translated_header_name("To:");
7232 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
7234 gtk_editable_set_editable(
7235 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
7236 prefs_common.type_any_header);
7238 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
7239 G_CALLBACK(compose_grab_focus_cb), compose);
7241 /* Entry field with cleanup button */
7242 button = gtk_button_new();
7243 gtk_button_set_image(GTK_BUTTON(button),
7244 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
7245 gtk_widget_show(button);
7246 CLAWS_SET_TIP(button,
7247 _("Delete entry contents"));
7248 entry = gtk_entry_new();
7249 gtk_widget_show(entry);
7250 CLAWS_SET_TIP(entry,
7251 _("Use <tab> to autocomplete from addressbook"));
7252 hbox = gtk_hbox_new (FALSE, 0);
7253 gtk_widget_show(hbox);
7254 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
7255 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
7256 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
7257 compose->header_nextrow, compose->header_nextrow+1,
7258 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
7260 g_signal_connect(G_OBJECT(entry), "key-press-event",
7261 G_CALLBACK(compose_headerentry_key_press_event_cb),
7263 g_signal_connect(G_OBJECT(entry), "changed",
7264 G_CALLBACK(compose_headerentry_changed_cb),
7266 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7267 G_CALLBACK(compose_grab_focus_cb), compose);
7269 g_signal_connect(G_OBJECT(button), "clicked",
7270 G_CALLBACK(compose_headerentry_button_clicked_cb),
7274 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7275 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7276 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7277 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7278 G_CALLBACK(compose_header_drag_received_cb),
7280 g_signal_connect(G_OBJECT(entry), "drag-drop",
7281 G_CALLBACK(compose_drag_drop),
7283 g_signal_connect(G_OBJECT(entry), "populate-popup",
7284 G_CALLBACK(compose_entry_popup_extend),
7288 #ifndef PASSWORD_CRYPTO_OLD
7289 GSList *pwd_servers = addrindex_get_password_protected_ldap_servers();
7290 if (pwd_servers != NULL && master_passphrase() == NULL) {
7291 gboolean enable = FALSE;
7292 debug_print("Master passphrase not available, disabling password-protected LDAP servers for this compose window.\n");
7293 /* Temporarily disable password-protected LDAP servers,
7294 * because user did not provide a master passphrase.
7295 * We can safely enable searchFlag on all servers in this list
7296 * later, since addrindex_get_password_protected_ldap_servers()
7297 * includes servers which have it enabled initially. */
7298 g_slist_foreach(pwd_servers, _ldap_srv_func, &enable);
7299 compose->passworded_ldap_servers = pwd_servers;
7301 #endif /* PASSWORD_CRYPTO_OLD */
7302 #endif /* USE_LDAP */
7304 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7306 headerentry->compose = compose;
7307 headerentry->combo = combo;
7308 headerentry->entry = entry;
7309 headerentry->button = button;
7310 headerentry->hbox = hbox;
7311 headerentry->headernum = compose->header_nextrow;
7312 headerentry->type = PREF_NONE;
7314 compose->header_nextrow++;
7315 compose->header_last = headerentry;
7316 compose->header_list =
7317 g_slist_append(compose->header_list,
7321 static void compose_add_header_entry(Compose *compose, const gchar *header,
7322 gchar *text, ComposePrefType pref_type)
7324 ComposeHeaderEntry *last_header = compose->header_last;
7325 gchar *tmp = g_strdup(text), *email;
7326 gboolean replyto_hdr;
7328 replyto_hdr = (!strcasecmp(header,
7329 prefs_common_translated_header_name("Reply-To:")) ||
7331 prefs_common_translated_header_name("Followup-To:")) ||
7333 prefs_common_translated_header_name("In-Reply-To:")));
7335 extract_address(tmp);
7336 email = g_utf8_strdown(tmp, -1);
7338 if (replyto_hdr == FALSE &&
7339 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7341 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7342 header, text, (gint) pref_type);
7348 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7349 gtk_entry_set_text(GTK_ENTRY(
7350 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7352 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7353 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7354 last_header->type = pref_type;
7356 if (replyto_hdr == FALSE)
7357 g_hash_table_insert(compose->email_hashtable, email,
7358 GUINT_TO_POINTER(1));
7365 static void compose_destroy_headerentry(Compose *compose,
7366 ComposeHeaderEntry *headerentry)
7368 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7371 extract_address(text);
7372 email = g_utf8_strdown(text, -1);
7373 g_hash_table_remove(compose->email_hashtable, email);
7377 gtk_widget_destroy(headerentry->combo);
7378 gtk_widget_destroy(headerentry->entry);
7379 gtk_widget_destroy(headerentry->button);
7380 gtk_widget_destroy(headerentry->hbox);
7381 g_free(headerentry);
7384 static void compose_remove_header_entries(Compose *compose)
7387 for (list = compose->header_list; list; list = list->next)
7388 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7390 compose->header_last = NULL;
7391 g_slist_free(compose->header_list);
7392 compose->header_list = NULL;
7393 compose->header_nextrow = 1;
7394 compose_create_header_entry(compose);
7397 static GtkWidget *compose_create_header(Compose *compose)
7399 GtkWidget *from_optmenu_hbox;
7400 GtkWidget *header_table_main;
7401 GtkWidget *header_scrolledwin;
7402 GtkWidget *header_table;
7404 /* parent with account selection and from header */
7405 header_table_main = gtk_table_new(2, 2, FALSE);
7406 gtk_widget_show(header_table_main);
7407 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7409 from_optmenu_hbox = compose_account_option_menu_create(compose);
7410 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7411 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7413 /* child with header labels and entries */
7414 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7415 gtk_widget_show(header_scrolledwin);
7416 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7418 header_table = gtk_table_new(2, 2, FALSE);
7419 gtk_widget_show(header_table);
7420 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7421 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7422 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7423 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7424 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7426 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7427 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7429 compose->header_table = header_table;
7430 compose->header_list = NULL;
7431 compose->header_nextrow = 0;
7433 compose_create_header_entry(compose);
7435 compose->table = NULL;
7437 return header_table_main;
7440 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7442 Compose *compose = (Compose *)data;
7443 GdkEventButton event;
7446 event.time = gtk_get_current_event_time();
7448 return attach_button_pressed(compose->attach_clist, &event, compose);
7451 static GtkWidget *compose_create_attach(Compose *compose)
7453 GtkWidget *attach_scrwin;
7454 GtkWidget *attach_clist;
7456 GtkListStore *store;
7457 GtkCellRenderer *renderer;
7458 GtkTreeViewColumn *column;
7459 GtkTreeSelection *selection;
7461 /* attachment list */
7462 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7463 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7464 GTK_POLICY_AUTOMATIC,
7465 GTK_POLICY_AUTOMATIC);
7466 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7468 store = gtk_list_store_new(N_ATTACH_COLS,
7474 G_TYPE_AUTO_POINTER,
7476 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7477 (GTK_TREE_MODEL(store)));
7478 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7479 g_object_unref(store);
7481 renderer = gtk_cell_renderer_text_new();
7482 column = gtk_tree_view_column_new_with_attributes
7483 (_("Mime type"), renderer, "text",
7484 COL_MIMETYPE, NULL);
7485 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7487 renderer = gtk_cell_renderer_text_new();
7488 column = gtk_tree_view_column_new_with_attributes
7489 (_("Size"), renderer, "text",
7491 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7493 renderer = gtk_cell_renderer_text_new();
7494 column = gtk_tree_view_column_new_with_attributes
7495 (_("Name"), renderer, "text",
7497 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7499 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7500 prefs_common.use_stripes_everywhere);
7501 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7502 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7504 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7505 G_CALLBACK(attach_selected), compose);
7506 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7507 G_CALLBACK(attach_button_pressed), compose);
7508 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7509 G_CALLBACK(popup_attach_button_pressed), compose);
7510 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7511 G_CALLBACK(attach_key_pressed), compose);
7514 gtk_drag_dest_set(attach_clist,
7515 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7516 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7517 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7518 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7519 G_CALLBACK(compose_attach_drag_received_cb),
7521 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7522 G_CALLBACK(compose_drag_drop),
7525 compose->attach_scrwin = attach_scrwin;
7526 compose->attach_clist = attach_clist;
7528 return attach_scrwin;
7531 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7533 static GtkWidget *compose_create_others(Compose *compose)
7536 GtkWidget *savemsg_checkbtn;
7537 GtkWidget *savemsg_combo;
7538 GtkWidget *savemsg_select;
7541 gchar *folderidentifier;
7543 /* Table for settings */
7544 table = gtk_table_new(3, 1, FALSE);
7545 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7546 gtk_widget_show(table);
7547 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7550 /* Save Message to folder */
7551 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7552 gtk_widget_show(savemsg_checkbtn);
7553 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7554 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7555 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7558 savemsg_combo = gtk_combo_box_text_new_with_entry();
7559 compose->savemsg_checkbtn = savemsg_checkbtn;
7560 compose->savemsg_combo = savemsg_combo;
7561 gtk_widget_show(savemsg_combo);
7563 if (prefs_common.compose_save_to_history)
7564 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7565 prefs_common.compose_save_to_history);
7566 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7567 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7568 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7569 G_CALLBACK(compose_grab_focus_cb), compose);
7570 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7571 if (compose->account->set_sent_folder || prefs_common.savemsg)
7572 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), TRUE);
7574 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), FALSE);
7575 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), TRUE);
7576 folderidentifier = folder_item_get_identifier(account_get_special_folder
7577 (compose->account, F_OUTBOX));
7578 compose_set_save_to(compose, folderidentifier);
7579 g_free(folderidentifier);
7582 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7583 gtk_widget_show(savemsg_select);
7584 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7585 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7586 G_CALLBACK(compose_savemsg_select_cb),
7592 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7597 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE,
7598 _("Select folder to save message to"));
7601 path = folder_item_get_identifier(dest);
7603 compose_set_save_to(compose, path);
7607 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7608 GdkAtom clip, GtkTextIter *insert_place);
7611 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7615 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7617 if (event->button == 3) {
7619 GtkTextIter sel_start, sel_end;
7620 gboolean stuff_selected;
7622 /* move the cursor to allow GtkAspell to check the word
7623 * under the mouse */
7624 if (event->x && event->y) {
7625 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7626 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7628 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7631 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7632 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7635 stuff_selected = gtk_text_buffer_get_selection_bounds(
7637 &sel_start, &sel_end);
7639 gtk_text_buffer_place_cursor (buffer, &iter);
7640 /* reselect stuff */
7642 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7643 gtk_text_buffer_select_range(buffer,
7644 &sel_start, &sel_end);
7646 return FALSE; /* pass the event so that the right-click goes through */
7649 if (event->button == 2) {
7654 /* get the middle-click position to paste at the correct place */
7655 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7656 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7658 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7661 entry_paste_clipboard(compose, text,
7662 prefs_common.linewrap_pastes,
7663 GDK_SELECTION_PRIMARY, &iter);
7671 static void compose_spell_menu_changed(void *data)
7673 Compose *compose = (Compose *)data;
7675 GtkWidget *menuitem;
7676 GtkWidget *parent_item;
7677 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7680 if (compose->gtkaspell == NULL)
7683 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7684 "/Menu/Spelling/Options");
7686 /* setting the submenu removes /Spelling/Options from the factory
7687 * so we need to save it */
7689 if (parent_item == NULL) {
7690 parent_item = compose->aspell_options_menu;
7691 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7693 compose->aspell_options_menu = parent_item;
7695 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7697 spell_menu = g_slist_reverse(spell_menu);
7698 for (items = spell_menu;
7699 items; items = items->next) {
7700 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7701 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7702 gtk_widget_show(GTK_WIDGET(menuitem));
7704 g_slist_free(spell_menu);
7706 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7707 gtk_widget_show(parent_item);
7710 static void compose_dict_changed(void *data)
7712 Compose *compose = (Compose *) data;
7714 if(!compose->gtkaspell)
7716 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7719 gtkaspell_highlight_all(compose->gtkaspell);
7720 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7724 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7726 Compose *compose = (Compose *)data;
7727 GdkEventButton event;
7730 event.time = gtk_get_current_event_time();
7734 return text_clicked(compose->text, &event, compose);
7737 static gboolean compose_force_window_origin = TRUE;
7738 static Compose *compose_create(PrefsAccount *account,
7747 GtkWidget *handlebox;
7749 GtkWidget *notebook;
7751 GtkWidget *attach_hbox;
7752 GtkWidget *attach_lab1;
7753 GtkWidget *attach_lab2;
7758 GtkWidget *subject_hbox;
7759 GtkWidget *subject_frame;
7760 GtkWidget *subject_entry;
7764 GtkWidget *edit_vbox;
7765 GtkWidget *ruler_hbox;
7767 GtkWidget *scrolledwin;
7769 GtkTextBuffer *buffer;
7770 GtkClipboard *clipboard;
7772 UndoMain *undostruct;
7774 GtkWidget *popupmenu;
7775 GtkWidget *tmpl_menu;
7776 GtkActionGroup *action_group = NULL;
7779 GtkAspell * gtkaspell = NULL;
7782 static GdkGeometry geometry;
7784 cm_return_val_if_fail(account != NULL, NULL);
7786 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER_BG],
7787 &default_header_bgcolor);
7788 gtkut_convert_int_to_gdk_color(prefs_common.color[COL_DEFAULT_HEADER],
7789 &default_header_color);
7791 debug_print("Creating compose window...\n");
7792 compose = g_new0(Compose, 1);
7794 compose->batch = batch;
7795 compose->account = account;
7796 compose->folder = folder;
7798 compose->mutex = cm_mutex_new();
7799 compose->set_cursor_pos = -1;
7801 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7803 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7804 gtk_widget_set_size_request(window, prefs_common.compose_width,
7805 prefs_common.compose_height);
7807 if (!geometry.max_width) {
7808 geometry.max_width = gdk_screen_width();
7809 geometry.max_height = gdk_screen_height();
7812 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7813 &geometry, GDK_HINT_MAX_SIZE);
7814 if (!geometry.min_width) {
7815 geometry.min_width = 600;
7816 geometry.min_height = 440;
7818 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7819 &geometry, GDK_HINT_MIN_SIZE);
7821 #ifndef GENERIC_UMPC
7822 if (compose_force_window_origin)
7823 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7824 prefs_common.compose_y);
7826 g_signal_connect(G_OBJECT(window), "delete_event",
7827 G_CALLBACK(compose_delete_cb), compose);
7828 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7829 gtk_widget_realize(window);
7831 gtkut_widget_set_composer_icon(window);
7833 vbox = gtk_vbox_new(FALSE, 0);
7834 gtk_container_add(GTK_CONTAINER(window), vbox);
7836 compose->ui_manager = gtk_ui_manager_new();
7837 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7838 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7839 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7840 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7841 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7842 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7843 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7844 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7845 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7846 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7848 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7850 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7851 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7853 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7855 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7865 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7866 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7867 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7879 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7880 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7881 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7883 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7884 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7885 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7886 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7888 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7890 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7891 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7892 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7893 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7894 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7895 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7896 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7897 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7898 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7899 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7900 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7901 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7902 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7903 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7904 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7906 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7908 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7909 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7910 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7911 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7912 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7924 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7925 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7932 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7933 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7942 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7943 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7944 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7945 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7946 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7947 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7948 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7950 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7951 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7952 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7953 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7954 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7956 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7958 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7959 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7960 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7961 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7962 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7964 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7965 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)
7966 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)
7967 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7969 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7971 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7972 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)
7973 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)
7975 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7977 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7978 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)
7979 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7981 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7982 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)
7983 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7985 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7987 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7988 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)
7989 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7990 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7991 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7992 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7994 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7995 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)
7996 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)
7997 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7998 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
8000 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
8001 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
8002 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
8003 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
8004 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
8005 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
8007 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
8008 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
8009 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)
8011 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
8012 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
8013 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
8017 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
8018 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
8019 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
8020 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8021 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
8022 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
8025 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
8027 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
8028 gtk_widget_show_all(menubar);
8030 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
8031 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
8033 if (prefs_common.toolbar_detachable) {
8034 handlebox = gtk_handle_box_new();
8036 handlebox = gtk_hbox_new(FALSE, 0);
8038 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
8040 gtk_widget_realize(handlebox);
8041 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
8044 vbox2 = gtk_vbox_new(FALSE, 2);
8045 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
8046 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
8049 notebook = gtk_notebook_new();
8050 gtk_widget_show(notebook);
8052 /* header labels and entries */
8053 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8054 compose_create_header(compose),
8055 gtk_label_new_with_mnemonic(_("Hea_der")));
8056 /* attachment list */
8057 attach_hbox = gtk_hbox_new(FALSE, 0);
8058 gtk_widget_show(attach_hbox);
8060 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
8061 gtk_widget_show(attach_lab1);
8062 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
8064 attach_lab2 = gtk_label_new("");
8065 gtk_widget_show(attach_lab2);
8066 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
8068 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8069 compose_create_attach(compose),
8072 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
8073 compose_create_others(compose),
8074 gtk_label_new_with_mnemonic(_("Othe_rs")));
8077 subject_hbox = gtk_hbox_new(FALSE, 0);
8078 gtk_widget_show(subject_hbox);
8080 subject_frame = gtk_frame_new(NULL);
8081 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
8082 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
8083 gtk_widget_show(subject_frame);
8085 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
8086 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
8087 gtk_widget_show(subject);
8089 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
8090 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
8091 gtk_widget_show(label);
8094 subject_entry = claws_spell_entry_new();
8096 subject_entry = gtk_entry_new();
8098 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
8099 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
8100 G_CALLBACK(compose_grab_focus_cb), compose);
8101 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
8102 gtk_widget_show(subject_entry);
8103 compose->subject_entry = subject_entry;
8104 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
8106 edit_vbox = gtk_vbox_new(FALSE, 0);
8108 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
8111 ruler_hbox = gtk_hbox_new(FALSE, 0);
8112 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
8114 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
8115 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
8116 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
8120 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
8121 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
8122 GTK_POLICY_AUTOMATIC,
8123 GTK_POLICY_AUTOMATIC);
8124 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
8126 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
8128 text = gtk_text_view_new();
8129 if (prefs_common.show_compose_margin) {
8130 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
8131 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
8133 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
8134 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
8135 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
8136 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8137 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
8139 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
8140 g_signal_connect_after(G_OBJECT(text), "size_allocate",
8141 G_CALLBACK(compose_edit_size_alloc),
8143 g_signal_connect(G_OBJECT(buffer), "changed",
8144 G_CALLBACK(compose_changed_cb), compose);
8145 g_signal_connect(G_OBJECT(text), "grab_focus",
8146 G_CALLBACK(compose_grab_focus_cb), compose);
8147 g_signal_connect(G_OBJECT(buffer), "insert_text",
8148 G_CALLBACK(text_inserted), compose);
8149 g_signal_connect(G_OBJECT(text), "button_press_event",
8150 G_CALLBACK(text_clicked), compose);
8151 g_signal_connect(G_OBJECT(text), "popup-menu",
8152 G_CALLBACK(compose_popup_menu), compose);
8153 g_signal_connect(G_OBJECT(subject_entry), "changed",
8154 G_CALLBACK(compose_changed_cb), compose);
8155 g_signal_connect(G_OBJECT(subject_entry), "activate",
8156 G_CALLBACK(compose_subject_entry_activated), compose);
8159 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
8160 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
8161 GDK_ACTION_COPY | GDK_ACTION_MOVE);
8162 g_signal_connect(G_OBJECT(text), "drag_data_received",
8163 G_CALLBACK(compose_insert_drag_received_cb),
8165 g_signal_connect(G_OBJECT(text), "drag-drop",
8166 G_CALLBACK(compose_drag_drop),
8168 g_signal_connect(G_OBJECT(text), "key-press-event",
8169 G_CALLBACK(completion_set_focus_to_subject),
8171 gtk_widget_show_all(vbox);
8173 /* pane between attach clist and text */
8174 paned = gtk_vpaned_new();
8175 gtk_container_add(GTK_CONTAINER(vbox2), paned);
8176 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
8177 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
8178 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
8179 g_signal_connect(G_OBJECT(notebook), "size_allocate",
8180 G_CALLBACK(compose_notebook_size_alloc), paned);
8182 gtk_widget_show_all(paned);
8185 if (prefs_common.textfont) {
8186 PangoFontDescription *font_desc;
8188 font_desc = pango_font_description_from_string
8189 (prefs_common.textfont);
8191 gtk_widget_modify_font(text, font_desc);
8192 pango_font_description_free(font_desc);
8196 gtk_action_group_add_actions(action_group, compose_popup_entries,
8197 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
8198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
8199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
8200 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
8201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
8202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
8203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
8205 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
8207 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8208 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8209 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
8211 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
8213 undostruct = undo_init(text);
8214 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
8217 address_completion_start(window);
8219 compose->window = window;
8220 compose->vbox = vbox;
8221 compose->menubar = menubar;
8222 compose->handlebox = handlebox;
8224 compose->vbox2 = vbox2;
8226 compose->paned = paned;
8228 compose->attach_label = attach_lab2;
8230 compose->notebook = notebook;
8231 compose->edit_vbox = edit_vbox;
8232 compose->ruler_hbox = ruler_hbox;
8233 compose->ruler = ruler;
8234 compose->scrolledwin = scrolledwin;
8235 compose->text = text;
8237 compose->focused_editable = NULL;
8239 compose->popupmenu = popupmenu;
8241 compose->tmpl_menu = tmpl_menu;
8243 compose->mode = mode;
8244 compose->rmode = mode;
8246 compose->targetinfo = NULL;
8247 compose->replyinfo = NULL;
8248 compose->fwdinfo = NULL;
8250 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
8251 g_str_equal, (GDestroyNotify) g_free, NULL);
8253 compose->replyto = NULL;
8255 compose->bcc = NULL;
8256 compose->followup_to = NULL;
8258 compose->ml_post = NULL;
8260 compose->inreplyto = NULL;
8261 compose->references = NULL;
8262 compose->msgid = NULL;
8263 compose->boundary = NULL;
8265 compose->autowrap = prefs_common.autowrap;
8266 compose->autoindent = prefs_common.auto_indent;
8267 compose->use_signing = FALSE;
8268 compose->use_encryption = FALSE;
8269 compose->privacy_system = NULL;
8270 compose->encdata = NULL;
8272 compose->modified = FALSE;
8274 compose->return_receipt = FALSE;
8276 compose->to_list = NULL;
8277 compose->newsgroup_list = NULL;
8279 compose->undostruct = undostruct;
8281 compose->sig_str = NULL;
8283 compose->exteditor_file = NULL;
8284 compose->exteditor_pid = INVALID_PID;
8285 compose->exteditor_tag = -1;
8286 compose->exteditor_socket = NULL;
8287 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8289 compose->folder_update_callback_id =
8290 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8291 compose_update_folder_hook,
8292 (gpointer) compose);
8295 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8296 if (mode != COMPOSE_REDIRECT) {
8297 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8298 strcmp(prefs_common.dictionary, "")) {
8299 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8300 prefs_common.alt_dictionary,
8301 conv_get_locale_charset_str(),
8302 prefs_common.color[COL_MISSPELLED],
8303 prefs_common.check_while_typing,
8304 prefs_common.recheck_when_changing_dict,
8305 prefs_common.use_alternate,
8306 prefs_common.use_both_dicts,
8307 GTK_TEXT_VIEW(text),
8308 GTK_WINDOW(compose->window),
8309 compose_dict_changed,
8310 compose_spell_menu_changed,
8313 alertpanel_error(_("Spell checker could not "
8315 gtkaspell_checkers_strerror());
8316 gtkaspell_checkers_reset_error();
8318 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8322 compose->gtkaspell = gtkaspell;
8323 compose_spell_menu_changed(compose);
8324 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8327 compose_select_account(compose, account, TRUE);
8329 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8330 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8332 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8333 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8335 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8336 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8338 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8339 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8341 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8342 if (account->protocol != A_NNTP)
8343 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8344 prefs_common_translated_header_name("To:"));
8346 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8347 prefs_common_translated_header_name("Newsgroups:"));
8349 #ifndef USE_ALT_ADDRBOOK
8350 addressbook_set_target_compose(compose);
8352 if (mode != COMPOSE_REDIRECT)
8353 compose_set_template_menu(compose);
8355 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8358 compose_list = g_list_append(compose_list, compose);
8360 if (!prefs_common.show_ruler)
8361 gtk_widget_hide(ruler_hbox);
8363 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8366 compose->priority = PRIORITY_NORMAL;
8367 compose_update_priority_menu_item(compose);
8369 compose_set_out_encoding(compose);
8372 compose_update_actions_menu(compose);
8374 /* Privacy Systems menu */
8375 compose_update_privacy_systems_menu(compose);
8376 compose_activate_privacy_system(compose, account, TRUE);
8378 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8380 gtk_widget_realize(window);
8382 gtk_widget_show(window);
8388 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8393 GtkWidget *optmenubox;
8394 GtkWidget *fromlabel;
8397 GtkWidget *from_name = NULL;
8399 gint num = 0, def_menu = 0;
8401 accounts = account_get_list();
8402 cm_return_val_if_fail(accounts != NULL, NULL);
8404 optmenubox = gtk_event_box_new();
8405 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8406 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8408 hbox = gtk_hbox_new(FALSE, 4);
8409 from_name = gtk_entry_new();
8411 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8412 G_CALLBACK(compose_grab_focus_cb), compose);
8413 g_signal_connect_after(G_OBJECT(from_name), "activate",
8414 G_CALLBACK(from_name_activate_cb), optmenu);
8416 for (; accounts != NULL; accounts = accounts->next, num++) {
8417 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8418 gchar *name, *from = NULL;
8420 if (ac == compose->account) def_menu = num;
8422 name = g_markup_printf_escaped("<i>%s</i>",
8425 if (ac == compose->account) {
8426 if (ac->name && *ac->name) {
8428 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8429 from = g_strdup_printf("%s <%s>",
8431 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8433 from = g_strdup_printf("%s",
8435 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8437 if (cur_account != compose->account) {
8438 gtk_widget_modify_base(
8439 GTK_WIDGET(from_name),
8440 GTK_STATE_NORMAL, &default_header_bgcolor);
8441 gtk_widget_modify_text(
8442 GTK_WIDGET(from_name),
8443 GTK_STATE_NORMAL, &default_header_color);
8446 COMBOBOX_ADD(menu, name, ac->account_id);
8451 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8453 g_signal_connect(G_OBJECT(optmenu), "changed",
8454 G_CALLBACK(account_activated),
8456 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8457 G_CALLBACK(compose_entry_popup_extend),
8460 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8461 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8463 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8464 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8465 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8467 /* Putting only the GtkEntry into focus chain of parent hbox causes
8468 * the account selector combobox next to it to be unreachable when
8469 * navigating widgets in GtkTable with up/down arrow keys.
8470 * Note: gtk_widget_set_can_focus() was not enough. */
8472 l = g_list_prepend(l, from_name);
8473 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8476 CLAWS_SET_TIP(optmenubox,
8477 _("Account to use for this email"));
8478 CLAWS_SET_TIP(from_name,
8479 _("Sender address to be used"));
8481 compose->account_combo = optmenu;
8482 compose->from_name = from_name;
8487 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8489 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8490 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8491 Compose *compose = (Compose *) data;
8493 compose->priority = value;
8497 static void compose_reply_change_mode(Compose *compose,
8500 gboolean was_modified = compose->modified;
8502 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8504 cm_return_if_fail(compose->replyinfo != NULL);
8506 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8508 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8510 if (action == COMPOSE_REPLY_TO_ALL)
8512 if (action == COMPOSE_REPLY_TO_SENDER)
8514 if (action == COMPOSE_REPLY_TO_LIST)
8517 compose_remove_header_entries(compose);
8518 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8519 if (compose->account->set_autocc && compose->account->auto_cc)
8520 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8522 if (compose->account->set_autobcc && compose->account->auto_bcc)
8523 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8525 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8526 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8527 compose_show_first_last_header(compose, TRUE);
8528 compose->modified = was_modified;
8529 compose_set_title(compose);
8532 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8534 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8535 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8536 Compose *compose = (Compose *) data;
8539 compose_reply_change_mode(compose, value);
8542 static void compose_update_priority_menu_item(Compose * compose)
8544 GtkWidget *menuitem = NULL;
8545 switch (compose->priority) {
8546 case PRIORITY_HIGHEST:
8547 menuitem = gtk_ui_manager_get_widget
8548 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8551 menuitem = gtk_ui_manager_get_widget
8552 (compose->ui_manager, "/Menu/Options/Priority/High");
8554 case PRIORITY_NORMAL:
8555 menuitem = gtk_ui_manager_get_widget
8556 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8559 menuitem = gtk_ui_manager_get_widget
8560 (compose->ui_manager, "/Menu/Options/Priority/Low");
8562 case PRIORITY_LOWEST:
8563 menuitem = gtk_ui_manager_get_widget
8564 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8567 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8570 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8572 Compose *compose = (Compose *) data;
8574 gboolean can_sign = FALSE, can_encrypt = FALSE;
8576 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8578 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8581 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8582 g_free(compose->privacy_system);
8583 compose->privacy_system = NULL;
8584 g_free(compose->encdata);
8585 compose->encdata = NULL;
8586 if (systemid != NULL) {
8587 compose->privacy_system = g_strdup(systemid);
8589 can_sign = privacy_system_can_sign(systemid);
8590 can_encrypt = privacy_system_can_encrypt(systemid);
8593 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8597 if (compose->toolbar->privacy_sign_btn != NULL) {
8598 gtk_widget_set_sensitive(
8599 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8601 gtk_toggle_tool_button_set_active(
8602 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn),
8603 can_sign ? compose->use_signing : FALSE);
8605 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8606 gtk_widget_set_sensitive(
8607 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8609 gtk_toggle_tool_button_set_active(
8610 GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn),
8611 can_encrypt ? compose->use_encryption : FALSE);
8615 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8617 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8618 GtkWidget *menuitem = NULL;
8619 GList *children, *amenu;
8620 gboolean can_sign = FALSE, can_encrypt = FALSE;
8621 gboolean found = FALSE;
8623 if (compose->privacy_system != NULL) {
8625 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8626 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8627 cm_return_if_fail(menuitem != NULL);
8629 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8632 while (amenu != NULL) {
8633 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8634 if (systemid != NULL) {
8635 if (strcmp(systemid, compose->privacy_system) == 0 &&
8636 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8637 menuitem = GTK_WIDGET(amenu->data);
8639 can_sign = privacy_system_can_sign(systemid);
8640 can_encrypt = privacy_system_can_encrypt(systemid);
8644 } else if (strlen(compose->privacy_system) == 0 &&
8645 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8646 menuitem = GTK_WIDGET(amenu->data);
8649 can_encrypt = FALSE;
8654 amenu = amenu->next;
8656 g_list_free(children);
8657 if (menuitem != NULL)
8658 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8660 if (warn && !found && strlen(compose->privacy_system)) {
8661 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8662 "will not be able to sign or encrypt this message."),
8663 compose->privacy_system);
8667 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8668 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8669 if (compose->toolbar->privacy_sign_btn != NULL) {
8670 gtk_widget_set_sensitive(
8671 GTK_WIDGET(compose->toolbar->privacy_sign_btn),
8674 if (compose->toolbar->privacy_encrypt_btn != NULL) {
8675 gtk_widget_set_sensitive(
8676 GTK_WIDGET(compose->toolbar->privacy_encrypt_btn),
8681 static void compose_set_out_encoding(Compose *compose)
8683 CharSet out_encoding;
8684 const gchar *branch = NULL;
8685 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8687 switch(out_encoding) {
8688 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8689 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8690 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8691 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8692 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8693 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8694 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8695 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8696 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8697 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8698 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8699 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8700 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8701 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8702 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8703 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8704 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8705 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8706 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8707 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8708 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8709 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8710 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8711 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8712 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8713 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8714 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8715 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8716 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8717 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8718 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8719 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8720 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8721 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8723 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8726 static void compose_set_template_menu(Compose *compose)
8728 GSList *tmpl_list, *cur;
8732 tmpl_list = template_get_config();
8734 menu = gtk_menu_new();
8736 gtk_menu_set_accel_group (GTK_MENU (menu),
8737 gtk_ui_manager_get_accel_group(compose->ui_manager));
8738 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8739 Template *tmpl = (Template *)cur->data;
8740 gchar *accel_path = NULL;
8741 item = gtk_menu_item_new_with_label(tmpl->name);
8742 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8743 g_signal_connect(G_OBJECT(item), "activate",
8744 G_CALLBACK(compose_template_activate_cb),
8746 g_object_set_data(G_OBJECT(item), "template", tmpl);
8747 gtk_widget_show(item);
8748 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8749 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8753 gtk_widget_show(menu);
8754 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8757 void compose_update_actions_menu(Compose *compose)
8759 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8762 static void compose_update_privacy_systems_menu(Compose *compose)
8764 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8765 GSList *systems, *cur;
8767 GtkWidget *system_none;
8769 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8770 GtkWidget *privacy_menu = gtk_menu_new();
8772 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8773 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8775 g_signal_connect(G_OBJECT(system_none), "activate",
8776 G_CALLBACK(compose_set_privacy_system_cb), compose);
8778 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8779 gtk_widget_show(system_none);
8781 systems = privacy_get_system_ids();
8782 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8783 gchar *systemid = cur->data;
8785 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8786 widget = gtk_radio_menu_item_new_with_label(group,
8787 privacy_system_get_name(systemid));
8788 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8789 g_strdup(systemid), g_free);
8790 g_signal_connect(G_OBJECT(widget), "activate",
8791 G_CALLBACK(compose_set_privacy_system_cb), compose);
8793 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8794 gtk_widget_show(widget);
8797 g_slist_free(systems);
8798 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8799 gtk_widget_show_all(privacy_menu);
8800 gtk_widget_show_all(privacy_menuitem);
8803 void compose_reflect_prefs_all(void)
8808 for (cur = compose_list; cur != NULL; cur = cur->next) {
8809 compose = (Compose *)cur->data;
8810 compose_set_template_menu(compose);
8814 void compose_reflect_prefs_pixmap_theme(void)
8819 for (cur = compose_list; cur != NULL; cur = cur->next) {
8820 compose = (Compose *)cur->data;
8821 toolbar_update(TOOLBAR_COMPOSE, compose);
8825 static const gchar *compose_quote_char_from_context(Compose *compose)
8827 const gchar *qmark = NULL;
8829 cm_return_val_if_fail(compose != NULL, NULL);
8831 switch (compose->mode) {
8832 /* use forward-specific quote char */
8833 case COMPOSE_FORWARD:
8834 case COMPOSE_FORWARD_AS_ATTACH:
8835 case COMPOSE_FORWARD_INLINE:
8836 if (compose->folder && compose->folder->prefs &&
8837 compose->folder->prefs->forward_with_format)
8838 qmark = compose->folder->prefs->forward_quotemark;
8839 else if (compose->account->forward_with_format)
8840 qmark = compose->account->forward_quotemark;
8842 qmark = prefs_common.fw_quotemark;
8845 /* use reply-specific quote char in all other modes */
8847 if (compose->folder && compose->folder->prefs &&
8848 compose->folder->prefs->reply_with_format)
8849 qmark = compose->folder->prefs->reply_quotemark;
8850 else if (compose->account->reply_with_format)
8851 qmark = compose->account->reply_quotemark;
8853 qmark = prefs_common.quotemark;
8857 if (qmark == NULL || *qmark == '\0')
8863 static void compose_template_apply(Compose *compose, Template *tmpl,
8867 GtkTextBuffer *buffer;
8871 gchar *parsed_str = NULL;
8872 gint cursor_pos = 0;
8873 const gchar *err_msg = _("The body of the template has an error at line %d.");
8876 /* process the body */
8878 text = GTK_TEXT_VIEW(compose->text);
8879 buffer = gtk_text_view_get_buffer(text);
8882 qmark = compose_quote_char_from_context(compose);
8884 if (compose->replyinfo != NULL) {
8887 gtk_text_buffer_set_text(buffer, "", -1);
8888 mark = gtk_text_buffer_get_insert(buffer);
8889 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8891 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8892 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8894 } else if (compose->fwdinfo != NULL) {
8897 gtk_text_buffer_set_text(buffer, "", -1);
8898 mark = gtk_text_buffer_get_insert(buffer);
8899 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8901 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8902 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8905 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8907 GtkTextIter start, end;
8910 gtk_text_buffer_get_start_iter(buffer, &start);
8911 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8912 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8914 /* clear the buffer now */
8916 gtk_text_buffer_set_text(buffer, "", -1);
8918 parsed_str = compose_quote_fmt(compose, dummyinfo,
8919 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8920 procmsg_msginfo_free( &dummyinfo );
8926 gtk_text_buffer_set_text(buffer, "", -1);
8927 mark = gtk_text_buffer_get_insert(buffer);
8928 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8931 if (replace && parsed_str && compose->account->auto_sig)
8932 compose_insert_sig(compose, FALSE);
8934 if (replace && parsed_str) {
8935 gtk_text_buffer_get_start_iter(buffer, &iter);
8936 gtk_text_buffer_place_cursor(buffer, &iter);
8940 cursor_pos = quote_fmt_get_cursor_pos();
8941 compose->set_cursor_pos = cursor_pos;
8942 if (cursor_pos == -1)
8944 gtk_text_buffer_get_start_iter(buffer, &iter);
8945 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8946 gtk_text_buffer_place_cursor(buffer, &iter);
8949 /* process the other fields */
8951 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8952 compose_template_apply_fields(compose, tmpl);
8953 quote_fmt_reset_vartable();
8954 quote_fmtlex_destroy();
8956 compose_changed_cb(NULL, compose);
8959 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8960 gtkaspell_highlight_all(compose->gtkaspell);
8964 static void compose_template_apply_fields_error(const gchar *header)
8969 tr = g_strdup(C_("'%s' stands for a header name",
8970 "Template '%s' format error."));
8971 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8972 alertpanel_error("%s", text);
8978 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8980 MsgInfo* dummyinfo = NULL;
8981 MsgInfo *msginfo = NULL;
8984 if (compose->replyinfo != NULL)
8985 msginfo = compose->replyinfo;
8986 else if (compose->fwdinfo != NULL)
8987 msginfo = compose->fwdinfo;
8989 dummyinfo = compose_msginfo_new_from_compose(compose);
8990 msginfo = dummyinfo;
8993 if (tmpl->from && *tmpl->from != '\0') {
8995 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8996 compose->gtkaspell);
8998 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9000 quote_fmt_scan_string(tmpl->from);
9003 buf = quote_fmt_get_buffer();
9005 compose_template_apply_fields_error("From");
9007 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
9010 quote_fmt_reset_vartable();
9011 quote_fmtlex_destroy();
9014 if (tmpl->to && *tmpl->to != '\0') {
9016 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9017 compose->gtkaspell);
9019 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9021 quote_fmt_scan_string(tmpl->to);
9024 buf = quote_fmt_get_buffer();
9026 compose_template_apply_fields_error("To");
9028 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
9031 quote_fmt_reset_vartable();
9032 quote_fmtlex_destroy();
9035 if (tmpl->cc && *tmpl->cc != '\0') {
9037 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9038 compose->gtkaspell);
9040 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9042 quote_fmt_scan_string(tmpl->cc);
9045 buf = quote_fmt_get_buffer();
9047 compose_template_apply_fields_error("Cc");
9049 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
9052 quote_fmt_reset_vartable();
9053 quote_fmtlex_destroy();
9056 if (tmpl->bcc && *tmpl->bcc != '\0') {
9058 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9059 compose->gtkaspell);
9061 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9063 quote_fmt_scan_string(tmpl->bcc);
9066 buf = quote_fmt_get_buffer();
9068 compose_template_apply_fields_error("Bcc");
9070 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
9073 quote_fmt_reset_vartable();
9074 quote_fmtlex_destroy();
9077 if (tmpl->replyto && *tmpl->replyto != '\0') {
9079 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9080 compose->gtkaspell);
9082 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9084 quote_fmt_scan_string(tmpl->replyto);
9087 buf = quote_fmt_get_buffer();
9089 compose_template_apply_fields_error("Reply-To");
9091 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
9094 quote_fmt_reset_vartable();
9095 quote_fmtlex_destroy();
9098 /* process the subject */
9099 if (tmpl->subject && *tmpl->subject != '\0') {
9101 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
9102 compose->gtkaspell);
9104 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
9106 quote_fmt_scan_string(tmpl->subject);
9109 buf = quote_fmt_get_buffer();
9111 compose_template_apply_fields_error("Subject");
9113 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
9116 quote_fmt_reset_vartable();
9117 quote_fmtlex_destroy();
9120 procmsg_msginfo_free( &dummyinfo );
9123 static void compose_destroy(Compose *compose)
9125 GtkAllocation allocation;
9126 GtkTextBuffer *buffer;
9127 GtkClipboard *clipboard;
9129 compose_list = g_list_remove(compose_list, compose);
9132 gboolean enable = TRUE;
9133 g_slist_foreach(compose->passworded_ldap_servers,
9134 _ldap_srv_func, &enable);
9135 g_slist_free(compose->passworded_ldap_servers);
9138 if (compose->updating) {
9139 debug_print("danger, not destroying anything now\n");
9140 compose->deferred_destroy = TRUE;
9144 /* NOTE: address_completion_end() does nothing with the window
9145 * however this may change. */
9146 address_completion_end(compose->window);
9148 slist_free_strings_full(compose->to_list);
9149 slist_free_strings_full(compose->newsgroup_list);
9150 slist_free_strings_full(compose->header_list);
9152 slist_free_strings_full(extra_headers);
9153 extra_headers = NULL;
9155 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
9157 g_hash_table_destroy(compose->email_hashtable);
9159 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
9160 compose->folder_update_callback_id);
9162 procmsg_msginfo_free(&(compose->targetinfo));
9163 procmsg_msginfo_free(&(compose->replyinfo));
9164 procmsg_msginfo_free(&(compose->fwdinfo));
9166 g_free(compose->replyto);
9167 g_free(compose->cc);
9168 g_free(compose->bcc);
9169 g_free(compose->newsgroups);
9170 g_free(compose->followup_to);
9172 g_free(compose->ml_post);
9174 g_free(compose->inreplyto);
9175 g_free(compose->references);
9176 g_free(compose->msgid);
9177 g_free(compose->boundary);
9179 g_free(compose->redirect_filename);
9180 if (compose->undostruct)
9181 undo_destroy(compose->undostruct);
9183 g_free(compose->sig_str);
9185 g_free(compose->exteditor_file);
9187 g_free(compose->orig_charset);
9189 g_free(compose->privacy_system);
9190 g_free(compose->encdata);
9192 #ifndef USE_ALT_ADDRBOOK
9193 if (addressbook_get_target_compose() == compose)
9194 addressbook_set_target_compose(NULL);
9197 if (compose->gtkaspell) {
9198 gtkaspell_delete(compose->gtkaspell);
9199 compose->gtkaspell = NULL;
9203 if (!compose->batch) {
9204 gtk_widget_get_allocation(GTK_WIDGET(compose->window),
9206 prefs_common.compose_width = allocation.width;
9207 prefs_common.compose_height = allocation.height;
9210 if (!gtk_widget_get_parent(compose->paned))
9211 gtk_widget_destroy(compose->paned);
9212 gtk_widget_destroy(compose->popupmenu);
9214 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9215 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
9216 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
9218 message_search_close(compose);
9219 gtk_widget_destroy(compose->window);
9220 toolbar_destroy(compose->toolbar);
9221 g_free(compose->toolbar);
9222 cm_mutex_free(compose->mutex);
9226 static void compose_attach_info_free(AttachInfo *ainfo)
9228 g_free(ainfo->file);
9229 g_free(ainfo->content_type);
9230 g_free(ainfo->name);
9231 g_free(ainfo->charset);
9235 static void compose_attach_update_label(Compose *compose)
9240 GtkTreeModel *model;
9244 if (compose == NULL)
9247 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
9248 if (!gtk_tree_model_get_iter_first(model, &iter)) {
9249 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
9253 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9254 total_size = ainfo->size;
9255 while(gtk_tree_model_iter_next(model, &iter)) {
9256 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9257 total_size += ainfo->size;
9260 text = g_strdup_printf(" (%d/%s)", i, to_human_readable(total_size));
9261 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
9265 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
9267 Compose *compose = (Compose *)data;
9268 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9269 GtkTreeSelection *selection;
9271 GtkTreeModel *model;
9273 selection = gtk_tree_view_get_selection(tree_view);
9274 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9275 cm_return_if_fail(sel);
9277 for (cur = sel; cur != NULL; cur = cur->next) {
9278 GtkTreePath *path = cur->data;
9279 GtkTreeRowReference *ref = gtk_tree_row_reference_new
9282 gtk_tree_path_free(path);
9285 for (cur = sel; cur != NULL; cur = cur->next) {
9286 GtkTreeRowReference *ref = cur->data;
9287 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
9290 if (gtk_tree_model_get_iter(model, &iter, path))
9291 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
9293 gtk_tree_path_free(path);
9294 gtk_tree_row_reference_free(ref);
9298 compose_attach_update_label(compose);
9301 static struct _AttachProperty
9304 GtkWidget *mimetype_entry;
9305 GtkWidget *encoding_optmenu;
9306 GtkWidget *path_entry;
9307 GtkWidget *filename_entry;
9309 GtkWidget *cancel_btn;
9312 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
9314 gtk_tree_path_free((GtkTreePath *)ptr);
9317 static void compose_attach_property(GtkAction *action, gpointer data)
9319 Compose *compose = (Compose *)data;
9320 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
9322 GtkComboBox *optmenu;
9323 GtkTreeSelection *selection;
9325 GtkTreeModel *model;
9328 static gboolean cancelled;
9330 /* only if one selected */
9331 selection = gtk_tree_view_get_selection(tree_view);
9332 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9335 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9336 cm_return_if_fail(sel);
9338 path = (GtkTreePath *) sel->data;
9339 gtk_tree_model_get_iter(model, &iter, path);
9340 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9343 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9349 if (!attach_prop.window)
9350 compose_attach_property_create(&cancelled);
9351 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9352 gtk_widget_grab_focus(attach_prop.ok_btn);
9353 gtk_widget_show(attach_prop.window);
9354 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9355 GTK_WINDOW(compose->window));
9357 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9358 if (ainfo->encoding == ENC_UNKNOWN)
9359 combobox_select_by_data(optmenu, ENC_BASE64);
9361 combobox_select_by_data(optmenu, ainfo->encoding);
9363 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9364 ainfo->content_type ? ainfo->content_type : "");
9365 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9366 ainfo->file ? ainfo->file : "");
9367 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9368 ainfo->name ? ainfo->name : "");
9371 const gchar *entry_text;
9373 gchar *cnttype = NULL;
9380 gtk_widget_hide(attach_prop.window);
9381 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9386 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9387 if (*entry_text != '\0') {
9390 text = g_strstrip(g_strdup(entry_text));
9391 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9392 cnttype = g_strdup(text);
9395 alertpanel_error(_("Invalid MIME type."));
9401 ainfo->encoding = combobox_get_active_data(optmenu);
9403 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9404 if (*entry_text != '\0') {
9405 if (is_file_exist(entry_text) &&
9406 (size = get_file_size(entry_text)) > 0)
9407 file = g_strdup(entry_text);
9410 (_("File doesn't exist or is empty."));
9416 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9417 if (*entry_text != '\0') {
9418 g_free(ainfo->name);
9419 ainfo->name = g_strdup(entry_text);
9423 g_free(ainfo->content_type);
9424 ainfo->content_type = cnttype;
9427 g_free(ainfo->file);
9431 ainfo->size = (goffset)size;
9433 /* update tree store */
9434 text = to_human_readable(ainfo->size);
9435 gtk_tree_model_get_iter(model, &iter, path);
9436 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9437 COL_MIMETYPE, ainfo->content_type,
9439 COL_NAME, ainfo->name,
9440 COL_CHARSET, ainfo->charset,
9446 gtk_tree_path_free(path);
9449 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9451 label = gtk_label_new(str); \
9452 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9453 GTK_FILL, 0, 0, 0); \
9454 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9456 entry = gtk_entry_new(); \
9457 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9458 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9461 static void compose_attach_property_create(gboolean *cancelled)
9467 GtkWidget *mimetype_entry;
9470 GtkListStore *optmenu_menu;
9471 GtkWidget *path_entry;
9472 GtkWidget *filename_entry;
9475 GtkWidget *cancel_btn;
9476 GList *mime_type_list, *strlist;
9479 debug_print("Creating attach_property window...\n");
9481 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9482 gtk_widget_set_size_request(window, 480, -1);
9483 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9484 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9485 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9486 gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
9487 g_signal_connect(G_OBJECT(window), "delete_event",
9488 G_CALLBACK(attach_property_delete_event),
9490 g_signal_connect(G_OBJECT(window), "key_press_event",
9491 G_CALLBACK(attach_property_key_pressed),
9494 vbox = gtk_vbox_new(FALSE, 8);
9495 gtk_container_add(GTK_CONTAINER(window), vbox);
9497 table = gtk_table_new(4, 2, FALSE);
9498 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9499 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9500 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9502 label = gtk_label_new(_("MIME type"));
9503 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9505 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9506 mimetype_entry = gtk_combo_box_text_new_with_entry();
9507 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9508 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9510 /* stuff with list */
9511 mime_type_list = procmime_get_mime_type_list();
9513 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9514 MimeType *type = (MimeType *) mime_type_list->data;
9517 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9519 if (g_list_find_custom(strlist, tmp, (GCompareFunc)g_strcmp0))
9522 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9523 (GCompareFunc)g_strcmp0);
9526 for (mime_type_list = strlist; mime_type_list != NULL;
9527 mime_type_list = mime_type_list->next) {
9528 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9529 g_free(mime_type_list->data);
9531 g_list_free(strlist);
9532 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9533 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9535 label = gtk_label_new(_("Encoding"));
9536 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9538 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9540 hbox = gtk_hbox_new(FALSE, 0);
9541 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9542 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9544 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9545 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9547 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9548 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9549 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9550 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9551 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9553 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9555 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9556 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9558 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9559 &ok_btn, GTK_STOCK_OK,
9561 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9562 gtk_widget_grab_default(ok_btn);
9564 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9565 G_CALLBACK(attach_property_ok),
9567 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9568 G_CALLBACK(attach_property_cancel),
9571 gtk_widget_show_all(vbox);
9573 attach_prop.window = window;
9574 attach_prop.mimetype_entry = mimetype_entry;
9575 attach_prop.encoding_optmenu = optmenu;
9576 attach_prop.path_entry = path_entry;
9577 attach_prop.filename_entry = filename_entry;
9578 attach_prop.ok_btn = ok_btn;
9579 attach_prop.cancel_btn = cancel_btn;
9582 #undef SET_LABEL_AND_ENTRY
9584 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9590 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9596 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9597 gboolean *cancelled)
9605 static gboolean attach_property_key_pressed(GtkWidget *widget,
9607 gboolean *cancelled)
9609 if (event && event->keyval == GDK_KEY_Escape) {
9613 if (event && event->keyval == GDK_KEY_Return) {
9621 static gboolean compose_can_autosave(Compose *compose)
9623 if (compose->privacy_system && compose->use_encryption)
9624 return prefs_common.autosave && prefs_common.autosave_encrypted;
9626 return prefs_common.autosave;
9630 * compose_exec_ext_editor:
9632 * Open (and optionally embed) external editor
9634 static void compose_exec_ext_editor(Compose *compose)
9639 GdkNativeWindow socket_wid = 0;
9641 #endif /* G_OS_WIN32 */
9643 GError *error = NULL;
9647 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9648 G_DIR_SEPARATOR, compose);
9650 if (compose_write_body_to_file(compose, tmp) < 0) {
9651 alertpanel_error(_("Could not write the body to file:\n%s"),
9657 if (compose_get_ext_editor_uses_socket()) {
9659 /* Only allow one socket */
9660 if (compose->exteditor_socket != NULL) {
9661 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9662 /* Move the focus off of the socket */
9663 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9668 /* Create the receiving GtkSocket */
9669 socket = gtk_socket_new ();
9670 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9671 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9673 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9674 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9675 /* Realize the socket so that we can use its ID */
9676 gtk_widget_realize(socket);
9677 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9678 compose->exteditor_socket = socket;
9679 #endif /* G_OS_WIN32 */
9682 if (compose_get_ext_editor_cmd_valid()) {
9683 if (compose_get_ext_editor_uses_socket()) {
9685 p = g_strdup(prefs_common_get_ext_editor_cmd());
9686 s = strstr(p, "%w");
9688 if (strstr(p, "%s") < s)
9689 cmd = g_strdup_printf(p, tmp, socket_wid);
9691 cmd = g_strdup_printf(p, socket_wid, tmp);
9693 #endif /* G_OS_WIN32 */
9695 cmd = g_strdup_printf(prefs_common_get_ext_editor_cmd(), tmp);
9698 if (prefs_common_get_ext_editor_cmd())
9699 g_warning("external editor command-line is invalid: '%s'",
9700 prefs_common_get_ext_editor_cmd());
9701 cmd = g_strdup_printf(DEFAULT_EDITOR_CMD, tmp);
9704 argv = strsplit_with_quote(cmd, " ", 0);
9706 if (!g_spawn_async(NULL, argv, NULL,
9707 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
9708 NULL, NULL, &pid, &error)) {
9709 alertpanel_error(_("Could not spawn the following "
9710 "command:\n%s\n%s"),
9711 cmd, error ? error->message : _("Unknown error"));
9713 g_error_free(error);
9722 compose->exteditor_file = g_strdup(tmp);
9723 compose->exteditor_pid = pid;
9724 compose->exteditor_tag = g_child_watch_add(pid,
9725 compose_ext_editor_closed_cb,
9728 compose_set_ext_editor_sensitive(compose, FALSE);
9734 * compose_ext_editor_cb:
9736 * External editor has closed (called by g_child_watch)
9738 static void compose_ext_editor_closed_cb(GPid pid, gint exit_status, gpointer data)
9740 Compose *compose = (Compose *)data;
9741 GError *error = NULL;
9742 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9743 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9744 GtkTextIter start, end;
9747 if (!g_spawn_check_exit_status(exit_status, &error)) {
9749 _("External editor stopped with an error: %s"),
9750 error ? error->message : _("Unknown error"));
9752 g_error_free(error);
9754 g_spawn_close_pid(compose->exteditor_pid);
9756 gtk_text_buffer_set_text(buffer, "", -1);
9757 compose_insert_file(compose, compose->exteditor_file);
9758 compose_changed_cb(NULL, compose);
9760 /* Check if we should save the draft or not */
9761 if (compose_can_autosave(compose))
9762 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9764 if (claws_unlink(compose->exteditor_file) < 0)
9765 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9767 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9768 gtk_text_buffer_get_start_iter(buffer, &start);
9769 gtk_text_buffer_get_end_iter(buffer, &end);
9770 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9771 if (chars && strlen(chars) > 0)
9772 compose->modified = TRUE;
9775 compose_set_ext_editor_sensitive(compose, TRUE);
9777 g_free(compose->exteditor_file);
9778 compose->exteditor_file = NULL;
9779 compose->exteditor_pid = INVALID_PID;
9780 compose->exteditor_tag = -1;
9781 if (compose->exteditor_socket) {
9782 gtk_widget_destroy(compose->exteditor_socket);
9783 compose->exteditor_socket = NULL;
9788 static gboolean compose_get_ext_editor_cmd_valid()
9790 gboolean has_s = FALSE;
9791 gboolean has_w = FALSE;
9792 const gchar *p = prefs_common_get_ext_editor_cmd();
9795 while ((p = strchr(p, '%'))) {
9801 } else if (*p == 'w') {
9812 static gboolean compose_ext_editor_kill(Compose *compose)
9814 GPid pid = compose->exteditor_pid;
9820 msg = g_strdup_printf
9821 (_("The external editor is still working.\n"
9822 "Force terminating the process?\n"
9823 "process id: %" G_PID_FORMAT), pid);
9824 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO,
9825 GTK_STOCK_YES, NULL, ALERTFOCUS_FIRST,
9826 FALSE, NULL, ALERT_WARNING);
9829 if (val == G_ALERTALTERNATE) {
9830 g_source_remove(compose->exteditor_tag);
9833 if (!TerminateProcess(compose->exteditor_pid, 0))
9834 perror("TerminateProcess");
9836 if (kill(pid, SIGTERM) < 0) perror("kill");
9837 waitpid(compose->exteditor_pid, NULL, 0);
9838 #endif /* G_OS_WIN32 */
9840 g_warning("terminated process id: %" G_PID_FORMAT ", "
9841 "temporary file: %s", pid, compose->exteditor_file);
9842 g_spawn_close_pid(compose->exteditor_pid);
9844 compose_set_ext_editor_sensitive(compose, TRUE);
9846 g_free(compose->exteditor_file);
9847 compose->exteditor_file = NULL;
9848 compose->exteditor_pid = INVALID_PID;
9849 compose->exteditor_tag = -1;
9857 static char *ext_editor_menu_entries[] = {
9858 "Menu/Message/Send",
9859 "Menu/Message/SendLater",
9860 "Menu/Message/InsertFile",
9861 "Menu/Message/InsertSig",
9862 "Menu/Message/ReplaceSig",
9863 "Menu/Message/Save",
9864 "Menu/Message/Print",
9869 "Menu/Tools/ShowRuler",
9870 "Menu/Tools/Actions",
9875 static void compose_set_ext_editor_sensitive(Compose *compose,
9880 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9881 cm_menu_set_sensitive_full(compose->ui_manager,
9882 ext_editor_menu_entries[i], sensitive);
9885 if (compose_get_ext_editor_uses_socket()) {
9887 if (compose->exteditor_socket)
9888 gtk_widget_hide(compose->exteditor_socket);
9889 gtk_widget_show(compose->scrolledwin);
9890 if (prefs_common.show_ruler)
9891 gtk_widget_show(compose->ruler_hbox);
9892 /* Fix the focus, as it doesn't go anywhere when the
9893 * socket is hidden or destroyed */
9894 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9896 g_assert (compose->exteditor_socket != NULL);
9897 /* Fix the focus, as it doesn't go anywhere when the
9898 * edit box is hidden */
9899 if (gtk_widget_is_focus(compose->text))
9900 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9901 gtk_widget_hide(compose->scrolledwin);
9902 gtk_widget_hide(compose->ruler_hbox);
9903 gtk_widget_show(compose->exteditor_socket);
9906 gtk_widget_set_sensitive(compose->text, sensitive);
9908 if (compose->toolbar->send_btn)
9909 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9910 if (compose->toolbar->sendl_btn)
9911 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9912 if (compose->toolbar->draft_btn)
9913 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9914 if (compose->toolbar->insert_btn)
9915 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9916 if (compose->toolbar->sig_btn)
9917 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9918 if (compose->toolbar->exteditor_btn)
9919 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9920 if (compose->toolbar->linewrap_current_btn)
9921 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9922 if (compose->toolbar->linewrap_all_btn)
9923 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9926 static gboolean compose_get_ext_editor_uses_socket()
9928 return (prefs_common_get_ext_editor_cmd() &&
9929 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9933 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9935 compose->exteditor_socket = NULL;
9936 /* returning FALSE allows destruction of the socket */
9939 #endif /* G_OS_WIN32 */
9942 * compose_undo_state_changed:
9944 * Change the sensivity of the menuentries undo and redo
9946 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9947 gint redo_state, gpointer data)
9949 Compose *compose = (Compose *)data;
9951 switch (undo_state) {
9952 case UNDO_STATE_TRUE:
9953 if (!undostruct->undo_state) {
9954 undostruct->undo_state = TRUE;
9955 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9958 case UNDO_STATE_FALSE:
9959 if (undostruct->undo_state) {
9960 undostruct->undo_state = FALSE;
9961 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9964 case UNDO_STATE_UNCHANGED:
9966 case UNDO_STATE_REFRESH:
9967 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9970 g_warning("undo state not recognized");
9974 switch (redo_state) {
9975 case UNDO_STATE_TRUE:
9976 if (!undostruct->redo_state) {
9977 undostruct->redo_state = TRUE;
9978 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9981 case UNDO_STATE_FALSE:
9982 if (undostruct->redo_state) {
9983 undostruct->redo_state = FALSE;
9984 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9987 case UNDO_STATE_UNCHANGED:
9989 case UNDO_STATE_REFRESH:
9990 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9993 g_warning("redo state not recognized");
9998 /* callback functions */
10000 static void compose_notebook_size_alloc(GtkNotebook *notebook,
10001 GtkAllocation *allocation,
10004 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
10007 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
10008 * includes "non-client" (windows-izm) in calculation, so this calculation
10009 * may not be accurate.
10011 static gboolean compose_edit_size_alloc(GtkEditable *widget,
10012 GtkAllocation *allocation,
10013 GtkSHRuler *shruler)
10015 if (prefs_common.show_ruler) {
10016 gint char_width = 0, char_height = 0;
10017 gint line_width_in_chars;
10019 gtkut_get_font_size(GTK_WIDGET(widget),
10020 &char_width, &char_height);
10021 line_width_in_chars =
10022 (allocation->width - allocation->x) / char_width;
10024 /* got the maximum */
10025 gtk_shruler_set_range(GTK_SHRULER(shruler),
10026 0.0, line_width_in_chars, 0);
10035 ComposePrefType type;
10036 gboolean entry_marked;
10037 } HeaderEntryState;
10039 static void account_activated(GtkComboBox *optmenu, gpointer data)
10041 Compose *compose = (Compose *)data;
10044 gchar *folderidentifier;
10045 gint account_id = 0;
10046 GtkTreeModel *menu;
10048 GSList *list, *saved_list = NULL;
10049 HeaderEntryState *state;
10051 /* Get ID of active account in the combo box */
10052 menu = gtk_combo_box_get_model(optmenu);
10053 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
10054 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
10056 ac = account_find_from_id(account_id);
10057 cm_return_if_fail(ac != NULL);
10059 if (ac != compose->account) {
10060 compose_select_account(compose, ac, FALSE);
10062 for (list = compose->header_list; list; list = list->next) {
10063 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
10065 if (hentry->type == PREF_ACCOUNT || !list->next) {
10066 compose_destroy_headerentry(compose, hentry);
10069 state = g_malloc0(sizeof(HeaderEntryState));
10070 state->header = gtk_editable_get_chars(GTK_EDITABLE(
10071 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
10072 state->entry = gtk_editable_get_chars(
10073 GTK_EDITABLE(hentry->entry), 0, -1);
10074 state->type = hentry->type;
10076 saved_list = g_slist_append(saved_list, state);
10077 compose_destroy_headerentry(compose, hentry);
10080 compose->header_last = NULL;
10081 g_slist_free(compose->header_list);
10082 compose->header_list = NULL;
10083 compose->header_nextrow = 1;
10084 compose_create_header_entry(compose);
10086 if (ac->set_autocc && ac->auto_cc)
10087 compose_entry_append(compose, ac->auto_cc,
10088 COMPOSE_CC, PREF_ACCOUNT);
10089 if (ac->set_autobcc && ac->auto_bcc)
10090 compose_entry_append(compose, ac->auto_bcc,
10091 COMPOSE_BCC, PREF_ACCOUNT);
10092 if (ac->set_autoreplyto && ac->auto_replyto)
10093 compose_entry_append(compose, ac->auto_replyto,
10094 COMPOSE_REPLYTO, PREF_ACCOUNT);
10096 for (list = saved_list; list; list = list->next) {
10097 state = (HeaderEntryState *) list->data;
10099 compose_add_header_entry(compose, state->header,
10100 state->entry, state->type);
10102 g_free(state->header);
10103 g_free(state->entry);
10106 g_slist_free(saved_list);
10108 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
10109 (ac->protocol == A_NNTP) ?
10110 COMPOSE_NEWSGROUPS : COMPOSE_TO);
10113 /* Set message save folder */
10114 compose_set_save_to(compose, NULL);
10115 if (compose->folder && compose->folder->prefs && compose->folder->prefs->save_copy_to_folder) {
10116 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10117 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10118 folderidentifier = folder_item_get_identifier(compose->folder);
10119 compose_set_save_to(compose, folderidentifier);
10120 g_free(folderidentifier);
10121 } else if (account_get_special_folder(compose->account, F_OUTBOX)) {
10122 if (compose->account->set_sent_folder || prefs_common.savemsg)
10123 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
10125 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), FALSE);
10126 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
10127 folderidentifier = folder_item_get_identifier(account_get_special_folder
10128 (compose->account, F_OUTBOX));
10129 compose_set_save_to(compose, folderidentifier);
10130 g_free(folderidentifier);
10134 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
10135 GtkTreeViewColumn *column, Compose *compose)
10137 compose_attach_property(NULL, compose);
10140 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
10143 Compose *compose = (Compose *)data;
10144 GtkTreeSelection *attach_selection;
10145 gint attach_nr_selected;
10148 if (!event) return FALSE;
10150 if (event->button == 3) {
10151 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
10152 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
10154 /* If no rows, or just one row is selected, right-click should
10155 * open menu relevant to the row being right-clicked on. We
10156 * achieve that by selecting the clicked row first. If more
10157 * than one row is selected, we shouldn't modify the selection,
10158 * as user may want to remove selected rows (attachments). */
10159 if (attach_nr_selected < 2) {
10160 gtk_tree_selection_unselect_all(attach_selection);
10161 attach_nr_selected = 0;
10162 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
10163 event->x, event->y, &path, NULL, NULL, NULL);
10164 if (path != NULL) {
10165 gtk_tree_selection_select_path(attach_selection, path);
10166 gtk_tree_path_free(path);
10167 attach_nr_selected++;
10171 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
10172 /* Properties menu item makes no sense with more than one row
10173 * selected, the properties dialog can only edit one attachment. */
10174 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
10176 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
10177 NULL, NULL, event->button, event->time);
10184 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
10187 Compose *compose = (Compose *)data;
10189 if (!event) return FALSE;
10191 switch (event->keyval) {
10192 case GDK_KEY_Delete:
10193 compose_attach_remove_selected(NULL, compose);
10199 static void compose_allow_user_actions (Compose *compose, gboolean allow)
10201 toolbar_comp_set_sensitive(compose, allow);
10202 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
10203 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
10205 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
10207 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
10208 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
10209 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
10211 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
10215 static void compose_send_cb(GtkAction *action, gpointer data)
10217 Compose *compose = (Compose *)data;
10220 if (compose->exteditor_tag != -1) {
10221 debug_print("ignoring send: external editor still open\n");
10225 if (prefs_common.work_offline &&
10226 !inc_offline_should_override(TRUE,
10227 _("Claws Mail needs network access in order "
10228 "to send this email.")))
10231 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
10232 g_source_remove(compose->draft_timeout_tag);
10233 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
10236 compose_send(compose);
10239 static void compose_send_later_cb(GtkAction *action, gpointer data)
10241 Compose *compose = (Compose *)data;
10242 ComposeQueueResult val;
10245 compose_allow_user_actions(compose, FALSE);
10246 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
10247 compose_allow_user_actions(compose, TRUE);
10250 if (val == COMPOSE_QUEUE_SUCCESS) {
10251 compose_close(compose);
10253 _display_queue_error(val);
10256 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10259 #define DRAFTED_AT_EXIT "drafted_at_exit"
10260 static void compose_register_draft(MsgInfo *info)
10262 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10263 DRAFTED_AT_EXIT, NULL);
10264 FILE *fp = claws_fopen(filepath, "ab");
10267 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10275 gboolean compose_draft (gpointer data, guint action)
10277 Compose *compose = (Compose *)data;
10279 FolderItemPrefs *prefs;
10283 MsgFlags flag = {0, 0};
10284 static gboolean lock = FALSE;
10285 MsgInfo *newmsginfo;
10287 gboolean target_locked = FALSE;
10288 gboolean err = FALSE;
10291 if (lock) return FALSE;
10293 if (compose->sending)
10296 draft = account_get_special_folder(compose->account, F_DRAFT);
10297 cm_return_val_if_fail(draft != NULL, FALSE);
10299 if (!g_mutex_trylock(compose->mutex)) {
10300 /* we don't want to lock the mutex once it's available,
10301 * because as the only other part of compose.c locking
10302 * it is compose_close - which means once unlocked,
10303 * the compose struct will be freed */
10304 debug_print("couldn't lock mutex, probably sending\n");
10310 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10311 G_DIR_SEPARATOR, compose);
10312 if ((fp = claws_fopen(tmp, "wb")) == NULL) {
10313 FILE_OP_ERROR(tmp, "claws_fopen");
10317 /* chmod for security unless folder chmod is set */
10318 prefs = draft->prefs;
10319 if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
10320 filemode = prefs->folder_chmod;
10321 if (filemode & S_IRGRP) filemode |= S_IWGRP;
10322 if (filemode & S_IROTH) filemode |= S_IWOTH;
10323 if (chmod(tmp, filemode) < 0)
10324 FILE_OP_ERROR(tmp, "chmod");
10325 } else if (change_file_mode_rw(fp, tmp) < 0) {
10326 FILE_OP_ERROR(tmp, "chmod");
10327 g_warning("can't change file mode");
10330 /* Save draft infos */
10331 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10332 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10334 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10335 gchar *savefolderid;
10337 savefolderid = compose_get_save_to(compose);
10338 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10339 g_free(savefolderid);
10341 if (compose->return_receipt) {
10342 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10344 if (compose->privacy_system) {
10345 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10346 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10347 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10350 /* Message-ID of message replying to */
10351 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10352 gchar *folderid = NULL;
10354 if (compose->replyinfo->folder)
10355 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10356 if (folderid == NULL)
10357 folderid = g_strdup("NULL");
10359 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10362 /* Message-ID of message forwarding to */
10363 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10364 gchar *folderid = NULL;
10366 if (compose->fwdinfo->folder)
10367 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10368 if (folderid == NULL)
10369 folderid = g_strdup("NULL");
10371 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10375 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10376 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10378 sheaders = compose_get_manual_headers_info(compose);
10379 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10382 /* end of headers */
10383 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10390 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10394 if (claws_safe_fclose(fp) == EOF) {
10398 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10399 if (compose->targetinfo) {
10400 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10402 flag.perm_flags |= MSG_LOCKED;
10404 flag.tmp_flags = MSG_DRAFT;
10406 folder_item_scan(draft);
10407 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10408 MsgInfo *tmpinfo = NULL;
10409 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10410 if (compose->msgid) {
10411 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10414 msgnum = tmpinfo->msgnum;
10415 procmsg_msginfo_free(&tmpinfo);
10416 debug_print("got draft msgnum %d from scanning\n", msgnum);
10418 debug_print("didn't get draft msgnum after scanning\n");
10421 debug_print("got draft msgnum %d from adding\n", msgnum);
10427 if (action != COMPOSE_AUTO_SAVE) {
10428 if (action != COMPOSE_DRAFT_FOR_EXIT)
10429 alertpanel_error(_("Could not save draft."));
10432 gtkut_window_popup(compose->window);
10433 val = alertpanel_full(_("Could not save draft"),
10434 _("Could not save draft.\n"
10435 "Do you want to cancel exit or discard this email?"),
10436 _("_Cancel exit"), _("_Discard email"), NULL, ALERTFOCUS_FIRST,
10437 FALSE, NULL, ALERT_QUESTION);
10438 if (val == G_ALERTALTERNATE) {
10440 g_mutex_unlock(compose->mutex); /* must be done before closing */
10441 compose_close(compose);
10445 g_mutex_unlock(compose->mutex); /* must be done before closing */
10454 if (compose->mode == COMPOSE_REEDIT) {
10455 compose_remove_reedit_target(compose, TRUE);
10458 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10461 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10463 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10465 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10466 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10467 procmsg_msginfo_set_flags(newmsginfo, 0,
10468 MSG_HAS_ATTACHMENT);
10470 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10471 compose_register_draft(newmsginfo);
10473 procmsg_msginfo_free(&newmsginfo);
10476 folder_item_scan(draft);
10478 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10480 g_mutex_unlock(compose->mutex); /* must be done before closing */
10481 compose_close(compose);
10488 GError *error = NULL;
10493 goffset size, mtime;
10495 path = folder_item_fetch_msg(draft, msgnum);
10496 if (path == NULL) {
10497 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10501 f = g_file_new_for_path(path);
10502 fi = g_file_query_info(f, "standard::size,time::modified",
10503 G_FILE_QUERY_INFO_NONE, NULL, &error);
10504 if (error != NULL) {
10505 debug_print("couldn't query file info for '%s': %s\n",
10506 path, error->message);
10507 g_error_free(error);
10512 size = g_file_info_get_size(fi);
10513 g_file_info_get_modification_time(fi, &tv);
10515 g_object_unref(fi);
10518 if (g_stat(path, &s) < 0) {
10519 FILE_OP_ERROR(path, "stat");
10524 mtime = s.st_mtime;
10528 procmsg_msginfo_free(&(compose->targetinfo));
10529 compose->targetinfo = procmsg_msginfo_new();
10530 compose->targetinfo->msgnum = msgnum;
10531 compose->targetinfo->size = size;
10532 compose->targetinfo->mtime = mtime;
10533 compose->targetinfo->folder = draft;
10535 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10536 compose->mode = COMPOSE_REEDIT;
10538 if (action == COMPOSE_AUTO_SAVE) {
10539 compose->autosaved_draft = compose->targetinfo;
10541 compose->modified = FALSE;
10542 compose_set_title(compose);
10546 g_mutex_unlock(compose->mutex);
10550 void compose_clear_exit_drafts(void)
10552 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10553 DRAFTED_AT_EXIT, NULL);
10554 if (is_file_exist(filepath))
10555 claws_unlink(filepath);
10560 void compose_reopen_exit_drafts(void)
10562 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10563 DRAFTED_AT_EXIT, NULL);
10564 FILE *fp = claws_fopen(filepath, "rb");
10568 while (claws_fgets(buf, sizeof(buf), fp)) {
10569 gchar **parts = g_strsplit(buf, "\t", 2);
10570 const gchar *folder = parts[0];
10571 int msgnum = parts[1] ? atoi(parts[1]):-1;
10573 if (folder && *folder && msgnum > -1) {
10574 FolderItem *item = folder_find_item_from_identifier(folder);
10575 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10577 compose_reedit(info, FALSE);
10584 compose_clear_exit_drafts();
10587 static void compose_save_cb(GtkAction *action, gpointer data)
10589 Compose *compose = (Compose *)data;
10590 compose_draft(compose, COMPOSE_KEEP_EDITING);
10591 compose->rmode = COMPOSE_REEDIT;
10594 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10596 if (compose && file_list) {
10599 for ( tmp = file_list; tmp; tmp = tmp->next) {
10600 gchar *file = (gchar *) tmp->data;
10601 gchar *utf8_filename = conv_filename_to_utf8(file);
10602 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10603 compose_changed_cb(NULL, compose);
10608 g_free(utf8_filename);
10613 static void compose_attach_cb(GtkAction *action, gpointer data)
10615 Compose *compose = (Compose *)data;
10618 if (compose->redirect_filename != NULL)
10621 /* Set focus_window properly, in case we were called via popup menu,
10622 * which unsets it (via focus_out_event callback on compose window). */
10623 manage_window_focus_in(compose->window, NULL, NULL);
10625 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10628 compose_attach_from_list(compose, file_list, TRUE);
10629 g_list_free(file_list);
10633 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10635 Compose *compose = (Compose *)data;
10637 gint files_inserted = 0;
10639 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10644 for ( tmp = file_list; tmp; tmp = tmp->next) {
10645 gchar *file = (gchar *) tmp->data;
10646 gchar *filedup = g_strdup(file);
10647 gchar *shortfile = g_path_get_basename(filedup);
10648 ComposeInsertResult res;
10649 /* insert the file if the file is short or if the user confirmed that
10650 he/she wants to insert the large file */
10651 res = compose_insert_file(compose, file);
10652 if (res == COMPOSE_INSERT_READ_ERROR) {
10653 alertpanel_error(_("File '%s' could not be read."), shortfile);
10654 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10655 alertpanel_error(_("File '%s' contained invalid characters\n"
10656 "for the current encoding, insertion may be incorrect."),
10658 } else if (res == COMPOSE_INSERT_SUCCESS)
10665 g_list_free(file_list);
10669 if (files_inserted > 0 && compose->gtkaspell &&
10670 compose->gtkaspell->check_while_typing)
10671 gtkaspell_highlight_all(compose->gtkaspell);
10675 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10677 Compose *compose = (Compose *)data;
10679 compose_insert_sig(compose, FALSE);
10682 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10684 Compose *compose = (Compose *)data;
10686 compose_insert_sig(compose, TRUE);
10689 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10693 Compose *compose = (Compose *)data;
10695 gtkut_widget_get_uposition(widget, &x, &y);
10696 if (!compose->batch) {
10697 prefs_common.compose_x = x;
10698 prefs_common.compose_y = y;
10700 if (compose->sending || compose->updating)
10702 compose_close_cb(NULL, compose);
10706 void compose_close_toolbar(Compose *compose)
10708 compose_close_cb(NULL, compose);
10711 static void compose_close_cb(GtkAction *action, gpointer data)
10713 Compose *compose = (Compose *)data;
10716 if (compose->exteditor_tag != -1) {
10717 if (!compose_ext_editor_kill(compose))
10721 if (compose->modified) {
10722 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10723 if (!g_mutex_trylock(compose->mutex)) {
10724 /* we don't want to lock the mutex once it's available,
10725 * because as the only other part of compose.c locking
10726 * it is compose_close - which means once unlocked,
10727 * the compose struct will be freed */
10728 debug_print("couldn't lock mutex, probably sending\n");
10731 if (!reedit || compose->folder->stype == F_DRAFT) {
10732 val = alertpanel(_("Discard message"),
10733 _("This message has been modified. Discard it?"),
10734 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10737 val = alertpanel(_("Save changes"),
10738 _("This message has been modified. Save the latest changes?"),
10739 _("_Don't save"), _("_Save to Drafts"), GTK_STOCK_CANCEL,
10740 ALERTFOCUS_SECOND);
10742 g_mutex_unlock(compose->mutex);
10744 case G_ALERTDEFAULT:
10745 if (compose_can_autosave(compose) && !reedit)
10746 compose_remove_draft(compose);
10748 case G_ALERTALTERNATE:
10749 compose_draft(data, COMPOSE_QUIT_EDITING);
10756 compose_close(compose);
10759 static void compose_print_cb(GtkAction *action, gpointer data)
10761 Compose *compose = (Compose *) data;
10763 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10764 if (compose->targetinfo)
10765 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10768 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10770 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10771 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10772 Compose *compose = (Compose *) data;
10775 compose->out_encoding = (CharSet)value;
10778 static void compose_address_cb(GtkAction *action, gpointer data)
10780 Compose *compose = (Compose *)data;
10782 #ifndef USE_ALT_ADDRBOOK
10783 addressbook_open(compose);
10785 GError* error = NULL;
10786 addressbook_connect_signals(compose);
10787 addressbook_dbus_open(TRUE, &error);
10789 g_warning("%s", error->message);
10790 g_error_free(error);
10795 static void about_show_cb(GtkAction *action, gpointer data)
10800 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10802 Compose *compose = (Compose *)data;
10807 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10808 cm_return_if_fail(tmpl != NULL);
10810 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10812 val = alertpanel(_("Apply template"), msg,
10813 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL, ALERTFOCUS_FIRST);
10816 if (val == G_ALERTDEFAULT)
10817 compose_template_apply(compose, tmpl, TRUE);
10818 else if (val == G_ALERTALTERNATE)
10819 compose_template_apply(compose, tmpl, FALSE);
10822 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10824 Compose *compose = (Compose *)data;
10826 if (compose->exteditor_tag != -1) {
10827 debug_print("ignoring open external editor: external editor still open\n");
10830 compose_exec_ext_editor(compose);
10833 static void compose_undo_cb(GtkAction *action, gpointer data)
10835 Compose *compose = (Compose *)data;
10836 gboolean prev_autowrap = compose->autowrap;
10838 compose->autowrap = FALSE;
10839 undo_undo(compose->undostruct);
10840 compose->autowrap = prev_autowrap;
10843 static void compose_redo_cb(GtkAction *action, gpointer data)
10845 Compose *compose = (Compose *)data;
10846 gboolean prev_autowrap = compose->autowrap;
10848 compose->autowrap = FALSE;
10849 undo_redo(compose->undostruct);
10850 compose->autowrap = prev_autowrap;
10853 static void entry_cut_clipboard(GtkWidget *entry)
10855 if (GTK_IS_EDITABLE(entry))
10856 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10857 else if (GTK_IS_TEXT_VIEW(entry))
10858 gtk_text_buffer_cut_clipboard(
10859 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10860 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10864 static void entry_copy_clipboard(GtkWidget *entry)
10866 if (GTK_IS_EDITABLE(entry))
10867 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10868 else if (GTK_IS_TEXT_VIEW(entry))
10869 gtk_text_buffer_copy_clipboard(
10870 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10871 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10874 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10875 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10877 if (GTK_IS_TEXT_VIEW(entry)) {
10878 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10879 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10880 GtkTextIter start_iter, end_iter;
10882 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10884 if (contents == NULL)
10887 /* we shouldn't delete the selection when middle-click-pasting, or we
10888 * can't mid-click-paste our own selection */
10889 if (clip != GDK_SELECTION_PRIMARY) {
10890 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10891 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10894 if (insert_place == NULL) {
10895 /* if insert_place isn't specified, insert at the cursor.
10896 * used for Ctrl-V pasting */
10897 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10898 start = gtk_text_iter_get_offset(&start_iter);
10899 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10901 /* if insert_place is specified, paste here.
10902 * used for mid-click-pasting */
10903 start = gtk_text_iter_get_offset(insert_place);
10904 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10905 if (prefs_common.primary_paste_unselects)
10906 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10910 /* paste unwrapped: mark the paste so it's not wrapped later */
10911 end = start + strlen(contents);
10912 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10913 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10914 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10915 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10916 /* rewrap paragraph now (after a mid-click-paste) */
10917 mark_start = gtk_text_buffer_get_insert(buffer);
10918 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10919 gtk_text_iter_backward_char(&start_iter);
10920 compose_beautify_paragraph(compose, &start_iter, TRUE);
10922 } else if (GTK_IS_EDITABLE(entry))
10923 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10925 compose->modified = TRUE;
10928 static void entry_allsel(GtkWidget *entry)
10930 if (GTK_IS_EDITABLE(entry))
10931 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10932 else if (GTK_IS_TEXT_VIEW(entry)) {
10933 GtkTextIter startiter, enditer;
10934 GtkTextBuffer *textbuf;
10936 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10937 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10938 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10940 gtk_text_buffer_move_mark_by_name(textbuf,
10941 "selection_bound", &startiter);
10942 gtk_text_buffer_move_mark_by_name(textbuf,
10943 "insert", &enditer);
10947 static void compose_cut_cb(GtkAction *action, gpointer data)
10949 Compose *compose = (Compose *)data;
10950 if (compose->focused_editable
10951 #ifndef GENERIC_UMPC
10952 && gtk_widget_has_focus(compose->focused_editable)
10955 entry_cut_clipboard(compose->focused_editable);
10958 static void compose_copy_cb(GtkAction *action, gpointer data)
10960 Compose *compose = (Compose *)data;
10961 if (compose->focused_editable
10962 #ifndef GENERIC_UMPC
10963 && gtk_widget_has_focus(compose->focused_editable)
10966 entry_copy_clipboard(compose->focused_editable);
10969 static void compose_paste_cb(GtkAction *action, gpointer data)
10971 Compose *compose = (Compose *)data;
10972 gint prev_autowrap;
10973 GtkTextBuffer *buffer;
10975 if (compose->focused_editable
10976 #ifndef GENERIC_UMPC
10977 && gtk_widget_has_focus(compose->focused_editable)
10980 entry_paste_clipboard(compose, compose->focused_editable,
10981 prefs_common.linewrap_pastes,
10982 GDK_SELECTION_CLIPBOARD, NULL);
10987 #ifndef GENERIC_UMPC
10988 gtk_widget_has_focus(compose->text) &&
10990 compose->gtkaspell &&
10991 compose->gtkaspell->check_while_typing)
10992 gtkaspell_highlight_all(compose->gtkaspell);
10996 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10998 Compose *compose = (Compose *)data;
10999 gint wrap_quote = prefs_common.linewrap_quote;
11000 if (compose->focused_editable
11001 #ifndef GENERIC_UMPC
11002 && gtk_widget_has_focus(compose->focused_editable)
11005 /* let text_insert() (called directly or at a later time
11006 * after the gtk_editable_paste_clipboard) know that
11007 * text is to be inserted as a quotation. implemented
11008 * by using a simple refcount... */
11009 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
11010 G_OBJECT(compose->focused_editable),
11011 "paste_as_quotation"));
11012 g_object_set_data(G_OBJECT(compose->focused_editable),
11013 "paste_as_quotation",
11014 GINT_TO_POINTER(paste_as_quotation + 1));
11015 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
11016 entry_paste_clipboard(compose, compose->focused_editable,
11017 prefs_common.linewrap_pastes,
11018 GDK_SELECTION_CLIPBOARD, NULL);
11019 prefs_common.linewrap_quote = wrap_quote;
11023 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
11025 Compose *compose = (Compose *)data;
11026 gint prev_autowrap;
11027 GtkTextBuffer *buffer;
11029 if (compose->focused_editable
11030 #ifndef GENERIC_UMPC
11031 && gtk_widget_has_focus(compose->focused_editable)
11034 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
11035 GDK_SELECTION_CLIPBOARD, NULL);
11040 #ifndef GENERIC_UMPC
11041 gtk_widget_has_focus(compose->text) &&
11043 compose->gtkaspell &&
11044 compose->gtkaspell->check_while_typing)
11045 gtkaspell_highlight_all(compose->gtkaspell);
11049 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
11051 Compose *compose = (Compose *)data;
11052 gint prev_autowrap;
11053 GtkTextBuffer *buffer;
11055 if (compose->focused_editable
11056 #ifndef GENERIC_UMPC
11057 && gtk_widget_has_focus(compose->focused_editable)
11060 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
11061 GDK_SELECTION_CLIPBOARD, NULL);
11066 #ifndef GENERIC_UMPC
11067 gtk_widget_has_focus(compose->text) &&
11069 compose->gtkaspell &&
11070 compose->gtkaspell->check_while_typing)
11071 gtkaspell_highlight_all(compose->gtkaspell);
11075 static void compose_allsel_cb(GtkAction *action, gpointer data)
11077 Compose *compose = (Compose *)data;
11078 if (compose->focused_editable
11079 #ifndef GENERIC_UMPC
11080 && gtk_widget_has_focus(compose->focused_editable)
11083 entry_allsel(compose->focused_editable);
11086 static void textview_move_beginning_of_line (GtkTextView *text)
11088 GtkTextBuffer *buffer;
11092 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11094 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11095 mark = gtk_text_buffer_get_insert(buffer);
11096 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11097 gtk_text_iter_set_line_offset(&ins, 0);
11098 gtk_text_buffer_place_cursor(buffer, &ins);
11101 static void textview_move_forward_character (GtkTextView *text)
11103 GtkTextBuffer *buffer;
11107 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11109 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11110 mark = gtk_text_buffer_get_insert(buffer);
11111 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11112 if (gtk_text_iter_forward_cursor_position(&ins))
11113 gtk_text_buffer_place_cursor(buffer, &ins);
11116 static void textview_move_backward_character (GtkTextView *text)
11118 GtkTextBuffer *buffer;
11122 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11124 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11125 mark = gtk_text_buffer_get_insert(buffer);
11126 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11127 if (gtk_text_iter_backward_cursor_position(&ins))
11128 gtk_text_buffer_place_cursor(buffer, &ins);
11131 static void textview_move_forward_word (GtkTextView *text)
11133 GtkTextBuffer *buffer;
11138 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11140 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11141 mark = gtk_text_buffer_get_insert(buffer);
11142 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11143 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
11144 if (gtk_text_iter_forward_word_ends(&ins, count)) {
11145 gtk_text_iter_backward_word_start(&ins);
11146 gtk_text_buffer_place_cursor(buffer, &ins);
11150 static void textview_move_backward_word (GtkTextView *text)
11152 GtkTextBuffer *buffer;
11156 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11158 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11159 mark = gtk_text_buffer_get_insert(buffer);
11160 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11161 if (gtk_text_iter_backward_word_starts(&ins, 1))
11162 gtk_text_buffer_place_cursor(buffer, &ins);
11165 static void textview_move_end_of_line (GtkTextView *text)
11167 GtkTextBuffer *buffer;
11171 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11173 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11174 mark = gtk_text_buffer_get_insert(buffer);
11175 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11176 if (gtk_text_iter_forward_to_line_end(&ins))
11177 gtk_text_buffer_place_cursor(buffer, &ins);
11180 static void textview_move_next_line (GtkTextView *text)
11182 GtkTextBuffer *buffer;
11187 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11189 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11190 mark = gtk_text_buffer_get_insert(buffer);
11191 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11192 offset = gtk_text_iter_get_line_offset(&ins);
11193 if (gtk_text_iter_forward_line(&ins)) {
11194 gtk_text_iter_set_line_offset(&ins, offset);
11195 gtk_text_buffer_place_cursor(buffer, &ins);
11199 static void textview_move_previous_line (GtkTextView *text)
11201 GtkTextBuffer *buffer;
11206 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11208 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11209 mark = gtk_text_buffer_get_insert(buffer);
11210 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11211 offset = gtk_text_iter_get_line_offset(&ins);
11212 if (gtk_text_iter_backward_line(&ins)) {
11213 gtk_text_iter_set_line_offset(&ins, offset);
11214 gtk_text_buffer_place_cursor(buffer, &ins);
11218 static void textview_delete_forward_character (GtkTextView *text)
11220 GtkTextBuffer *buffer;
11222 GtkTextIter ins, end_iter;
11224 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11226 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11227 mark = gtk_text_buffer_get_insert(buffer);
11228 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11230 if (gtk_text_iter_forward_char(&end_iter)) {
11231 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11235 static void textview_delete_backward_character (GtkTextView *text)
11237 GtkTextBuffer *buffer;
11239 GtkTextIter ins, end_iter;
11241 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11243 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11244 mark = gtk_text_buffer_get_insert(buffer);
11245 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11247 if (gtk_text_iter_backward_char(&end_iter)) {
11248 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11252 static void textview_delete_forward_word (GtkTextView *text)
11254 GtkTextBuffer *buffer;
11256 GtkTextIter ins, end_iter;
11258 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11260 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11261 mark = gtk_text_buffer_get_insert(buffer);
11262 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11264 if (gtk_text_iter_forward_word_end(&end_iter)) {
11265 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11269 static void textview_delete_backward_word (GtkTextView *text)
11271 GtkTextBuffer *buffer;
11273 GtkTextIter ins, end_iter;
11275 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11277 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11278 mark = gtk_text_buffer_get_insert(buffer);
11279 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11281 if (gtk_text_iter_backward_word_start(&end_iter)) {
11282 gtk_text_buffer_delete(buffer, &end_iter, &ins);
11286 static void textview_delete_line (GtkTextView *text)
11288 GtkTextBuffer *buffer;
11290 GtkTextIter ins, start_iter, end_iter;
11292 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11294 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11295 mark = gtk_text_buffer_get_insert(buffer);
11296 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11299 gtk_text_iter_set_line_offset(&start_iter, 0);
11302 if (gtk_text_iter_ends_line(&end_iter)){
11303 if (!gtk_text_iter_forward_char(&end_iter))
11304 gtk_text_iter_backward_char(&start_iter);
11307 gtk_text_iter_forward_to_line_end(&end_iter);
11308 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11311 static void textview_delete_to_line_end (GtkTextView *text)
11313 GtkTextBuffer *buffer;
11315 GtkTextIter ins, end_iter;
11317 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11319 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11320 mark = gtk_text_buffer_get_insert(buffer);
11321 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11323 if (gtk_text_iter_ends_line(&end_iter))
11324 gtk_text_iter_forward_char(&end_iter);
11326 gtk_text_iter_forward_to_line_end(&end_iter);
11327 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11330 #define DO_ACTION(name, act) { \
11331 if(!strcmp(name, a_name)) { \
11335 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11337 const gchar *a_name = gtk_action_get_name(action);
11338 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11339 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11340 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11341 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11342 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11343 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11344 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11345 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11346 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11347 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11348 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11349 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11350 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11351 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11352 return COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11355 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11357 Compose *compose = (Compose *)data;
11358 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11359 ComposeCallAdvancedAction action = COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED;
11361 action = compose_call_advanced_action_from_path(gaction);
11364 void (*do_action) (GtkTextView *text);
11365 } action_table[] = {
11366 {textview_move_beginning_of_line},
11367 {textview_move_forward_character},
11368 {textview_move_backward_character},
11369 {textview_move_forward_word},
11370 {textview_move_backward_word},
11371 {textview_move_end_of_line},
11372 {textview_move_next_line},
11373 {textview_move_previous_line},
11374 {textview_delete_forward_character},
11375 {textview_delete_backward_character},
11376 {textview_delete_forward_word},
11377 {textview_delete_backward_word},
11378 {textview_delete_line},
11379 {textview_delete_to_line_end}
11382 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11384 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11385 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11386 if (action_table[action].do_action)
11387 action_table[action].do_action(text);
11389 g_warning("not implemented yet");
11393 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11395 GtkAllocation allocation;
11399 if (GTK_IS_EDITABLE(widget)) {
11400 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11401 gtk_editable_set_position(GTK_EDITABLE(widget),
11404 if ((parent = gtk_widget_get_parent(widget))
11405 && (parent = gtk_widget_get_parent(parent))
11406 && (parent = gtk_widget_get_parent(parent))) {
11407 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11408 gtk_widget_get_allocation(widget, &allocation);
11409 gint y = allocation.y;
11410 gint height = allocation.height;
11411 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11412 (GTK_SCROLLED_WINDOW(parent));
11414 gfloat value = gtk_adjustment_get_value(shown);
11415 gfloat upper = gtk_adjustment_get_upper(shown);
11416 gfloat page_size = gtk_adjustment_get_page_size(shown);
11417 if (y < (int)value) {
11418 gtk_adjustment_set_value(shown, y - 1);
11420 if ((y + height) > ((int)value + (int)page_size)) {
11421 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11422 gtk_adjustment_set_value(shown,
11423 y + height - (int)page_size - 1);
11425 gtk_adjustment_set_value(shown,
11426 (int)upper - (int)page_size - 1);
11433 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11434 compose->focused_editable = widget;
11436 #ifdef GENERIC_UMPC
11437 if (GTK_IS_TEXT_VIEW(widget)
11438 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11439 g_object_ref(compose->notebook);
11440 g_object_ref(compose->edit_vbox);
11441 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11442 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11443 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11444 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11445 g_object_unref(compose->notebook);
11446 g_object_unref(compose->edit_vbox);
11447 g_signal_handlers_block_by_func(G_OBJECT(widget),
11448 G_CALLBACK(compose_grab_focus_cb),
11450 gtk_widget_grab_focus(widget);
11451 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11452 G_CALLBACK(compose_grab_focus_cb),
11454 } else if (!GTK_IS_TEXT_VIEW(widget)
11455 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11456 g_object_ref(compose->notebook);
11457 g_object_ref(compose->edit_vbox);
11458 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11459 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11460 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11461 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11462 g_object_unref(compose->notebook);
11463 g_object_unref(compose->edit_vbox);
11464 g_signal_handlers_block_by_func(G_OBJECT(widget),
11465 G_CALLBACK(compose_grab_focus_cb),
11467 gtk_widget_grab_focus(widget);
11468 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11469 G_CALLBACK(compose_grab_focus_cb),
11475 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11477 compose->modified = TRUE;
11478 /* compose_beautify_paragraph(compose, NULL, TRUE); */
11479 #ifndef GENERIC_UMPC
11480 compose_set_title(compose);
11484 static void compose_wrap_cb(GtkAction *action, gpointer data)
11486 Compose *compose = (Compose *)data;
11487 compose_beautify_paragraph(compose, NULL, TRUE);
11490 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11492 Compose *compose = (Compose *)data;
11493 compose_wrap_all_full(compose, TRUE);
11496 static void compose_find_cb(GtkAction *action, gpointer data)
11498 Compose *compose = (Compose *)data;
11500 message_search_compose(compose);
11503 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11506 Compose *compose = (Compose *)data;
11507 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11508 if (compose->autowrap)
11509 compose_wrap_all_full(compose, TRUE);
11510 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11513 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11516 Compose *compose = (Compose *)data;
11517 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11520 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11522 Compose *compose = (Compose *)data;
11524 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11525 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_sign_btn), compose->use_signing);
11528 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11530 Compose *compose = (Compose *)data;
11532 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11533 gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(compose->toolbar->privacy_encrypt_btn), compose->use_encryption);
11536 static void compose_activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11538 g_free(compose->privacy_system);
11539 g_free(compose->encdata);
11541 compose->privacy_system = g_strdup(account->default_privacy_system);
11542 compose_update_privacy_system_menu_item(compose, warn);
11545 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item)
11547 if (folder_item != NULL) {
11548 if (folder_item->prefs->always_sign != SIGN_OR_ENCRYPT_DEFAULT &&
11549 privacy_system_can_sign(compose->privacy_system)) {
11550 compose_use_signing(compose,
11551 (folder_item->prefs->always_sign == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11553 if (folder_item->prefs->always_encrypt != SIGN_OR_ENCRYPT_DEFAULT &&
11554 privacy_system_can_encrypt(compose->privacy_system)) {
11555 compose_use_encryption(compose,
11556 (folder_item->prefs->always_encrypt == SIGN_OR_ENCRYPT_ALWAYS) ? TRUE : FALSE);
11561 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11563 Compose *compose = (Compose *)data;
11565 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11566 gtk_widget_show(compose->ruler_hbox);
11567 prefs_common.show_ruler = TRUE;
11569 gtk_widget_hide(compose->ruler_hbox);
11570 gtk_widget_queue_resize(compose->edit_vbox);
11571 prefs_common.show_ruler = FALSE;
11575 static void compose_attach_drag_received_cb (GtkWidget *widget,
11576 GdkDragContext *context,
11579 GtkSelectionData *data,
11582 gpointer user_data)
11584 Compose *compose = (Compose *)user_data;
11588 type = gtk_selection_data_get_data_type(data);
11589 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11590 && gtk_drag_get_source_widget(context) !=
11591 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11592 list = uri_list_extract_filenames(
11593 (const gchar *)gtk_selection_data_get_data(data));
11594 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11595 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11596 compose_attach_append
11597 (compose, (const gchar *)tmp->data,
11598 utf8_filename, NULL, NULL);
11599 g_free(utf8_filename);
11602 compose_changed_cb(NULL, compose);
11603 list_free_strings_full(list);
11604 } else if (gtk_drag_get_source_widget(context)
11605 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11606 /* comes from our summaryview */
11607 SummaryView * summaryview = NULL;
11608 GSList * list = NULL, *cur = NULL;
11610 if (mainwindow_get_mainwindow())
11611 summaryview = mainwindow_get_mainwindow()->summaryview;
11614 list = summary_get_selected_msg_list(summaryview);
11616 for (cur = list; cur; cur = cur->next) {
11617 MsgInfo *msginfo = (MsgInfo *)cur->data;
11618 gchar *file = NULL;
11620 file = procmsg_get_message_file_full(msginfo,
11623 compose_attach_append(compose, (const gchar *)file,
11624 (const gchar *)file, "message/rfc822", NULL);
11628 g_slist_free(list);
11632 static gboolean compose_drag_drop(GtkWidget *widget,
11633 GdkDragContext *drag_context,
11635 guint time, gpointer user_data)
11637 /* not handling this signal makes compose_insert_drag_received_cb
11642 static gboolean completion_set_focus_to_subject
11643 (GtkWidget *widget,
11644 GdkEventKey *event,
11647 cm_return_val_if_fail(compose != NULL, FALSE);
11649 /* make backtab move to subject field */
11650 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11651 gtk_widget_grab_focus(compose->subject_entry);
11657 static void compose_insert_drag_received_cb (GtkWidget *widget,
11658 GdkDragContext *drag_context,
11661 GtkSelectionData *data,
11664 gpointer user_data)
11666 Compose *compose = (Compose *)user_data;
11672 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11674 type = gtk_selection_data_get_data_type(data);
11675 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11676 AlertValue val = G_ALERTDEFAULT;
11677 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11679 list = uri_list_extract_filenames(ddata);
11680 num_files = g_list_length(list);
11681 if (list == NULL && strstr(ddata, "://")) {
11682 /* Assume a list of no files, and data has ://, is a remote link */
11683 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11684 gchar *tmpfile = get_tmp_file();
11685 str_write_to_file(tmpdata, tmpfile, TRUE);
11687 compose_insert_file(compose, tmpfile);
11688 claws_unlink(tmpfile);
11690 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11691 compose_beautify_paragraph(compose, NULL, TRUE);
11694 switch (prefs_common.compose_dnd_mode) {
11695 case COMPOSE_DND_ASK:
11696 msg = g_strdup_printf(
11698 "Do you want to insert the contents of the file "
11699 "into the message body, or attach it to the email?",
11700 "Do you want to insert the contents of the %d files "
11701 "into the message body, or attach them to the email?",
11704 val = alertpanel_full(_("Insert or attach?"), msg,
11705 GTK_STOCK_CANCEL, _("_Insert"), _("_Attach"),
11707 TRUE, NULL, ALERT_QUESTION);
11710 case COMPOSE_DND_INSERT:
11711 val = G_ALERTALTERNATE;
11713 case COMPOSE_DND_ATTACH:
11714 val = G_ALERTOTHER;
11717 /* unexpected case */
11718 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11721 if (val & G_ALERTDISABLE) {
11722 val &= ~G_ALERTDISABLE;
11723 /* remember what action to perform by default, only if we don't click Cancel */
11724 if (val == G_ALERTALTERNATE)
11725 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11726 else if (val == G_ALERTOTHER)
11727 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11730 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11731 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11732 list_free_strings_full(list);
11734 } else if (val == G_ALERTOTHER) {
11735 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11736 list_free_strings_full(list);
11740 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11741 compose_insert_file(compose, (const gchar *)tmp->data);
11743 list_free_strings_full(list);
11744 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11749 static void compose_header_drag_received_cb (GtkWidget *widget,
11750 GdkDragContext *drag_context,
11753 GtkSelectionData *data,
11756 gpointer user_data)
11758 GtkEditable *entry = (GtkEditable *)user_data;
11759 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11761 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11764 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11765 gchar *decoded=g_new(gchar, strlen(email));
11768 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11769 gtk_editable_delete_text(entry, 0, -1);
11770 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11771 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11775 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11778 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11780 Compose *compose = (Compose *)data;
11782 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11783 compose->return_receipt = TRUE;
11785 compose->return_receipt = FALSE;
11788 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11790 Compose *compose = (Compose *)data;
11792 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11793 compose->remove_references = TRUE;
11795 compose->remove_references = FALSE;
11798 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11799 ComposeHeaderEntry *headerentry)
11801 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11802 gtk_widget_modify_base(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11803 gtk_widget_modify_text(GTK_WIDGET(headerentry->entry), GTK_STATE_NORMAL, NULL);
11807 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11808 GdkEventKey *event,
11809 ComposeHeaderEntry *headerentry)
11811 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11812 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11813 !(event->state & GDK_MODIFIER_MASK) &&
11814 (event->keyval == GDK_KEY_BackSpace) &&
11815 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11816 gtk_container_remove
11817 (GTK_CONTAINER(headerentry->compose->header_table),
11818 headerentry->combo);
11819 gtk_container_remove
11820 (GTK_CONTAINER(headerentry->compose->header_table),
11821 headerentry->entry);
11822 headerentry->compose->header_list =
11823 g_slist_remove(headerentry->compose->header_list,
11825 g_free(headerentry);
11826 } else if (event->keyval == GDK_KEY_Tab) {
11827 if (headerentry->compose->header_last == headerentry) {
11828 /* Override default next focus, and give it to subject_entry
11829 * instead of notebook tabs
11831 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11832 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11839 static gboolean scroll_postpone(gpointer data)
11841 Compose *compose = (Compose *)data;
11843 if (compose->batch)
11846 GTK_EVENTS_FLUSH();
11847 compose_show_first_last_header(compose, FALSE);
11851 static void compose_headerentry_changed_cb(GtkWidget *entry,
11852 ComposeHeaderEntry *headerentry)
11854 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11855 compose_create_header_entry(headerentry->compose);
11856 g_signal_handlers_disconnect_matched
11857 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11858 0, 0, NULL, NULL, headerentry);
11860 if (!headerentry->compose->batch)
11861 g_timeout_add(0, scroll_postpone, headerentry->compose);
11865 static gboolean compose_defer_auto_save_draft(Compose *compose)
11867 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11868 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11872 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11874 GtkAdjustment *vadj;
11876 cm_return_if_fail(compose);
11881 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11882 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11883 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11884 gtk_widget_get_parent(compose->header_table)));
11885 gtk_adjustment_set_value(vadj, (show_first ?
11886 gtk_adjustment_get_lower(vadj) :
11887 (gtk_adjustment_get_upper(vadj) -
11888 gtk_adjustment_get_page_size(vadj))));
11889 gtk_adjustment_changed(vadj);
11892 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11893 const gchar *text, gint len, Compose *compose)
11895 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11896 (G_OBJECT(compose->text), "paste_as_quotation"));
11899 cm_return_if_fail(text != NULL);
11901 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11902 G_CALLBACK(text_inserted),
11904 if (paste_as_quotation) {
11906 const gchar *qmark;
11908 GtkTextIter start_iter;
11911 len = strlen(text);
11913 new_text = g_strndup(text, len);
11915 qmark = compose_quote_char_from_context(compose);
11917 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11918 gtk_text_buffer_place_cursor(buffer, iter);
11920 pos = gtk_text_iter_get_offset(iter);
11922 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11923 _("Quote format error at line %d."));
11924 quote_fmt_reset_vartable();
11926 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11927 GINT_TO_POINTER(paste_as_quotation - 1));
11929 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11930 gtk_text_buffer_place_cursor(buffer, iter);
11931 gtk_text_buffer_delete_mark(buffer, mark);
11933 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11934 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11935 compose_beautify_paragraph(compose, &start_iter, FALSE);
11936 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11937 gtk_text_buffer_delete_mark(buffer, mark);
11939 if (strcmp(text, "\n") || compose->automatic_break
11940 || gtk_text_iter_starts_line(iter)) {
11941 GtkTextIter before_ins;
11942 gtk_text_buffer_insert(buffer, iter, text, len);
11943 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11944 before_ins = *iter;
11945 gtk_text_iter_backward_chars(&before_ins, len);
11946 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11949 /* check if the preceding is just whitespace or quote */
11950 GtkTextIter start_line;
11951 gchar *tmp = NULL, *quote = NULL;
11952 gint quote_len = 0, is_normal = 0;
11953 start_line = *iter;
11954 gtk_text_iter_set_line_offset(&start_line, 0);
11955 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11958 if (*tmp == '\0') {
11961 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11969 gtk_text_buffer_insert(buffer, iter, text, len);
11971 gtk_text_buffer_insert_with_tags_by_name(buffer,
11972 iter, text, len, "no_join", NULL);
11977 if (!paste_as_quotation) {
11978 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11979 compose_beautify_paragraph(compose, iter, FALSE);
11980 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11981 gtk_text_buffer_delete_mark(buffer, mark);
11984 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11985 G_CALLBACK(text_inserted),
11987 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11989 if (compose_can_autosave(compose) &&
11990 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11991 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11992 compose->draft_timeout_tag = g_timeout_add
11993 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11997 static void compose_check_all(GtkAction *action, gpointer data)
11999 Compose *compose = (Compose *)data;
12000 if (!compose->gtkaspell)
12003 if (gtk_widget_has_focus(compose->subject_entry))
12004 claws_spell_entry_check_all(
12005 CLAWS_SPELL_ENTRY(compose->subject_entry));
12007 gtkaspell_check_all(compose->gtkaspell);
12010 static void compose_highlight_all(GtkAction *action, gpointer data)
12012 Compose *compose = (Compose *)data;
12013 if (compose->gtkaspell) {
12014 claws_spell_entry_recheck_all(
12015 CLAWS_SPELL_ENTRY(compose->subject_entry));
12016 gtkaspell_highlight_all(compose->gtkaspell);
12020 static void compose_check_backwards(GtkAction *action, gpointer data)
12022 Compose *compose = (Compose *)data;
12023 if (!compose->gtkaspell) {
12024 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12028 if (gtk_widget_has_focus(compose->subject_entry))
12029 claws_spell_entry_check_backwards(
12030 CLAWS_SPELL_ENTRY(compose->subject_entry));
12032 gtkaspell_check_backwards(compose->gtkaspell);
12035 static void compose_check_forwards_go(GtkAction *action, gpointer data)
12037 Compose *compose = (Compose *)data;
12038 if (!compose->gtkaspell) {
12039 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
12043 if (gtk_widget_has_focus(compose->subject_entry))
12044 claws_spell_entry_check_forwards_go(
12045 CLAWS_SPELL_ENTRY(compose->subject_entry));
12047 gtkaspell_check_forwards_go(compose->gtkaspell);
12052 *\brief Guess originating forward account from MsgInfo and several
12053 * "common preference" settings. Return NULL if no guess.
12055 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
12057 PrefsAccount *account = NULL;
12059 cm_return_val_if_fail(msginfo, NULL);
12060 cm_return_val_if_fail(msginfo->folder, NULL);
12061 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
12063 if (msginfo->folder->prefs->enable_default_account)
12064 account = account_find_from_id(msginfo->folder->prefs->default_account);
12066 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
12068 Xstrdup_a(to, msginfo->to, return NULL);
12069 extract_address(to);
12070 account = account_find_from_address(to, FALSE);
12073 if (!account && prefs_common.forward_account_autosel) {
12075 if (!procheader_get_header_from_msginfo
12076 (msginfo, &cc, "Cc:")) {
12077 gchar *buf = cc + strlen("Cc:");
12078 extract_address(buf);
12079 account = account_find_from_address(buf, FALSE);
12084 if (!account && prefs_common.forward_account_autosel) {
12085 gchar *deliveredto = NULL;
12086 if (!procheader_get_header_from_msginfo
12087 (msginfo, &deliveredto, "Delivered-To:")) {
12088 gchar *buf = deliveredto + strlen("Delivered-To:");
12089 extract_address(buf);
12090 account = account_find_from_address(buf, FALSE);
12091 g_free(deliveredto);
12096 account = msginfo->folder->folder->account;
12101 gboolean compose_close(Compose *compose)
12105 cm_return_val_if_fail(compose, FALSE);
12107 if (!g_mutex_trylock(compose->mutex)) {
12108 /* we have to wait for the (possibly deferred by auto-save)
12109 * drafting to be done, before destroying the compose under
12111 debug_print("waiting for drafting to finish...\n");
12112 compose_allow_user_actions(compose, FALSE);
12113 if (compose->close_timeout_tag == 0) {
12114 compose->close_timeout_tag =
12115 g_timeout_add (500, (GSourceFunc) compose_close,
12121 if (compose->draft_timeout_tag >= 0) {
12122 g_source_remove(compose->draft_timeout_tag);
12123 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
12126 gtkut_widget_get_uposition(compose->window, &x, &y);
12127 if (!compose->batch) {
12128 prefs_common.compose_x = x;
12129 prefs_common.compose_y = y;
12131 g_mutex_unlock(compose->mutex);
12132 compose_destroy(compose);
12137 * Add entry field for each address in list.
12138 * \param compose E-Mail composition object.
12139 * \param listAddress List of (formatted) E-Mail addresses.
12141 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
12144 node = listAddress;
12146 addr = ( gchar * ) node->data;
12147 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
12148 node = g_list_next( node );
12152 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
12153 guint action, gboolean opening_multiple)
12155 gchar *body = NULL;
12156 GSList *new_msglist = NULL;
12157 MsgInfo *tmp_msginfo = NULL;
12158 gboolean originally_enc = FALSE;
12159 gboolean originally_sig = FALSE;
12160 Compose *compose = NULL;
12161 gchar *s_system = NULL;
12163 cm_return_if_fail(msginfo_list != NULL);
12165 if (g_slist_length(msginfo_list) == 1 && !opening_multiple && msgview != NULL) {
12166 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
12167 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
12169 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
12170 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
12171 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
12172 orig_msginfo, mimeinfo);
12173 if (tmp_msginfo != NULL) {
12174 new_msglist = g_slist_append(NULL, tmp_msginfo);
12176 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
12177 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
12178 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
12180 tmp_msginfo->folder = orig_msginfo->folder;
12181 tmp_msginfo->msgnum = orig_msginfo->msgnum;
12182 if (orig_msginfo->tags) {
12183 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
12184 tmp_msginfo->folder->tags_dirty = TRUE;
12190 if (!opening_multiple && msgview != NULL)
12191 body = messageview_get_selection(msgview);
12194 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
12195 procmsg_msginfo_free(&tmp_msginfo);
12196 g_slist_free(new_msglist);
12198 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
12200 if (compose && originally_enc) {
12201 compose_force_encryption(compose, compose->account, FALSE, s_system);
12204 if (compose && originally_sig && compose->account->default_sign_reply) {
12205 compose_force_signing(compose, compose->account, s_system);
12209 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12212 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
12215 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
12216 && msginfo_list != NULL
12217 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
12218 GSList *cur = msginfo_list;
12219 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
12220 "messages. Opening the windows "
12221 "could take some time. Do you "
12222 "want to continue?"),
12223 g_slist_length(msginfo_list));
12224 if (g_slist_length(msginfo_list) > 9
12225 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_YES, NULL,
12226 ALERTFOCUS_SECOND) != G_ALERTALTERNATE) {
12231 /* We'll open multiple compose windows */
12232 /* let the WM place the next windows */
12233 compose_force_window_origin = FALSE;
12234 for (; cur; cur = cur->next) {
12236 tmplist.data = cur->data;
12237 tmplist.next = NULL;
12238 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
12240 compose_force_window_origin = TRUE;
12242 /* forwarding multiple mails as attachments is done via a
12243 * single compose window */
12244 if (msginfo_list != NULL) {
12245 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
12246 } else if (msgview != NULL) {
12248 tmplist.data = msgview->msginfo;
12249 tmplist.next = NULL;
12250 compose_reply_from_messageview_real(msgview, &tmplist, action, FALSE);
12252 debug_print("Nothing to reply to\n");
12257 void compose_check_for_email_account(Compose *compose)
12259 PrefsAccount *ac = NULL, *curr = NULL;
12265 if (compose->account && compose->account->protocol == A_NNTP) {
12266 ac = account_get_cur_account();
12267 if (ac->protocol == A_NNTP) {
12268 list = account_get_list();
12270 for( ; list != NULL ; list = g_list_next(list)) {
12271 curr = (PrefsAccount *) list->data;
12272 if (curr->protocol != A_NNTP) {
12278 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
12283 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
12284 const gchar *address)
12286 GSList *msginfo_list = NULL;
12287 gchar *body = messageview_get_selection(msgview);
12290 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
12292 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
12293 compose_check_for_email_account(compose);
12294 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
12295 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
12296 compose_reply_set_subject(compose, msginfo);
12299 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
12302 void compose_set_position(Compose *compose, gint pos)
12304 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12306 gtkut_text_view_set_position(text, pos);
12309 gboolean compose_search_string(Compose *compose,
12310 const gchar *str, gboolean case_sens)
12312 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12314 return gtkut_text_view_search_string(text, str, case_sens);
12317 gboolean compose_search_string_backward(Compose *compose,
12318 const gchar *str, gboolean case_sens)
12320 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12322 return gtkut_text_view_search_string_backward(text, str, case_sens);
12325 /* allocate a msginfo structure and populate its data from a compose data structure */
12326 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12328 MsgInfo *newmsginfo;
12330 gchar date[RFC822_DATE_BUFFSIZE];
12332 cm_return_val_if_fail( compose != NULL, NULL );
12334 newmsginfo = procmsg_msginfo_new();
12337 get_rfc822_date(date, sizeof(date));
12338 newmsginfo->date = g_strdup(date);
12341 if (compose->from_name) {
12342 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12343 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12347 if (compose->subject_entry)
12348 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12350 /* to, cc, reply-to, newsgroups */
12351 for (list = compose->header_list; list; list = list->next) {
12352 gchar *header = gtk_editable_get_chars(
12354 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12355 gchar *entry = gtk_editable_get_chars(
12356 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12358 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12359 if ( newmsginfo->to == NULL ) {
12360 newmsginfo->to = g_strdup(entry);
12361 } else if (entry && *entry) {
12362 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12363 g_free(newmsginfo->to);
12364 newmsginfo->to = tmp;
12367 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12368 if ( newmsginfo->cc == NULL ) {
12369 newmsginfo->cc = g_strdup(entry);
12370 } else if (entry && *entry) {
12371 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12372 g_free(newmsginfo->cc);
12373 newmsginfo->cc = tmp;
12376 if ( strcasecmp(header,
12377 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12378 if ( newmsginfo->newsgroups == NULL ) {
12379 newmsginfo->newsgroups = g_strdup(entry);
12380 } else if (entry && *entry) {
12381 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12382 g_free(newmsginfo->newsgroups);
12383 newmsginfo->newsgroups = tmp;
12391 /* other data is unset */
12397 /* update compose's dictionaries from folder dict settings */
12398 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12399 FolderItem *folder_item)
12401 cm_return_if_fail(compose != NULL);
12403 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12404 FolderItemPrefs *prefs = folder_item->prefs;
12406 if (prefs->enable_default_dictionary)
12407 gtkaspell_change_dict(compose->gtkaspell,
12408 prefs->default_dictionary, FALSE);
12409 if (folder_item->prefs->enable_default_alt_dictionary)
12410 gtkaspell_change_alt_dict(compose->gtkaspell,
12411 prefs->default_alt_dictionary);
12412 if (prefs->enable_default_dictionary
12413 || prefs->enable_default_alt_dictionary)
12414 compose_spell_menu_changed(compose);
12419 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12421 Compose *compose = (Compose *)data;
12423 cm_return_if_fail(compose != NULL);
12425 gtk_widget_grab_focus(compose->text);
12428 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12430 gtk_combo_box_popup(GTK_COMBO_BOX(data));