2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2022 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>
34 #ifdef GDK_WINDOWING_X11
38 #include <pango/pango-break.h>
43 #include <sys/types.h>
48 # include <sys/wait.h>
52 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
59 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
66 #include "mainwindow.h"
68 #ifndef USE_ALT_ADDRBOOK
69 #include "addressbook.h"
71 #include "addressbook-dbus.h"
72 #include "addressadd.h"
74 #include "folderview.h"
77 #include "stock_pixmap.h"
78 #include "send_message.h"
81 #include "customheader.h"
82 #include "prefs_common.h"
83 #include "prefs_account.h"
87 #include "procheader.h"
89 #include "statusbar.h"
91 #include "quoted-printable.h"
95 #include "gtkshruler.h"
97 #include "alertpanel.h"
98 #include "manage_window.h"
100 #include "folder_item_prefs.h"
101 #include "addr_compl.h"
102 #include "quote_fmt.h"
104 #include "foldersel.h"
107 #include "message_search.h"
108 #include "combobox.h"
112 #include "autofaces.h"
113 #include "spell_entry.h"
115 #include "file-utils.h"
118 #include "password.h"
119 #include "ldapserver.h"
133 #define N_ATTACH_COLS (N_COL_COLUMNS)
137 COMPOSE_CALL_ADVANCED_ACTION_UNDEFINED = -1,
138 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE = 0,
139 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
140 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
141 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
142 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
146 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
147 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
148 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
149 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
150 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
152 } ComposeCallAdvancedAction;
156 PRIORITY_HIGHEST = 1,
165 COMPOSE_INSERT_SUCCESS,
166 COMPOSE_INSERT_READ_ERROR,
167 COMPOSE_INSERT_INVALID_CHARACTER,
168 COMPOSE_INSERT_NO_FILE
169 } ComposeInsertResult;
173 COMPOSE_WRITE_FOR_SEND,
174 COMPOSE_WRITE_FOR_STORE
179 COMPOSE_QUOTE_FORCED,
186 SUBJECT_FIELD_PRESENT,
191 #define B64_LINE_SIZE 57
192 #define B64_BUFFSIZE 77
194 #define MAX_REFERENCES_LEN 999
196 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
197 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
199 #define COMPOSE_PRIVACY_WARNING() { \
200 alertpanel_error(_("You have opted to sign and/or encrypt this " \
201 "message but have not selected a privacy system.\n\n" \
202 "Signing and encrypting have been disabled for this " \
207 #define INVALID_PID INVALID_HANDLE_VALUE
209 #define INVALID_PID -1
212 static GdkRGBA default_header_bgcolor =
215 static GdkRGBA default_header_color =
218 static GList *compose_list = NULL;
219 static GSList *extra_headers = NULL;
221 static Compose *compose_generic_new (PrefsAccount *account,
225 GList *listAddress );
227 static Compose *compose_create (PrefsAccount *account,
232 static void compose_entry_indicate (Compose *compose,
233 const gchar *address);
234 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
235 ComposeQuoteMode quote_mode,
239 static Compose *compose_forward_multiple (PrefsAccount *account,
240 GSList *msginfo_list);
241 static Compose *compose_reply (MsgInfo *msginfo,
242 ComposeQuoteMode quote_mode,
247 static Compose *compose_reply_mode (ComposeMode mode,
248 GSList *msginfo_list,
250 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
251 static void compose_update_privacy_systems_menu(Compose *compose);
253 static GtkWidget *compose_account_option_menu_create
255 static void compose_set_out_encoding (Compose *compose);
256 static void compose_set_template_menu (Compose *compose);
257 static void compose_destroy (Compose *compose);
259 static MailField compose_entries_set (Compose *compose,
261 ComposeEntryType to_type);
262 static gint compose_parse_header (Compose *compose,
264 static gint compose_parse_manual_headers (Compose *compose,
266 HeaderEntry *entries);
267 static gchar *compose_parse_references (const gchar *ref,
270 static gchar *compose_quote_fmt (Compose *compose,
276 gboolean need_unescape,
277 const gchar *err_msg);
279 static void compose_reply_set_entry (Compose *compose,
285 followup_and_reply_to);
286 static void compose_reedit_set_entry (Compose *compose,
289 static void compose_insert_sig (Compose *compose,
291 static ComposeInsertResult compose_insert_file (Compose *compose,
294 static gboolean compose_attach_append (Compose *compose,
297 const gchar *content_type,
298 const gchar *charset);
299 static void compose_attach_parts (Compose *compose,
302 static gboolean compose_beautify_paragraph (Compose *compose,
303 GtkTextIter *par_iter,
305 static void compose_wrap_all (Compose *compose);
306 static void compose_wrap_all_full (Compose *compose,
309 static void compose_set_title (Compose *compose);
310 static void compose_select_account (Compose *compose,
311 PrefsAccount *account,
314 static PrefsAccount *compose_current_mail_account(void);
315 /* static gint compose_send (Compose *compose); */
316 static gboolean compose_check_for_valid_recipient
318 static gboolean compose_check_entries (Compose *compose,
319 gboolean check_everything);
320 static gint compose_write_to_file (Compose *compose,
323 gboolean attach_parts);
324 static gint compose_write_body_to_file (Compose *compose,
326 static gint compose_remove_reedit_target (Compose *compose,
328 static void compose_remove_draft (Compose *compose);
329 static ComposeQueueResult compose_queue_sub (Compose *compose,
333 gboolean perform_checks,
334 gboolean remove_reedit_target);
335 static int compose_add_attachments (Compose *compose,
337 static gchar *compose_get_header (Compose *compose);
338 static gchar *compose_get_manual_headers_info (Compose *compose);
340 static void compose_convert_header (Compose *compose,
345 gboolean addr_field);
347 static void compose_attach_info_free (AttachInfo *ainfo);
348 static void compose_attach_remove_selected (GtkAction *action,
351 static void compose_template_apply (Compose *compose,
354 static void compose_attach_property (GtkAction *action,
356 static void compose_attach_property_create (gboolean *cancelled);
357 static void attach_property_ok (GtkWidget *widget,
358 gboolean *cancelled);
359 static void attach_property_cancel (GtkWidget *widget,
360 gboolean *cancelled);
361 static gint attach_property_delete_event (GtkWidget *widget,
363 gboolean *cancelled);
364 static gboolean attach_property_key_pressed (GtkWidget *widget,
366 gboolean *cancelled);
368 static void compose_exec_ext_editor (Compose *compose);
369 static gboolean compose_ext_editor_kill (Compose *compose);
370 static void compose_ext_editor_closed_cb (GPid pid,
373 static void compose_set_ext_editor_sensitive (Compose *compose,
375 static gboolean compose_get_ext_editor_cmd_valid();
376 static gboolean compose_get_ext_editor_uses_socket();
378 static gboolean compose_ext_editor_plug_removed_cb
381 #endif /* G_OS_WIN32 */
383 static void compose_undo_state_changed (UndoMain *undostruct,
388 static void compose_create_header_entry (Compose *compose);
389 static void compose_add_header_entry (Compose *compose, const gchar *header,
390 gchar *text, ComposePrefType pref_type);
391 static void compose_remove_header_entries(Compose *compose);
393 static void compose_update_priority_menu_item(Compose * compose);
395 static void compose_spell_menu_changed (void *data);
396 static void compose_dict_changed (void *data);
398 static void compose_add_field_list ( Compose *compose,
399 GList *listAddress );
401 /* callback functions */
403 static void compose_notebook_size_alloc (GtkNotebook *notebook,
404 GtkAllocation *allocation,
406 static gboolean compose_edit_size_alloc (GtkEditable *widget,
407 GtkAllocation *allocation,
408 GtkSHRuler *shruler);
409 static void account_activated (GtkComboBox *optmenu,
411 static void attach_selected (GtkTreeView *tree_view,
412 GtkTreePath *tree_path,
413 GtkTreeViewColumn *column,
415 static gboolean attach_button_pressed (GtkWidget *widget,
416 GdkEventButton *event,
418 static gboolean attach_key_pressed (GtkWidget *widget,
421 static void compose_send_cb (GtkAction *action, gpointer data);
422 static void compose_send_later_cb (GtkAction *action, gpointer data);
424 static void compose_save_cb (GtkAction *action,
427 static void compose_attach_cb (GtkAction *action,
429 static void compose_insert_file_cb (GtkAction *action,
431 static void compose_insert_sig_cb (GtkAction *action,
433 static void compose_replace_sig_cb (GtkAction *action,
436 static void compose_close_cb (GtkAction *action,
438 static void compose_print_cb (GtkAction *action,
441 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
443 static void compose_address_cb (GtkAction *action,
445 static void about_show_cb (GtkAction *action,
447 static void compose_template_activate_cb(GtkWidget *widget,
450 static void compose_ext_editor_cb (GtkAction *action,
453 static gint compose_delete_cb (GtkWidget *widget,
457 static void compose_undo_cb (GtkAction *action,
459 static void compose_redo_cb (GtkAction *action,
461 static void compose_cut_cb (GtkAction *action,
463 static void compose_copy_cb (GtkAction *action,
465 static void compose_paste_cb (GtkAction *action,
467 static void compose_paste_as_quote_cb (GtkAction *action,
469 static void compose_paste_no_wrap_cb (GtkAction *action,
471 static void compose_paste_wrap_cb (GtkAction *action,
473 static void compose_allsel_cb (GtkAction *action,
476 static void compose_advanced_action_cb (GtkAction *action,
479 static void compose_grab_focus_cb (GtkWidget *widget,
482 static void compose_changed_cb (GtkTextBuffer *textbuf,
485 static void compose_wrap_cb (GtkAction *action,
487 static void compose_wrap_all_cb (GtkAction *action,
489 static void compose_find_cb (GtkAction *action,
491 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
493 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
496 static void compose_toggle_ruler_cb (GtkToggleAction *action,
498 static void compose_toggle_sign_cb (GtkToggleAction *action,
500 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
502 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
503 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
504 static void compose_activate_privacy_system (Compose *compose,
505 PrefsAccount *account,
507 static void compose_apply_folder_privacy_settings(Compose *compose, FolderItem *folder_item);
508 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
510 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
512 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
513 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
514 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
516 static void compose_attach_drag_received_cb (GtkWidget *widget,
517 GdkDragContext *drag_context,
520 GtkSelectionData *data,
524 static void compose_insert_drag_received_cb (GtkWidget *widget,
525 GdkDragContext *drag_context,
528 GtkSelectionData *data,
532 static void compose_header_drag_received_cb (GtkWidget *widget,
533 GdkDragContext *drag_context,
536 GtkSelectionData *data,
541 static gboolean compose_drag_drop (GtkWidget *widget,
542 GdkDragContext *drag_context,
544 guint time, gpointer user_data);
545 static gboolean completion_set_focus_to_subject
550 static void text_inserted (GtkTextBuffer *buffer,
555 static Compose *compose_generic_reply(MsgInfo *msginfo,
556 ComposeQuoteMode quote_mode,
560 gboolean followup_and_reply_to,
563 static void compose_headerentry_changed_cb (GtkWidget *entry,
564 ComposeHeaderEntry *headerentry);
565 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
567 ComposeHeaderEntry *headerentry);
568 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
569 ComposeHeaderEntry *headerentry);
571 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
573 static void compose_allow_user_actions (Compose *compose, gboolean allow);
575 static void compose_nothing_cb (GtkAction *action, gpointer data)
581 static void compose_check_all (GtkAction *action, gpointer data);
582 static void compose_highlight_all (GtkAction *action, gpointer data);
583 static void compose_check_backwards (GtkAction *action, gpointer data);
584 static void compose_check_forwards_go (GtkAction *action, gpointer data);
587 static PrefsAccount *compose_find_account (MsgInfo *msginfo);
589 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
592 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
593 FolderItem *folder_item);
595 static void compose_attach_update_label(Compose *compose);
596 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
597 gboolean respect_default_to);
598 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
599 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
601 static GtkActionEntry compose_popup_entries[] =
603 {"Compose", NULL, "Compose", NULL, NULL, NULL },
604 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
605 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
606 {"Compose/---", NULL, "---", NULL, NULL, NULL },
607 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
610 static GtkActionEntry compose_entries[] =
612 {"Menu", NULL, "Menu", NULL, NULL, NULL },
614 {"Message", NULL, N_("_Message"), NULL, NULL, NULL },
615 {"Edit", NULL, N_("_Edit"), NULL, NULL, NULL },
617 {"Spelling", NULL, N_("_Spelling"), NULL, NULL, NULL },
619 {"Options", NULL, N_("_Options"), NULL, NULL, NULL },
620 {"Tools", NULL, N_("_Tools"), NULL, NULL, NULL },
621 {"Help", NULL, N_("_Help"), NULL, NULL, NULL },
623 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
624 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
625 {"Message/---", NULL, "---", NULL, NULL, NULL },
627 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
628 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
629 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
630 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
631 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
632 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
633 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
634 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
635 /* {"Message/---", NULL, "---", NULL, NULL, NULL }, */
636 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
639 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
640 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
641 {"Edit/---", NULL, "---", NULL, NULL, NULL },
643 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
644 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
645 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
647 {"Edit/SpecialPaste", NULL, N_("_Special paste"), NULL, NULL, NULL },
648 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
649 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
650 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
652 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
654 {"Edit/Advanced", NULL, N_("A_dvanced"), NULL, NULL, NULL },
655 {"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*/
656 {"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*/
657 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
658 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
659 {"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*/
660 {"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*/
661 {"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*/
662 {"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*/
663 {"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*/
664 {"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*/
665 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
666 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
667 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
668 {"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*/
670 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
671 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
673 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
674 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
675 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
676 /* {"Edit/---", NULL, "---", NULL, NULL, NULL }, */
677 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
680 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
681 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
682 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
683 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
685 {"Spelling/---", NULL, "---", NULL, NULL, NULL },
686 {"Spelling/Options", NULL, N_("_Options"), NULL, NULL, NULL },
690 {"Options/ReplyMode", NULL, N_("Reply _mode"), NULL, NULL, NULL },
691 {"Options/---", NULL, "---", NULL, NULL, NULL },
692 {"Options/PrivacySystem", NULL, N_("Privacy _System"), NULL, NULL, NULL },
693 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
695 /* {"Options/---", NULL, "---", NULL, NULL, NULL }, */
696 {"Options/Priority", NULL, N_("_Priority"), NULL, NULL, NULL },
698 {"Options/Encoding", NULL, N_("Character _encoding"), NULL, NULL, NULL },
699 {"Options/Encoding/---", NULL, "---", NULL, NULL, NULL },
700 #define ENC_ACTION(cs_char,c_char,string) \
701 {"Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
703 {"Options/Encoding/Western", NULL, N_("Western European"), NULL, NULL, NULL },
704 {"Options/Encoding/Baltic", NULL, N_("Baltic"), NULL, NULL, NULL },
705 {"Options/Encoding/Hebrew", NULL, N_("Hebrew"), NULL, NULL, NULL },
706 {"Options/Encoding/Arabic", NULL, N_("Arabic"), NULL, NULL, NULL },
707 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic"), NULL, NULL, NULL },
708 {"Options/Encoding/Japanese", NULL, N_("Japanese"), NULL, NULL, NULL },
709 {"Options/Encoding/Chinese", NULL, N_("Chinese"), NULL, NULL, NULL },
710 {"Options/Encoding/Korean", NULL, N_("Korean"), NULL, NULL, NULL },
711 {"Options/Encoding/Thai", NULL, N_("Thai"), NULL, NULL, NULL },
714 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
716 {"Tools/Template", NULL, N_("_Template"), NULL, NULL, NULL },
717 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
718 {"Tools/Actions", NULL, N_("Actio_ns"), NULL, NULL, NULL },
719 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
722 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
725 static GtkToggleActionEntry compose_toggle_entries[] =
727 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb), FALSE }, /* Toggle */
728 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb), FALSE }, /* Toggle */
729 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb), FALSE }, /* Toggle */
730 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb), FALSE }, /* Toggle */
731 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb), FALSE }, /* Toggle */
732 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb), FALSE }, /* Toggle */
733 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb), FALSE }, /* Toggle */
736 static GtkRadioActionEntry compose_radio_rm_entries[] =
738 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
739 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
740 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
741 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
744 static GtkRadioActionEntry compose_radio_prio_entries[] =
746 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
747 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
748 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
749 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
750 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
753 static GtkRadioActionEntry compose_radio_enc_entries[] =
755 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
775 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
776 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
777 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
778 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
779 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
780 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
781 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
782 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
783 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
784 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
785 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
786 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
787 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
790 static GtkTargetEntry compose_mime_types[] =
792 {"text/uri-list", 0, 0},
793 {"UTF8_STRING", 0, 0},
797 static gboolean compose_put_existing_to_front(MsgInfo *info)
799 const GList *compose_list = compose_get_compose_list();
800 const GList *elem = NULL;
803 for (elem = compose_list; elem != NULL && elem->data != NULL;
805 Compose *c = (Compose*)elem->data;
807 if (!c->targetinfo || !c->targetinfo->msgid ||
811 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
812 gtkut_window_popup(c->window);
820 static GdkRGBA quote_color1 =
822 static GdkRGBA quote_color2 =
824 static GdkRGBA quote_color3 =
827 static GdkRGBA quote_bgcolor1 =
829 static GdkRGBA quote_bgcolor2 =
831 static GdkRGBA quote_bgcolor3 =
834 static GdkRGBA signature_color =
837 static GdkRGBA uri_color =
840 static void compose_create_tags(GtkTextView *text, Compose *compose)
842 GtkTextBuffer *buffer;
843 GdkRGBA black = { 0, 0, 0, 1 };
845 buffer = gtk_text_view_get_buffer(text);
847 if (prefs_common.enable_color) {
848 /* grab the quote colors, converting from an int to a GdkColor */
849 quote_color1 = prefs_common.color[COL_QUOTE_LEVEL1];
850 quote_color2 = prefs_common.color[COL_QUOTE_LEVEL2];
851 quote_color3 = prefs_common.color[COL_QUOTE_LEVEL3];
852 quote_bgcolor1 = prefs_common.color[COL_QUOTE_LEVEL1_BG];
853 quote_bgcolor2 = prefs_common.color[COL_QUOTE_LEVEL2_BG];
854 quote_bgcolor3 = prefs_common.color[COL_QUOTE_LEVEL3_BG];
855 signature_color = prefs_common.color[COL_SIGNATURE];
856 uri_color = prefs_common.color[COL_URI];
858 signature_color = quote_color1 = quote_color2 = quote_color3 =
859 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
862 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
863 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
864 "foreground-rgba", "e_color1,
865 "paragraph-background-rgba", "e_bgcolor1,
867 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
868 "foreground-rgba", "e_color2,
869 "paragraph-background-rgba", "e_bgcolor2,
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-rgba", "e_color3,
873 "paragraph-background-rgba", "e_bgcolor3,
876 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
877 "foreground-rgba", "e_color1,
879 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
880 "foreground-rgba", "e_color2,
882 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
883 "foreground-rgba", "e_color3,
887 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
888 "foreground-rgba", &signature_color,
891 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
892 "foreground-rgba", &uri_color,
894 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
895 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
898 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
901 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
904 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
906 return compose_generic_new(account, mailto, item, NULL, NULL);
909 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
911 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
914 #define SCROLL_TO_CURSOR(compose) { \
915 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
916 gtk_text_view_get_buffer( \
917 GTK_TEXT_VIEW(compose->text))); \
918 gtk_text_view_scroll_mark_onscreen( \
919 GTK_TEXT_VIEW(compose->text), \
923 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
926 if (folderidentifier) {
927 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
928 prefs_common.compose_save_to_history = add_history(
929 prefs_common.compose_save_to_history, folderidentifier);
930 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
931 prefs_common.compose_save_to_history);
934 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
935 if (folderidentifier)
936 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
938 gtk_entry_set_text(GTK_ENTRY(entry), "");
941 static gchar *compose_get_save_to(Compose *compose)
944 gchar *result = NULL;
945 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
946 result = gtk_editable_get_chars(entry, 0, -1);
949 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
950 prefs_common.compose_save_to_history = add_history(
951 prefs_common.compose_save_to_history, result);
952 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
953 prefs_common.compose_save_to_history);
958 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
959 GList *attach_files, GList *listAddress )
962 GtkTextView *textview;
963 GtkTextBuffer *textbuf;
965 const gchar *subject_format = NULL;
966 const gchar *body_format = NULL;
967 gchar *mailto_from = NULL;
968 PrefsAccount *mailto_account = NULL;
969 MsgInfo* dummyinfo = NULL;
970 gint cursor_pos = -1;
971 MailField mfield = NO_FIELD_PRESENT;
975 /* check if mailto defines a from */
976 if (mailto && *mailto != '\0') {
977 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
978 /* mailto defines a from, check if we can get account prefs from it,
979 if not, the account prefs will be guessed using other ways, but we'll keep
982 mailto_account = account_find_from_address(mailto_from, TRUE);
983 if (mailto_account == NULL) {
985 Xstrdup_a(tmp_from, mailto_from, return NULL);
986 extract_address(tmp_from);
987 mailto_account = account_find_from_address(tmp_from, TRUE);
991 account = mailto_account;
994 /* if no account prefs set from mailto, set if from folder prefs (if any) */
995 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
996 account = account_find_from_id(item->prefs->default_account);
998 /* if no account prefs set, fallback to the current one */
999 if (!account) account = cur_account;
1000 cm_return_val_if_fail(account != NULL, NULL);
1002 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1003 compose_apply_folder_privacy_settings(compose, item);
1005 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1006 (account->default_encrypt || account->default_sign))
1007 COMPOSE_PRIVACY_WARNING();
1009 /* override from name if mailto asked for it */
1011 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1012 g_free(mailto_from);
1014 /* override from name according to folder properties */
1015 if (item && item->prefs &&
1016 item->prefs->compose_with_format &&
1017 item->prefs->compose_override_from_format &&
1018 *item->prefs->compose_override_from_format != '\0') {
1023 dummyinfo = compose_msginfo_new_from_compose(compose);
1025 /* decode \-escape sequences in the internal representation of the quote format */
1026 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1027 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1030 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1031 compose->gtkaspell);
1033 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1035 quote_fmt_scan_string(tmp);
1038 buf = quote_fmt_get_buffer();
1040 alertpanel_error(_("New message From format error."));
1042 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1043 quote_fmt_reset_vartable();
1044 quote_fmtlex_destroy();
1049 compose->replyinfo = NULL;
1050 compose->fwdinfo = NULL;
1052 textview = GTK_TEXT_VIEW(compose->text);
1053 textbuf = gtk_text_view_get_buffer(textview);
1054 compose_create_tags(textview, compose);
1056 undo_block(compose->undostruct);
1058 compose_set_dictionaries_from_folder_prefs(compose, item);
1061 if (account->auto_sig)
1062 compose_insert_sig(compose, FALSE);
1063 gtk_text_buffer_get_start_iter(textbuf, &iter);
1064 gtk_text_buffer_place_cursor(textbuf, &iter);
1066 if (account->protocol != A_NNTP) {
1067 if (mailto && *mailto != '\0') {
1068 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1071 compose_set_folder_prefs(compose, item, TRUE);
1073 if (item && item->ret_rcpt) {
1074 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1077 if (mailto && *mailto != '\0') {
1078 if (!strchr(mailto, '@'))
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1083 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1084 mfield = TO_FIELD_PRESENT;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1092 compose_add_field_list( compose, listAddress );
1094 if (item && item->prefs && item->prefs->compose_with_format) {
1095 subject_format = item->prefs->compose_subject_format;
1096 body_format = item->prefs->compose_body_format;
1097 } else if (account->compose_with_format) {
1098 subject_format = account->compose_subject_format;
1099 body_format = account->compose_body_format;
1100 } else if (prefs_common.compose_with_format) {
1101 subject_format = prefs_common.compose_subject_format;
1102 body_format = prefs_common.compose_body_format;
1105 if (subject_format || body_format) {
1108 && *subject_format != '\0' )
1110 gchar *subject = NULL;
1115 dummyinfo = compose_msginfo_new_from_compose(compose);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp = g_malloc(strlen(subject_format)+1);
1119 pref_get_unescaped_pref(tmp, subject_format);
1121 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1123 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1124 compose->gtkaspell);
1126 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1128 quote_fmt_scan_string(tmp);
1131 buf = quote_fmt_get_buffer();
1133 alertpanel_error(_("New message subject format error."));
1135 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1136 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1137 quote_fmt_reset_vartable();
1138 quote_fmtlex_destroy();
1142 mfield = SUBJECT_FIELD_PRESENT;
1146 && *body_format != '\0' )
1149 GtkTextBuffer *buffer;
1150 GtkTextIter start, end;
1154 dummyinfo = compose_msginfo_new_from_compose(compose);
1156 text = GTK_TEXT_VIEW(compose->text);
1157 buffer = gtk_text_view_get_buffer(text);
1158 gtk_text_buffer_get_start_iter(buffer, &start);
1159 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1160 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1162 compose_quote_fmt(compose, dummyinfo,
1164 NULL, tmp, FALSE, TRUE,
1165 _("The body of the \"New message\" template has an error at line %d."));
1166 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1167 quote_fmt_reset_vartable();
1171 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1172 gtkaspell_highlight_all(compose->gtkaspell);
1174 mfield = BODY_FIELD_PRESENT;
1178 procmsg_msginfo_free( &dummyinfo );
1184 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1185 ainfo = (AttachInfo *) curr->data;
1187 compose_insert_file(compose, ainfo->file);
1189 compose_attach_append(compose, ainfo->file, ainfo->file,
1190 ainfo->content_type, ainfo->charset);
1194 compose_show_first_last_header(compose, TRUE);
1196 /* Set save folder */
1197 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1198 gchar *folderidentifier;
1200 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1201 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1202 folderidentifier = folder_item_get_identifier(item);
1203 compose_set_save_to(compose, folderidentifier);
1204 g_free(folderidentifier);
1207 /* Place cursor according to provided input (mfield) */
1209 case NO_FIELD_PRESENT:
1210 if (compose->header_last)
1211 gtk_widget_grab_focus(compose->header_last->entry);
1213 case TO_FIELD_PRESENT:
1214 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1216 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1219 gtk_widget_grab_focus(compose->subject_entry);
1221 case SUBJECT_FIELD_PRESENT:
1222 textview = GTK_TEXT_VIEW(compose->text);
1225 textbuf = gtk_text_view_get_buffer(textview);
1228 mark = gtk_text_buffer_get_insert(textbuf);
1229 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1230 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1232 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1233 * only defers where it comes to the variable body
1234 * is not null. If no body is present compose->text
1235 * will be null in which case you cannot place the
1236 * cursor inside the component so. An empty component
1237 * is therefore created before placing the cursor
1239 case BODY_FIELD_PRESENT:
1240 cursor_pos = quote_fmt_get_cursor_pos();
1241 if (cursor_pos == -1)
1242 gtk_widget_grab_focus(compose->header_last->entry);
1244 gtk_widget_grab_focus(compose->text);
1248 undo_unblock(compose->undostruct);
1250 if (prefs_common.auto_exteditor)
1251 compose_exec_ext_editor(compose);
1253 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1255 SCROLL_TO_CURSOR(compose);
1257 compose->modified = FALSE;
1258 compose_set_title(compose);
1260 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1265 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1266 gboolean override_pref, const gchar *system)
1268 const gchar *privacy = NULL;
1270 cm_return_if_fail(compose != NULL);
1271 cm_return_if_fail(account != NULL);
1273 if (privacy_system_can_encrypt(compose->privacy_system) == FALSE ||
1274 (override_pref == FALSE && account->default_encrypt_reply == FALSE))
1277 if (account->default_privacy_system && strlen(account->default_privacy_system))
1278 privacy = account->default_privacy_system;
1282 GSList *privacy_avail = privacy_get_system_ids();
1283 if (privacy_avail && g_slist_length(privacy_avail)) {
1284 privacy = (gchar *)(privacy_avail->data);
1286 g_slist_free_full(privacy_avail, g_free);
1288 if (privacy != NULL) {
1290 g_free(compose->privacy_system);
1291 compose->privacy_system = NULL;
1292 g_free(compose->encdata);
1293 compose->encdata = NULL;
1295 if (compose->privacy_system == NULL)
1296 compose->privacy_system = g_strdup(privacy);
1297 else if (*(compose->privacy_system) == '\0') {
1298 g_free(compose->privacy_system);
1299 g_free(compose->encdata);
1300 compose->encdata = NULL;
1301 compose->privacy_system = g_strdup(privacy);
1303 compose_update_privacy_system_menu_item(compose, FALSE);
1304 compose_use_encryption(compose, TRUE);
1308 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1310 const gchar *privacy = NULL;
1311 if (privacy_system_can_sign(compose->privacy_system) == FALSE)
1314 if (account->default_privacy_system && strlen(account->default_privacy_system))
1315 privacy = account->default_privacy_system;
1319 GSList *privacy_avail = privacy_get_system_ids();
1320 if (privacy_avail && g_slist_length(privacy_avail)) {
1321 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 compose_update_privacy_system_menu_item(compose, FALSE);
1335 compose_use_signing(compose, TRUE);
1339 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1343 Compose *compose = NULL;
1345 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1347 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1348 cm_return_val_if_fail(msginfo != NULL, NULL);
1350 list_len = g_slist_length(msginfo_list);
1354 case COMPOSE_REPLY_TO_ADDRESS:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1356 FALSE, prefs_common.default_reply_list, FALSE, body);
1358 case COMPOSE_REPLY_WITH_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1360 FALSE, prefs_common.default_reply_list, FALSE, body);
1362 case COMPOSE_REPLY_WITHOUT_QUOTE:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1364 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1366 case COMPOSE_REPLY_TO_SENDER:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1368 FALSE, FALSE, TRUE, body);
1370 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1371 compose = compose_followup_and_reply_to(msginfo,
1372 COMPOSE_QUOTE_CHECK,
1373 FALSE, FALSE, body);
1375 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1377 FALSE, FALSE, TRUE, body);
1379 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1380 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1381 FALSE, FALSE, TRUE, NULL);
1383 case COMPOSE_REPLY_TO_ALL:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1385 TRUE, FALSE, FALSE, body);
1387 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1389 TRUE, FALSE, FALSE, body);
1391 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1393 TRUE, FALSE, FALSE, NULL);
1395 case COMPOSE_REPLY_TO_LIST:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1397 FALSE, TRUE, FALSE, body);
1399 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1401 FALSE, TRUE, FALSE, body);
1403 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1404 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1405 FALSE, TRUE, FALSE, NULL);
1407 case COMPOSE_FORWARD:
1408 if (prefs_common.forward_as_attachment) {
1409 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1412 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1416 case COMPOSE_FORWARD_INLINE:
1417 /* check if we reply to more than one Message */
1418 if (list_len == 1) {
1419 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1422 /* more messages FALL THROUGH */
1423 case COMPOSE_FORWARD_AS_ATTACH:
1424 compose = compose_forward_multiple(NULL, msginfo_list);
1426 case COMPOSE_REDIRECT:
1427 compose = compose_redirect(NULL, msginfo, FALSE);
1430 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1433 if (compose == NULL) {
1434 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1438 compose->rmode = mode;
1439 switch (compose->rmode) {
1441 case COMPOSE_REPLY_WITH_QUOTE:
1442 case COMPOSE_REPLY_WITHOUT_QUOTE:
1443 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1444 debug_print("reply mode Normal\n");
1445 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1446 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1448 case COMPOSE_REPLY_TO_SENDER:
1449 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1450 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1451 debug_print("reply mode Sender\n");
1452 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1454 case COMPOSE_REPLY_TO_ALL:
1455 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1456 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1457 debug_print("reply mode All\n");
1458 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1460 case COMPOSE_REPLY_TO_LIST:
1461 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1462 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1463 debug_print("reply mode List\n");
1464 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1466 case COMPOSE_REPLY_TO_ADDRESS:
1467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1475 static Compose *compose_reply(MsgInfo *msginfo,
1476 ComposeQuoteMode quote_mode,
1482 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1483 to_sender, FALSE, body);
1486 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1487 ComposeQuoteMode quote_mode,
1492 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1493 to_sender, TRUE, body);
1496 static void compose_extract_original_charset(Compose *compose)
1498 MsgInfo *info = NULL;
1499 if (compose->replyinfo) {
1500 info = compose->replyinfo;
1501 } else if (compose->fwdinfo) {
1502 info = compose->fwdinfo;
1503 } else if (compose->targetinfo) {
1504 info = compose->targetinfo;
1507 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1508 MimeInfo *partinfo = mimeinfo;
1509 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1510 partinfo = procmime_mimeinfo_next(partinfo);
1512 compose->orig_charset =
1513 g_strdup(procmime_mimeinfo_get_parameter(
1514 partinfo, "charset"));
1516 procmime_mimeinfo_free_all(&mimeinfo);
1520 #define SIGNAL_BLOCK(buffer) { \
1521 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1522 G_CALLBACK(compose_changed_cb), \
1524 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1525 G_CALLBACK(text_inserted), \
1529 #define SIGNAL_UNBLOCK(buffer) { \
1530 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1533 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1538 static Compose *compose_generic_reply(MsgInfo *msginfo,
1539 ComposeQuoteMode quote_mode,
1540 gboolean to_all, gboolean to_ml,
1542 gboolean followup_and_reply_to,
1546 PrefsAccount *account = NULL;
1547 GtkTextView *textview;
1548 GtkTextBuffer *textbuf;
1549 gboolean quote = FALSE;
1550 const gchar *qmark = NULL;
1551 const gchar *body_fmt = NULL;
1552 gchar *s_system = NULL;
1554 cm_return_val_if_fail(msginfo != NULL, NULL);
1555 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1557 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1559 cm_return_val_if_fail(account != NULL, NULL);
1561 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1562 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1564 compose->updating = TRUE;
1566 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1569 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1570 if (!compose->replyinfo)
1571 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1573 compose_extract_original_charset(compose);
1575 if (msginfo->folder && msginfo->folder->ret_rcpt)
1576 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1578 /* Set save folder */
1579 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1580 gchar *folderidentifier;
1582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1583 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1584 folderidentifier = folder_item_get_identifier(msginfo->folder);
1585 compose_set_save_to(compose, folderidentifier);
1586 g_free(folderidentifier);
1589 if (compose_parse_header(compose, msginfo) < 0) {
1590 compose->updating = FALSE;
1591 compose_destroy(compose);
1595 /* override from name according to folder properties */
1596 if (msginfo->folder && msginfo->folder->prefs &&
1597 msginfo->folder->prefs->reply_with_format &&
1598 msginfo->folder->prefs->reply_override_from_format &&
1599 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1604 /* decode \-escape sequences in the internal representation of the quote format */
1605 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1606 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1609 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1610 compose->gtkaspell);
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1614 quote_fmt_scan_string(tmp);
1617 buf = quote_fmt_get_buffer();
1619 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1621 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1622 quote_fmt_reset_vartable();
1623 quote_fmtlex_destroy();
1628 textview = (GTK_TEXT_VIEW(compose->text));
1629 textbuf = gtk_text_view_get_buffer(textview);
1630 compose_create_tags(textview, compose);
1632 undo_block(compose->undostruct);
1634 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1635 gtkaspell_block_check(compose->gtkaspell);
1638 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1639 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1640 /* use the reply format of folder (if enabled), or the account's one
1641 (if enabled) or fallback to the global reply format, which is always
1642 enabled (even if empty), and use the relevant quotemark */
1644 if (msginfo->folder && msginfo->folder->prefs &&
1645 msginfo->folder->prefs->reply_with_format) {
1646 qmark = msginfo->folder->prefs->reply_quotemark;
1647 body_fmt = msginfo->folder->prefs->reply_body_format;
1649 } else if (account->reply_with_format) {
1650 qmark = account->reply_quotemark;
1651 body_fmt = account->reply_body_format;
1654 qmark = prefs_common.quotemark;
1655 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1656 body_fmt = gettext(prefs_common.quotefmt);
1663 /* empty quotemark is not allowed */
1664 if (qmark == NULL || *qmark == '\0')
1666 compose_quote_fmt(compose, compose->replyinfo,
1667 body_fmt, qmark, body, FALSE, TRUE,
1668 _("The body of the \"Reply\" template has an error at line %d."));
1669 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1670 quote_fmt_reset_vartable();
1673 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1674 compose_force_encryption(compose, account, FALSE, s_system);
1677 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1678 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1679 compose_force_signing(compose, account, s_system);
1683 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1684 ((account->default_encrypt || account->default_sign) ||
1685 (account->default_encrypt_reply && MSG_IS_ENCRYPTED(compose->replyinfo->flags)) ||
1686 (account->default_sign_reply && MSG_IS_SIGNED(compose->replyinfo->flags))))
1687 COMPOSE_PRIVACY_WARNING();
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1747 if (!account && !(account = compose_find_account(msginfo)))
1748 account = cur_account;
1750 if (!prefs_common.forward_as_attachment)
1751 mode = COMPOSE_FORWARD_INLINE;
1753 mode = COMPOSE_FORWARD;
1754 compose = compose_create(account, msginfo->folder, mode, batch);
1755 compose_apply_folder_privacy_settings(compose, msginfo->folder);
1757 compose->updating = TRUE;
1758 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1759 if (!compose->fwdinfo)
1760 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1762 compose_extract_original_charset(compose);
1764 if (msginfo->subject && *msginfo->subject) {
1765 gchar *buf, *buf2, *p;
1767 buf = p = g_strdup(msginfo->subject);
1768 p += subject_get_prefix_length(p);
1769 memmove(buf, p, strlen(p) + 1);
1771 buf2 = g_strdup_printf("Fw: %s", buf);
1772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1778 /* override from name according to folder properties */
1779 if (msginfo->folder && msginfo->folder->prefs &&
1780 msginfo->folder->prefs->forward_with_format &&
1781 msginfo->folder->prefs->forward_override_from_format &&
1782 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1786 MsgInfo *full_msginfo = NULL;
1789 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1791 full_msginfo = procmsg_msginfo_copy(msginfo);
1793 /* decode \-escape sequences in the internal representation of the quote format */
1794 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1795 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1798 gtkaspell_block_check(compose->gtkaspell);
1799 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1800 compose->gtkaspell);
1802 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1804 quote_fmt_scan_string(tmp);
1807 buf = quote_fmt_get_buffer();
1809 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1811 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1812 quote_fmt_reset_vartable();
1813 quote_fmtlex_destroy();
1816 procmsg_msginfo_free(&full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file does not exist", msgfile);
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(&full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1890 SIGNAL_UNBLOCK(textbuf);
1892 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1893 (account->default_encrypt || account->default_sign))
1894 COMPOSE_PRIVACY_WARNING();
1896 cursor_pos = quote_fmt_get_cursor_pos();
1897 if (cursor_pos == -1)
1898 gtk_widget_grab_focus(compose->header_last->entry);
1900 gtk_widget_grab_focus(compose->text);
1902 if (!no_extedit && prefs_common.auto_exteditor)
1903 compose_exec_ext_editor(compose);
1906 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1907 gchar *folderidentifier;
1909 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1910 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
1911 folderidentifier = folder_item_get_identifier(msginfo->folder);
1912 compose_set_save_to(compose, folderidentifier);
1913 g_free(folderidentifier);
1916 undo_unblock(compose->undostruct);
1918 compose->modified = FALSE;
1919 compose_set_title(compose);
1921 compose->updating = FALSE;
1922 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1923 SCROLL_TO_CURSOR(compose);
1925 if (compose->deferred_destroy) {
1926 compose_destroy(compose);
1930 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1935 #undef INSERT_FW_HEADER
1937 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1940 GtkTextView *textview;
1941 GtkTextBuffer *textbuf;
1945 gboolean single_mail = TRUE;
1947 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1949 if (g_slist_length(msginfo_list) > 1)
1950 single_mail = FALSE;
1952 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1953 if (((MsgInfo *)msginfo->data)->folder == NULL)
1956 /* guess account from first selected message */
1958 !(account = compose_find_account(msginfo_list->data)))
1959 account = cur_account;
1961 cm_return_val_if_fail(account != NULL, NULL);
1963 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1964 if (msginfo->data) {
1965 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1966 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1970 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1971 g_warning("no msginfo_list");
1975 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1976 compose_apply_folder_privacy_settings(compose, ((MsgInfo *)msginfo_list->data)->folder);
1977 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
1978 (account->default_encrypt || account->default_sign))
1979 COMPOSE_PRIVACY_WARNING();
1981 compose->updating = TRUE;
1983 /* override from name according to folder properties */
1984 if (msginfo_list->data) {
1985 MsgInfo *msginfo = msginfo_list->data;
1987 if (msginfo->folder && msginfo->folder->prefs &&
1988 msginfo->folder->prefs->forward_with_format &&
1989 msginfo->folder->prefs->forward_override_from_format &&
1990 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1995 /* decode \-escape sequences in the internal representation of the quote format */
1996 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1997 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2000 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2001 compose->gtkaspell);
2003 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2005 quote_fmt_scan_string(tmp);
2008 buf = quote_fmt_get_buffer();
2010 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2012 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2013 quote_fmt_reset_vartable();
2014 quote_fmtlex_destroy();
2020 textview = GTK_TEXT_VIEW(compose->text);
2021 textbuf = gtk_text_view_get_buffer(textview);
2022 compose_create_tags(textview, compose);
2024 undo_block(compose->undostruct);
2025 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2026 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2028 if (!is_file_exist(msgfile))
2029 g_warning("%s: file does not exist", msgfile);
2031 compose_attach_append(compose, msgfile, msgfile,
2032 "message/rfc822", NULL);
2037 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2038 if (info->subject && *info->subject) {
2039 gchar *buf, *buf2, *p;
2041 buf = p = g_strdup(info->subject);
2042 p += subject_get_prefix_length(p);
2043 memmove(buf, p, strlen(p) + 1);
2045 buf2 = g_strdup_printf("Fw: %s", buf);
2046 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2052 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2053 _("Fw: multiple emails"));
2056 SIGNAL_BLOCK(textbuf);
2058 if (account->auto_sig)
2059 compose_insert_sig(compose, FALSE);
2061 compose_wrap_all(compose);
2063 SIGNAL_UNBLOCK(textbuf);
2065 gtk_text_buffer_get_start_iter(textbuf, &iter);
2066 gtk_text_buffer_place_cursor(textbuf, &iter);
2068 if (prefs_common.auto_exteditor)
2069 compose_exec_ext_editor(compose);
2071 gtk_widget_grab_focus(compose->header_last->entry);
2072 undo_unblock(compose->undostruct);
2073 compose->modified = FALSE;
2074 compose_set_title(compose);
2076 compose->updating = FALSE;
2077 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2078 SCROLL_TO_CURSOR(compose);
2080 if (compose->deferred_destroy) {
2081 compose_destroy(compose);
2085 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2090 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2092 GtkTextIter start = *iter;
2093 GtkTextIter end_iter;
2094 int start_pos = gtk_text_iter_get_offset(&start);
2096 if (!compose->account->sig_sep)
2099 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2100 start_pos+strlen(compose->account->sig_sep));
2102 /* check sig separator */
2103 str = gtk_text_iter_get_text(&start, &end_iter);
2104 if (!strcmp(str, compose->account->sig_sep)) {
2106 /* check end of line (\n) */
2107 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2108 start_pos+strlen(compose->account->sig_sep));
2109 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2110 start_pos+strlen(compose->account->sig_sep)+1);
2111 tmp = gtk_text_iter_get_text(&start, &end_iter);
2112 if (!strcmp(tmp,"\n")) {
2124 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2126 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2127 Compose *compose = (Compose *)data;
2128 FolderItem *old_item = NULL;
2129 FolderItem *new_item = NULL;
2130 gchar *old_id, *new_id;
2132 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2133 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2136 old_item = hookdata->item;
2137 new_item = hookdata->item2;
2139 old_id = folder_item_get_identifier(old_item);
2140 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2142 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2143 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2144 compose->targetinfo->folder = new_item;
2147 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2148 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2149 compose->replyinfo->folder = new_item;
2152 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2153 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2154 compose->fwdinfo->folder = new_item;
2162 static void compose_colorize_signature(Compose *compose)
2164 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2166 GtkTextIter end_iter;
2167 gtk_text_buffer_get_start_iter(buffer, &iter);
2168 while (gtk_text_iter_forward_line(&iter))
2169 if (compose_is_sig_separator(compose, buffer, &iter)) {
2170 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2171 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2175 #define BLOCK_WRAP() { \
2176 prev_autowrap = compose->autowrap; \
2177 buffer = gtk_text_view_get_buffer( \
2178 GTK_TEXT_VIEW(compose->text)); \
2179 compose->autowrap = FALSE; \
2181 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2182 G_CALLBACK(compose_changed_cb), \
2184 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2185 G_CALLBACK(text_inserted), \
2188 #define UNBLOCK_WRAP() { \
2189 compose->autowrap = prev_autowrap; \
2190 if (compose->autowrap) { \
2191 gint old = compose->draft_timeout_tag; \
2192 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2193 compose_wrap_all(compose); \
2194 compose->draft_timeout_tag = old; \
2197 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2198 G_CALLBACK(compose_changed_cb), \
2200 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2201 G_CALLBACK(text_inserted), \
2205 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2207 Compose *compose = NULL;
2208 PrefsAccount *account = NULL;
2209 GtkTextView *textview;
2210 GtkTextBuffer *textbuf;
2214 gboolean use_signing = FALSE;
2215 gboolean use_encryption = FALSE;
2216 gchar *privacy_system = NULL;
2217 int priority = PRIORITY_NORMAL;
2218 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2219 gboolean autowrap = prefs_common.autowrap;
2220 gboolean autoindent = prefs_common.auto_indent;
2221 HeaderEntry *manual_headers = NULL;
2223 cm_return_val_if_fail(msginfo != NULL, NULL);
2224 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2226 if (compose_put_existing_to_front(msginfo)) {
2230 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2231 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2232 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2233 gchar *queueheader_buf = NULL;
2236 /* Select Account from queue headers */
2237 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2238 "X-Claws-Account-Id:")) {
2239 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2240 account = account_find_from_id(id);
2241 g_free(queueheader_buf);
2243 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2244 "X-Sylpheed-Account-Id:")) {
2245 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2246 account = account_find_from_id(id);
2247 g_free(queueheader_buf);
2249 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2251 id = atoi(&queueheader_buf[strlen("NAID:")]);
2252 account = account_find_from_id(id);
2253 g_free(queueheader_buf);
2255 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2257 id = atoi(&queueheader_buf[strlen("MAID:")]);
2258 account = account_find_from_id(id);
2259 g_free(queueheader_buf);
2261 if (!account && !procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2263 account = account_find_from_address(queueheader_buf, FALSE);
2264 g_free(queueheader_buf);
2266 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2268 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2269 use_signing = param;
2270 g_free(queueheader_buf);
2272 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2273 "X-Sylpheed-Sign:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2275 use_signing = param;
2276 g_free(queueheader_buf);
2278 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2279 "X-Claws-Encrypt:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2281 use_encryption = param;
2282 g_free(queueheader_buf);
2284 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2285 "X-Sylpheed-Encrypt:")) {
2286 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2287 use_encryption = param;
2288 g_free(queueheader_buf);
2290 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2291 "X-Claws-Auto-Wrapping:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2294 g_free(queueheader_buf);
2296 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2297 "X-Claws-Auto-Indent:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2300 g_free(queueheader_buf);
2302 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2303 "X-Claws-Privacy-System:")) {
2304 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2305 g_free(queueheader_buf);
2307 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2308 "X-Sylpheed-Privacy-System:")) {
2309 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2310 g_free(queueheader_buf);
2312 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2314 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2316 g_free(queueheader_buf);
2318 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2320 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2321 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2322 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2323 if (orig_item != NULL) {
2324 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2329 g_free(queueheader_buf);
2331 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2333 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2334 if (tokens && tokens[0] && tokens[1] && tokens[2]) {
2335 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2336 if (orig_item != NULL) {
2337 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2342 g_free(queueheader_buf);
2344 /* Get manual headers */
2345 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf,
2346 "X-Claws-Manual-Headers:")) {
2347 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2348 if (listmh && *listmh != '\0') {
2349 debug_print("Got manual headers: %s\n", listmh);
2350 manual_headers = procheader_entries_from_str(listmh);
2354 g_free(queueheader_buf);
2357 account = msginfo->folder->folder->account;
2360 if (!account && prefs_common.reedit_account_autosel) {
2362 if (!procheader_get_header_from_msginfo(msginfo, &from, "FROM:")) {
2363 extract_address(from);
2364 account = account_find_from_address(from, FALSE);
2370 account = cur_account;
2373 g_warning("can't select account");
2375 procheader_entries_free(manual_headers);
2379 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2381 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
2382 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
2383 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2384 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2385 compose->autowrap = autowrap;
2386 compose->replyinfo = replyinfo;
2387 compose->fwdinfo = fwdinfo;
2389 compose->updating = TRUE;
2390 compose->priority = priority;
2392 if (privacy_system != NULL) {
2393 compose->privacy_system = privacy_system;
2394 compose_use_signing(compose, use_signing);
2395 compose_use_encryption(compose, use_encryption);
2396 compose_update_privacy_system_menu_item(compose, FALSE);
2398 compose_activate_privacy_system(compose, account, FALSE);
2400 compose_apply_folder_privacy_settings(compose, msginfo->folder);
2401 if (privacy_system_can_sign(compose->privacy_system) == FALSE &&
2402 (account->default_encrypt || account->default_sign))
2403 COMPOSE_PRIVACY_WARNING();
2405 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2406 compose->targetinfo->tags = g_slist_copy(msginfo->tags);
2408 compose_extract_original_charset(compose);
2410 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2411 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2412 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2413 gchar *queueheader_buf = NULL;
2415 /* Set message save folder */
2416 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "SCF:")) {
2417 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2418 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2419 compose_set_save_to(compose, &queueheader_buf[4]);
2420 g_free(queueheader_buf);
2422 if (!procheader_get_header_from_msginfo(msginfo, &queueheader_buf, "RRCPT:")) {
2423 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2425 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2427 g_free(queueheader_buf);
2431 if (compose_parse_header(compose, msginfo) < 0) {
2432 compose->updating = FALSE;
2433 compose_destroy(compose);
2435 procheader_entries_free(manual_headers);
2438 compose_reedit_set_entry(compose, msginfo);
2440 textview = GTK_TEXT_VIEW(compose->text);
2441 textbuf = gtk_text_view_get_buffer(textview);
2442 compose_create_tags(textview, compose);
2444 mark = gtk_text_buffer_get_insert(textbuf);
2445 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2447 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2448 G_CALLBACK(compose_changed_cb),
2451 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2452 fp = procmime_get_first_encrypted_text_content(msginfo);
2454 compose_force_encryption(compose, account, TRUE, NULL);
2457 fp = procmime_get_first_text_content(msginfo);
2460 g_warning("can't get text part");
2464 gchar buf[BUFFSIZE];
2465 gboolean prev_autowrap;
2466 GtkTextBuffer *buffer;
2468 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2470 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2476 compose_attach_parts(compose, msginfo);
2478 compose_colorize_signature(compose);
2480 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2481 G_CALLBACK(compose_changed_cb),
2484 if (manual_headers != NULL) {
2485 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2486 procheader_entries_free(manual_headers);
2487 compose->updating = FALSE;
2488 compose_destroy(compose);
2491 procheader_entries_free(manual_headers);
2494 gtk_widget_grab_focus(compose->text);
2496 if (prefs_common.auto_exteditor) {
2497 compose_exec_ext_editor(compose);
2499 compose->modified = FALSE;
2500 compose_set_title(compose);
2502 compose->updating = FALSE;
2503 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2504 SCROLL_TO_CURSOR(compose);
2506 if (compose->deferred_destroy) {
2507 compose_destroy(compose);
2511 compose->sig_str = account_get_signature_str(compose->account);
2513 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2518 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2525 cm_return_val_if_fail(msginfo != NULL, NULL);
2528 account = account_get_reply_account(msginfo,
2529 prefs_common.reply_account_autosel);
2530 cm_return_val_if_fail(account != NULL, NULL);
2532 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2534 compose->updating = TRUE;
2536 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2537 compose->replyinfo = NULL;
2538 compose->fwdinfo = NULL;
2540 compose_show_first_last_header(compose, TRUE);
2542 gtk_widget_grab_focus(compose->header_last->entry);
2544 filename = procmsg_get_message_file(msginfo);
2546 if (filename == NULL) {
2547 compose->updating = FALSE;
2548 compose_destroy(compose);
2553 compose->redirect_filename = filename;
2555 /* Set save folder */
2556 item = msginfo->folder;
2557 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2558 gchar *folderidentifier;
2560 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2561 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo), TRUE);
2562 folderidentifier = folder_item_get_identifier(item);
2563 compose_set_save_to(compose, folderidentifier);
2564 g_free(folderidentifier);
2567 compose_attach_parts(compose, msginfo);
2569 if (msginfo->subject)
2570 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2572 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2574 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2575 _("The body of the \"Redirect\" template has an error at line %d."));
2576 quote_fmt_reset_vartable();
2577 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2579 compose_colorize_signature(compose);
2582 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2583 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2584 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2592 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2596 if (compose->toolbar->draft_btn)
2597 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2598 if (compose->toolbar->insert_btn)
2599 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2600 if (compose->toolbar->attach_btn)
2601 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2602 if (compose->toolbar->sig_btn)
2603 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2604 if (compose->toolbar->exteditor_btn)
2605 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2606 if (compose->toolbar->linewrap_current_btn)
2607 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2608 if (compose->toolbar->linewrap_all_btn)
2609 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2610 if (compose->toolbar->privacy_sign_btn)
2611 gtk_widget_set_sensitive(compose->toolbar->privacy_sign_btn, FALSE);
2612 if (compose->toolbar->privacy_encrypt_btn)
2613 gtk_widget_set_sensitive(compose->toolbar->privacy_encrypt_btn, FALSE);
2615 compose->modified = FALSE;
2616 compose_set_title(compose);
2617 compose->updating = FALSE;
2618 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2619 SCROLL_TO_CURSOR(compose);
2621 if (compose->deferred_destroy) {
2622 compose_destroy(compose);
2626 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2631 const GList *compose_get_compose_list(void)
2633 return compose_list;
2636 void compose_entry_append(Compose *compose, const gchar *address,
2637 ComposeEntryType type, ComposePrefType pref_type)
2639 const gchar *header;
2641 gboolean in_quote = FALSE;
2642 if (!address || *address == '\0') return;
2649 header = N_("Bcc:");
2651 case COMPOSE_REPLYTO:
2652 header = N_("Reply-To:");
2654 case COMPOSE_NEWSGROUPS:
2655 header = N_("Newsgroups:");
2657 case COMPOSE_FOLLOWUPTO:
2658 header = N_( "Followup-To:");
2660 case COMPOSE_INREPLYTO:
2661 header = N_( "In-Reply-To:");
2668 header = prefs_common_translated_header_name(header);
2670 cur = begin = (gchar *)address;
2672 /* we separate the line by commas, but not if we're inside a quoted
2674 while (*cur != '\0') {
2676 in_quote = !in_quote;
2677 if (*cur == ',' && !in_quote) {
2678 gchar *tmp = g_strdup(begin);
2680 tmp[cur-begin]='\0';
2683 while (*tmp == ' ' || *tmp == '\t')
2685 compose_add_header_entry(compose, header, tmp, pref_type);
2686 compose_entry_indicate(compose, tmp);
2693 gchar *tmp = g_strdup(begin);
2695 tmp[cur-begin]='\0';
2696 while (*tmp == ' ' || *tmp == '\t')
2698 compose_add_header_entry(compose, header, tmp, pref_type);
2699 compose_entry_indicate(compose, tmp);
2704 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2710 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2711 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2712 if (gtk_entry_get_text(entry) &&
2713 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2714 /* Modify background color */
2715 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_bgcolor, color);
2716 gtk_widget_modify_base(
2717 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2718 GTK_STATE_NORMAL, &color);
2720 /* Modify foreground color */
2721 GTKUT_GDKRGBA_TO_GDKCOLOR(default_header_color, color);
2722 gtk_widget_modify_text(
2723 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2724 GTK_STATE_NORMAL, &color);
2729 void compose_toolbar_cb(gint action, gpointer data)
2731 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2732 Compose *compose = (Compose*)toolbar_item->parent;
2734 cm_return_if_fail(compose != NULL);
2738 compose_send_cb(NULL, compose);
2741 compose_send_later_cb(NULL, compose);
2744 compose_draft(compose, COMPOSE_QUIT_EDITING);
2747 compose_insert_file_cb(NULL, compose);
2750 compose_attach_cb(NULL, compose);
2753 compose_insert_sig(compose, FALSE);
2756 compose_insert_sig(compose, TRUE);
2759 compose_ext_editor_cb(NULL, compose);
2761 case A_LINEWRAP_CURRENT:
2762 compose_beautify_paragraph(compose, NULL, TRUE);
2764 case A_LINEWRAP_ALL:
2765 compose_wrap_all_full(compose, TRUE);
2768 compose_address_cb(NULL, compose);
2771 case A_CHECK_SPELLING:
2772 compose_check_all(NULL, compose);
2775 case A_PRIVACY_SIGN:
2777 case A_PRIVACY_ENCRYPT:
2784 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2789 gchar *subject = NULL;
2793 gchar **attach = NULL;
2794 gchar *inreplyto = NULL;
2795 MailField mfield = NO_FIELD_PRESENT;
2797 /* get mailto parts but skip from */
2798 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2801 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2802 mfield = TO_FIELD_PRESENT;
2805 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2807 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2809 if (!g_utf8_validate (subject, -1, NULL)) {
2810 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2811 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2814 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2816 mfield = SUBJECT_FIELD_PRESENT;
2819 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2820 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2823 gboolean prev_autowrap = compose->autowrap;
2825 compose->autowrap = FALSE;
2827 mark = gtk_text_buffer_get_insert(buffer);
2828 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2830 if (!g_utf8_validate (body, -1, NULL)) {
2831 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2832 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2835 gtk_text_buffer_insert(buffer, &iter, body, -1);
2837 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2839 compose->autowrap = prev_autowrap;
2840 if (compose->autowrap)
2841 compose_wrap_all(compose);
2842 mfield = BODY_FIELD_PRESENT;
2846 gint i = 0, att = 0;
2847 gchar *warn_files = NULL;
2848 while (attach[i] != NULL) {
2849 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2850 if (utf8_filename) {
2851 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2852 gchar *tmp = g_strdup_printf("%s%s\n",
2853 warn_files?warn_files:"",
2859 g_free(utf8_filename);
2861 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2866 alertpanel_notice(ngettext(
2867 "The following file has been attached: \n%s",
2868 "The following files have been attached: \n%s", att), warn_files);
2873 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2886 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2888 static HeaderEntry hentry[] = {
2889 {"Reply-To:", NULL, TRUE },
2890 {"Cc:", NULL, TRUE },
2891 {"References:", NULL, FALSE },
2892 {"Bcc:", NULL, TRUE },
2893 {"Newsgroups:", NULL, TRUE },
2894 {"Followup-To:", NULL, TRUE },
2895 {"List-Post:", NULL, FALSE },
2896 {"X-Priority:", NULL, FALSE },
2897 {NULL, NULL, FALSE }
2914 cm_return_val_if_fail(msginfo != NULL, -1);
2916 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
2917 procheader_get_header_fields(fp, hentry);
2920 if (hentry[H_REPLY_TO].body != NULL) {
2921 if (hentry[H_REPLY_TO].body[0] != '\0') {
2923 conv_unmime_header(hentry[H_REPLY_TO].body,
2926 g_free(hentry[H_REPLY_TO].body);
2927 hentry[H_REPLY_TO].body = NULL;
2929 if (hentry[H_CC].body != NULL) {
2930 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2931 g_free(hentry[H_CC].body);
2932 hentry[H_CC].body = NULL;
2934 if (hentry[H_REFERENCES].body != NULL) {
2935 if (compose->mode == COMPOSE_REEDIT)
2936 compose->references = hentry[H_REFERENCES].body;
2938 compose->references = compose_parse_references
2939 (hentry[H_REFERENCES].body, msginfo->msgid);
2940 g_free(hentry[H_REFERENCES].body);
2942 hentry[H_REFERENCES].body = NULL;
2944 if (hentry[H_BCC].body != NULL) {
2945 if (compose->mode == COMPOSE_REEDIT)
2947 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2948 g_free(hentry[H_BCC].body);
2949 hentry[H_BCC].body = NULL;
2951 if (hentry[H_NEWSGROUPS].body != NULL) {
2952 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2953 hentry[H_NEWSGROUPS].body = NULL;
2955 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2956 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2957 compose->followup_to =
2958 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2961 g_free(hentry[H_FOLLOWUP_TO].body);
2962 hentry[H_FOLLOWUP_TO].body = NULL;
2964 if (hentry[H_LIST_POST].body != NULL) {
2965 gchar *to = NULL, *start = NULL;
2967 extract_address(hentry[H_LIST_POST].body);
2968 if (hentry[H_LIST_POST].body[0] != '\0') {
2969 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2971 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2972 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2975 g_free(compose->ml_post);
2976 compose->ml_post = to;
2979 g_free(hentry[H_LIST_POST].body);
2980 hentry[H_LIST_POST].body = NULL;
2983 /* CLAWS - X-Priority */
2984 if (compose->mode == COMPOSE_REEDIT)
2985 if (hentry[H_X_PRIORITY].body != NULL) {
2988 priority = atoi(hentry[H_X_PRIORITY].body);
2989 g_free(hentry[H_X_PRIORITY].body);
2991 hentry[H_X_PRIORITY].body = NULL;
2993 if (priority < PRIORITY_HIGHEST ||
2994 priority > PRIORITY_LOWEST)
2995 priority = PRIORITY_NORMAL;
2997 compose->priority = priority;
3000 if (compose->mode == COMPOSE_REEDIT) {
3001 if (msginfo->inreplyto && *msginfo->inreplyto)
3002 compose->inreplyto = g_strdup(msginfo->inreplyto);
3004 if (msginfo->msgid && *msginfo->msgid &&
3005 compose->folder != NULL &&
3006 compose->folder->stype == F_DRAFT)
3007 compose->msgid = g_strdup(msginfo->msgid);
3009 if (msginfo->msgid && *msginfo->msgid)
3010 compose->inreplyto = g_strdup(msginfo->msgid);
3012 if (!compose->references) {
3013 if (msginfo->msgid && *msginfo->msgid) {
3014 if (msginfo->inreplyto && *msginfo->inreplyto)
3015 compose->references =
3016 g_strdup_printf("<%s>\n\t<%s>",
3020 compose->references =
3021 g_strconcat("<", msginfo->msgid, ">",
3023 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
3024 compose->references =
3025 g_strconcat("<", msginfo->inreplyto, ">",
3034 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3039 cm_return_val_if_fail(msginfo != NULL, -1);
3041 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL) return -1;
3042 procheader_get_header_fields(fp, entries);
3046 while (he != NULL && he->name != NULL) {
3048 GtkListStore *model = NULL;
3050 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3051 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3052 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3053 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3054 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3061 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3063 GSList *ref_id_list, *cur;
3067 ref_id_list = references_list_append(NULL, ref);
3068 if (!ref_id_list) return NULL;
3069 if (msgid && *msgid)
3070 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3075 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3076 /* "<" + Message-ID + ">" + CR+LF+TAB */
3077 len += strlen((gchar *)cur->data) + 5;
3079 if (len > MAX_REFERENCES_LEN) {
3080 /* remove second message-ID */
3081 if (ref_id_list && ref_id_list->next &&
3082 ref_id_list->next->next) {
3083 g_free(ref_id_list->next->data);
3084 ref_id_list = g_slist_remove
3085 (ref_id_list, ref_id_list->next->data);
3087 slist_free_strings_full(ref_id_list);
3094 new_ref = g_string_new("");
3095 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3096 if (new_ref->len > 0)
3097 g_string_append(new_ref, "\n\t");
3098 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3101 slist_free_strings_full(ref_id_list);
3103 new_ref_str = new_ref->str;
3104 g_string_free(new_ref, FALSE);
3109 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3110 const gchar *fmt, const gchar *qmark,
3111 const gchar *body, gboolean rewrap,
3112 gboolean need_unescape,
3113 const gchar *err_msg)
3115 MsgInfo* dummyinfo = NULL;
3116 gchar *quote_str = NULL;
3118 gboolean prev_autowrap;
3119 const gchar *trimmed_body = body;
3120 gint cursor_pos = -1;
3121 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3122 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3127 SIGNAL_BLOCK(buffer);
3130 dummyinfo = compose_msginfo_new_from_compose(compose);
3131 msginfo = dummyinfo;
3134 if (qmark != NULL) {
3136 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3137 compose->gtkaspell);
3139 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3141 quote_fmt_scan_string(qmark);
3144 buf = quote_fmt_get_buffer();
3147 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3149 Xstrdup_a(quote_str, buf, goto error)
3152 if (fmt && *fmt != '\0') {
3155 while (*trimmed_body == '\n')
3159 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3160 compose->gtkaspell);
3162 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3164 if (need_unescape) {
3167 /* decode \-escape sequences in the internal representation of the quote format */
3168 tmp = g_malloc(strlen(fmt)+1);
3169 pref_get_unescaped_pref(tmp, fmt);
3170 quote_fmt_scan_string(tmp);
3174 quote_fmt_scan_string(fmt);
3178 buf = quote_fmt_get_buffer();
3181 gint line = quote_fmt_get_line();
3182 alertpanel_error(err_msg, line);
3190 prev_autowrap = compose->autowrap;
3191 compose->autowrap = FALSE;
3193 mark = gtk_text_buffer_get_insert(buffer);
3194 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3195 if (g_utf8_validate(buf, -1, NULL)) {
3196 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3198 gchar *tmpout = NULL;
3199 tmpout = conv_codeset_strdup
3200 (buf, conv_get_locale_charset_str_no_utf8(),
3202 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3204 tmpout = g_malloc(strlen(buf)*2+1);
3205 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3207 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3211 cursor_pos = quote_fmt_get_cursor_pos();
3212 if (cursor_pos == -1)
3213 cursor_pos = gtk_text_iter_get_offset(&iter);
3214 compose->set_cursor_pos = cursor_pos;
3216 gtk_text_buffer_get_start_iter(buffer, &iter);
3217 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3218 gtk_text_buffer_place_cursor(buffer, &iter);
3220 compose->autowrap = prev_autowrap;
3221 if (compose->autowrap && rewrap)
3222 compose_wrap_all(compose);
3229 SIGNAL_UNBLOCK(buffer);
3231 procmsg_msginfo_free( &dummyinfo );
3236 /* if ml_post is of type addr@host and from is of type
3237 * addr-anything@host, return TRUE
3239 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3241 gchar *left_ml = NULL;
3242 gchar *right_ml = NULL;
3243 gchar *left_from = NULL;
3244 gchar *right_from = NULL;
3245 gboolean result = FALSE;
3247 if (!ml_post || !from)
3250 left_ml = g_strdup(ml_post);
3251 if (strstr(left_ml, "@")) {
3252 right_ml = strstr(left_ml, "@")+1;
3253 *(strstr(left_ml, "@")) = '\0';
3256 left_from = g_strdup(from);
3257 if (strstr(left_from, "@")) {
3258 right_from = strstr(left_from, "@")+1;
3259 *(strstr(left_from, "@")) = '\0';
3262 if (right_ml && right_from
3263 && !strncmp(left_from, left_ml, strlen(left_ml))
3264 && !strcmp(right_from, right_ml)) {
3273 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3274 gboolean respect_default_to)
3278 if (!folder || !folder->prefs)
3281 if (folder->prefs->enable_default_from) {
3282 gtk_entry_set_text(GTK_ENTRY(compose->from_name), folder->prefs->default_from);
3283 compose_entry_indicate(compose, folder->prefs->default_from);
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, NULL, _("_Cancel"),
3702 NULL, _("_Insert"), NULL, 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 NULL, _("_Cancel"), NULL, _("_Attach anyway"),
3830 NULL, NULL, 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)) {