2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
87 #include "quoted-printable.h"
91 #include "gtkshruler.h"
93 #include "alertpanel.h"
94 #include "manage_window.h"
96 #include "folder_item_prefs.h"
97 #include "addr_compl.h"
98 #include "quote_fmt.h"
100 #include "foldersel.h"
103 #include "message_search.h"
104 #include "combobox.h"
108 #include "autofaces.h"
109 #include "spell_entry.h"
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
140 } ComposeCallAdvancedAction;
144 PRIORITY_HIGHEST = 1,
153 COMPOSE_INSERT_SUCCESS,
154 COMPOSE_INSERT_READ_ERROR,
155 COMPOSE_INSERT_INVALID_CHARACTER,
156 COMPOSE_INSERT_NO_FILE
157 } ComposeInsertResult;
161 COMPOSE_WRITE_FOR_SEND,
162 COMPOSE_WRITE_FOR_STORE
167 COMPOSE_QUOTE_FORCED,
174 SUBJECT_FIELD_PRESENT,
179 #define B64_LINE_SIZE 57
180 #define B64_BUFFSIZE 77
182 #define MAX_REFERENCES_LEN 999
184 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
185 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
187 static GList *compose_list = NULL;
188 static GSList *extra_headers = NULL;
190 static Compose *compose_generic_new (PrefsAccount *account,
194 GList *listAddress );
196 static Compose *compose_create (PrefsAccount *account,
201 static void compose_entry_mark_default_to (Compose *compose,
202 const gchar *address);
203 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
204 ComposeQuoteMode quote_mode,
208 static Compose *compose_forward_multiple (PrefsAccount *account,
209 GSList *msginfo_list);
210 static Compose *compose_reply (MsgInfo *msginfo,
211 ComposeQuoteMode quote_mode,
216 static Compose *compose_reply_mode (ComposeMode mode,
217 GSList *msginfo_list,
219 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
220 static void compose_update_privacy_systems_menu(Compose *compose);
222 static GtkWidget *compose_account_option_menu_create
224 static void compose_set_out_encoding (Compose *compose);
225 static void compose_set_template_menu (Compose *compose);
226 static void compose_destroy (Compose *compose);
228 static MailField compose_entries_set (Compose *compose,
230 ComposeEntryType to_type);
231 static gint compose_parse_header (Compose *compose,
233 static gint compose_parse_manual_headers (Compose *compose,
235 HeaderEntry *entries);
236 static gchar *compose_parse_references (const gchar *ref,
239 static gchar *compose_quote_fmt (Compose *compose,
245 gboolean need_unescape,
246 const gchar *err_msg);
248 static void compose_reply_set_entry (Compose *compose,
254 followup_and_reply_to);
255 static void compose_reedit_set_entry (Compose *compose,
258 static void compose_insert_sig (Compose *compose,
260 static ComposeInsertResult compose_insert_file (Compose *compose,
263 static gboolean compose_attach_append (Compose *compose,
266 const gchar *content_type,
267 const gchar *charset);
268 static void compose_attach_parts (Compose *compose,
271 static gboolean compose_beautify_paragraph (Compose *compose,
272 GtkTextIter *par_iter,
274 static void compose_wrap_all (Compose *compose);
275 static void compose_wrap_all_full (Compose *compose,
278 static void compose_set_title (Compose *compose);
279 static void compose_select_account (Compose *compose,
280 PrefsAccount *account,
283 static PrefsAccount *compose_current_mail_account(void);
284 /* static gint compose_send (Compose *compose); */
285 static gboolean compose_check_for_valid_recipient
287 static gboolean compose_check_entries (Compose *compose,
288 gboolean check_everything);
289 static gint compose_write_to_file (Compose *compose,
292 gboolean attach_parts);
293 static gint compose_write_body_to_file (Compose *compose,
295 static gint compose_remove_reedit_target (Compose *compose,
297 static void compose_remove_draft (Compose *compose);
298 static gint compose_queue_sub (Compose *compose,
302 gboolean check_subject,
303 gboolean remove_reedit_target);
304 static int compose_add_attachments (Compose *compose,
306 static gchar *compose_get_header (Compose *compose);
307 static gchar *compose_get_manual_headers_info (Compose *compose);
309 static void compose_convert_header (Compose *compose,
314 gboolean addr_field);
316 static void compose_attach_info_free (AttachInfo *ainfo);
317 static void compose_attach_remove_selected (GtkAction *action,
320 static void compose_template_apply (Compose *compose,
323 static void compose_attach_property (GtkAction *action,
325 static void compose_attach_property_create (gboolean *cancelled);
326 static void attach_property_ok (GtkWidget *widget,
327 gboolean *cancelled);
328 static void attach_property_cancel (GtkWidget *widget,
329 gboolean *cancelled);
330 static gint attach_property_delete_event (GtkWidget *widget,
332 gboolean *cancelled);
333 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 gboolean *cancelled);
337 static void compose_exec_ext_editor (Compose *compose);
339 static gint compose_exec_ext_editor_real (const gchar *file);
340 static gboolean compose_ext_editor_kill (Compose *compose);
341 static gboolean compose_input_cb (GIOChannel *source,
342 GIOCondition condition,
344 static void compose_set_ext_editor_sensitive (Compose *compose,
346 #endif /* G_OS_UNIX */
348 static void compose_undo_state_changed (UndoMain *undostruct,
353 static void compose_create_header_entry (Compose *compose);
354 static void compose_add_header_entry (Compose *compose, const gchar *header,
355 gchar *text, ComposePrefType pref_type);
356 static void compose_remove_header_entries(Compose *compose);
358 static void compose_update_priority_menu_item(Compose * compose);
360 static void compose_spell_menu_changed (void *data);
361 static void compose_dict_changed (void *data);
363 static void compose_add_field_list ( Compose *compose,
364 GList *listAddress );
366 /* callback functions */
368 static void compose_notebook_size_alloc (GtkNotebook *notebook,
369 GtkAllocation *allocation,
371 static gboolean compose_edit_size_alloc (GtkEditable *widget,
372 GtkAllocation *allocation,
373 GtkSHRuler *shruler);
374 static void account_activated (GtkComboBox *optmenu,
376 static void attach_selected (GtkTreeView *tree_view,
377 GtkTreePath *tree_path,
378 GtkTreeViewColumn *column,
380 static gboolean attach_button_pressed (GtkWidget *widget,
381 GdkEventButton *event,
383 static gboolean attach_key_pressed (GtkWidget *widget,
386 static void compose_send_cb (GtkAction *action, gpointer data);
387 static void compose_send_later_cb (GtkAction *action, gpointer data);
389 static void compose_save_cb (GtkAction *action,
392 static void compose_attach_cb (GtkAction *action,
394 static void compose_insert_file_cb (GtkAction *action,
396 static void compose_insert_sig_cb (GtkAction *action,
398 static void compose_replace_sig_cb (GtkAction *action,
401 static void compose_close_cb (GtkAction *action,
403 static void compose_print_cb (GtkAction *action,
406 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
408 static void compose_address_cb (GtkAction *action,
410 static void about_show_cb (GtkAction *action,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (GtkAction *action,
418 static gint compose_delete_cb (GtkWidget *widget,
422 static void compose_undo_cb (GtkAction *action,
424 static void compose_redo_cb (GtkAction *action,
426 static void compose_cut_cb (GtkAction *action,
428 static void compose_copy_cb (GtkAction *action,
430 static void compose_paste_cb (GtkAction *action,
432 static void compose_paste_as_quote_cb (GtkAction *action,
434 static void compose_paste_no_wrap_cb (GtkAction *action,
436 static void compose_paste_wrap_cb (GtkAction *action,
438 static void compose_allsel_cb (GtkAction *action,
441 static void compose_advanced_action_cb (GtkAction *action,
444 static void compose_grab_focus_cb (GtkWidget *widget,
447 static void compose_changed_cb (GtkTextBuffer *textbuf,
450 static void compose_wrap_cb (GtkAction *action,
452 static void compose_wrap_all_cb (GtkAction *action,
454 static void compose_find_cb (GtkAction *action,
456 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
458 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
461 static void compose_toggle_ruler_cb (GtkToggleAction *action,
463 static void compose_toggle_sign_cb (GtkToggleAction *action,
465 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
467 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
468 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
469 static void activate_privacy_system (Compose *compose,
470 PrefsAccount *account,
472 static void compose_use_signing(Compose *compose, gboolean use_signing);
473 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
474 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
476 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
478 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
479 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
480 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
482 static void compose_attach_drag_received_cb (GtkWidget *widget,
483 GdkDragContext *drag_context,
486 GtkSelectionData *data,
490 static void compose_insert_drag_received_cb (GtkWidget *widget,
491 GdkDragContext *drag_context,
494 GtkSelectionData *data,
498 static void compose_header_drag_received_cb (GtkWidget *widget,
499 GdkDragContext *drag_context,
502 GtkSelectionData *data,
507 static gboolean compose_drag_drop (GtkWidget *widget,
508 GdkDragContext *drag_context,
510 guint time, gpointer user_data);
511 static gboolean completion_set_focus_to_subject
516 static void text_inserted (GtkTextBuffer *buffer,
521 static Compose *compose_generic_reply(MsgInfo *msginfo,
522 ComposeQuoteMode quote_mode,
526 gboolean followup_and_reply_to,
529 static void compose_headerentry_changed_cb (GtkWidget *entry,
530 ComposeHeaderEntry *headerentry);
531 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
533 ComposeHeaderEntry *headerentry);
534 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
535 ComposeHeaderEntry *headerentry);
537 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
539 static void compose_allow_user_actions (Compose *compose, gboolean allow);
541 static void compose_nothing_cb (GtkAction *action, gpointer data)
547 static void compose_check_all (GtkAction *action, gpointer data);
548 static void compose_highlight_all (GtkAction *action, gpointer data);
549 static void compose_check_backwards (GtkAction *action, gpointer data);
550 static void compose_check_forwards_go (GtkAction *action, gpointer data);
553 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
555 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
558 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
559 FolderItem *folder_item);
561 static void compose_attach_update_label(Compose *compose);
562 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
563 gboolean respect_default_to);
564 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
565 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
567 static GtkActionEntry compose_popup_entries[] =
569 {"Compose", NULL, "Compose" },
570 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
571 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
572 {"Compose/---", NULL, "---", NULL, NULL, NULL },
573 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
576 static GtkActionEntry compose_entries[] =
578 {"Menu", NULL, "Menu" },
580 {"Message", NULL, N_("_Message") },
581 {"Edit", NULL, N_("_Edit") },
583 {"Spelling", NULL, N_("_Spelling") },
585 {"Options", NULL, N_("_Options") },
586 {"Tools", NULL, N_("_Tools") },
587 {"Help", NULL, N_("_Help") },
589 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
590 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
591 {"Message/---", NULL, "---" },
593 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
594 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
595 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
596 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
597 /* {"Message/---", NULL, "---" }, */
598 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
599 /* {"Message/---", NULL, "---" }, */
600 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
601 /* {"Message/---", NULL, "---" }, */
602 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
605 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
606 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
607 {"Edit/---", NULL, "---" },
609 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
610 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
611 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
613 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
614 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
615 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
616 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
618 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
620 {"Edit/Advanced", NULL, N_("A_dvanced") },
621 {"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*/
622 {"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*/
623 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
624 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
625 {"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*/
626 {"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*/
627 {"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*/
628 {"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*/
629 {"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*/
630 {"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*/
631 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
632 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
633 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
634 {"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*/
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
641 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
646 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
647 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
648 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
649 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
651 {"Spelling/---", NULL, "---" },
652 {"Spelling/Options", NULL, N_("_Options") },
657 {"Options/ReplyMode", NULL, N_("Reply _mode") },
658 {"Options/---", NULL, "---" },
659 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
660 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662 /* {"Options/---", NULL, "---" }, */
664 {"Options/Priority", NULL, N_("_Priority") },
666 {"Options/Encoding", NULL, N_("Character _encoding") },
667 {"Options/Encoding/---", NULL, "---" },
668 #define ENC_ACTION(cs_char,c_char,string) \
669 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
671 {"Options/Encoding/Western", NULL, N_("Western European") },
672 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
673 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
674 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
675 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
676 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
677 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
678 {"Options/Encoding/Korean", NULL, N_("Korean") },
679 {"Options/Encoding/Thai", NULL, N_("Thai") },
682 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
684 {"Tools/Template", NULL, N_("_Template") },
685 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
686 {"Tools/Actions", NULL, N_("Actio_ns") },
687 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
690 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
693 static GtkToggleActionEntry compose_toggle_entries[] =
695 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
696 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
697 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
698 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
699 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
700 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
701 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
704 static GtkRadioActionEntry compose_radio_rm_entries[] =
706 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
707 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
708 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
709 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
712 static GtkRadioActionEntry compose_radio_prio_entries[] =
714 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
716 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
717 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
718 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
721 static GtkRadioActionEntry compose_radio_enc_entries[] =
723 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
758 static GtkTargetEntry compose_mime_types[] =
760 {"text/uri-list", 0, 0},
761 {"UTF8_STRING", 0, 0},
765 static gboolean compose_put_existing_to_front(MsgInfo *info)
767 const GList *compose_list = compose_get_compose_list();
768 const GList *elem = NULL;
771 for (elem = compose_list; elem != NULL && elem->data != NULL;
773 Compose *c = (Compose*)elem->data;
775 if (!c->targetinfo || !c->targetinfo->msgid ||
779 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
780 gtkut_window_popup(c->window);
788 static GdkColor quote_color1 =
789 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
790 static GdkColor quote_color2 =
791 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
792 static GdkColor quote_color3 =
793 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor quote_bgcolor1 =
796 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
797 static GdkColor quote_bgcolor2 =
798 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
799 static GdkColor quote_bgcolor3 =
800 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
802 static GdkColor signature_color = {
809 static GdkColor uri_color = {
816 static void compose_create_tags(GtkTextView *text, Compose *compose)
818 GtkTextBuffer *buffer;
819 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
820 #if !GTK_CHECK_VERSION(2, 24, 0)
827 buffer = gtk_text_view_get_buffer(text);
829 if (prefs_common.enable_color) {
830 /* grab the quote colors, converting from an int to a GdkColor */
831 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
833 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
835 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
837 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
839 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
841 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
843 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
845 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
848 signature_color = quote_color1 = quote_color2 = quote_color3 =
849 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
852 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
853 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
854 "foreground-gdk", "e_color1,
855 "paragraph-background-gdk", "e_bgcolor1,
857 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
858 "foreground-gdk", "e_color2,
859 "paragraph-background-gdk", "e_bgcolor2,
861 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
862 "foreground-gdk", "e_color3,
863 "paragraph-background-gdk", "e_bgcolor3,
866 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
867 "foreground-gdk", "e_color1,
869 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
870 "foreground-gdk", "e_color2,
872 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
873 "foreground-gdk", "e_color3,
877 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
878 "foreground-gdk", &signature_color,
881 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
882 "foreground-gdk", &uri_color,
884 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
885 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
887 #if !GTK_CHECK_VERSION(2, 24, 0)
888 color[0] = quote_color1;
889 color[1] = quote_color2;
890 color[2] = quote_color3;
891 color[3] = quote_bgcolor1;
892 color[4] = quote_bgcolor2;
893 color[5] = quote_bgcolor3;
894 color[6] = signature_color;
895 color[7] = uri_color;
897 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
898 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
900 for (i = 0; i < 8; i++) {
901 if (success[i] == FALSE) {
902 g_warning("Compose: color allocation failed.\n");
903 quote_color1 = quote_color2 = quote_color3 =
904 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
905 signature_color = uri_color = black;
911 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
914 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
917 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
919 return compose_generic_new(account, mailto, item, NULL, NULL);
922 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
924 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
927 #define SCROLL_TO_CURSOR(compose) { \
928 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
929 gtk_text_view_get_buffer( \
930 GTK_TEXT_VIEW(compose->text))); \
931 gtk_text_view_scroll_mark_onscreen( \
932 GTK_TEXT_VIEW(compose->text), \
936 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
939 if (folderidentifier) {
940 #if !GTK_CHECK_VERSION(2, 24, 0)
941 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
943 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
945 prefs_common.compose_save_to_history = add_history(
946 prefs_common.compose_save_to_history, folderidentifier);
947 #if !GTK_CHECK_VERSION(2, 24, 0)
948 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
949 prefs_common.compose_save_to_history);
951 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
952 prefs_common.compose_save_to_history);
956 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
957 if (folderidentifier)
958 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
960 gtk_entry_set_text(GTK_ENTRY(entry), "");
963 static gchar *compose_get_save_to(Compose *compose)
966 gchar *result = NULL;
967 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
968 result = gtk_editable_get_chars(entry, 0, -1);
971 #if !GTK_CHECK_VERSION(2, 24, 0)
972 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
974 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
976 prefs_common.compose_save_to_history = add_history(
977 prefs_common.compose_save_to_history, result);
978 #if !GTK_CHECK_VERSION(2, 24, 0)
979 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
980 prefs_common.compose_save_to_history);
982 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
983 prefs_common.compose_save_to_history);
989 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
990 GList *attach_files, GList *listAddress )
993 GtkTextView *textview;
994 GtkTextBuffer *textbuf;
996 const gchar *subject_format = NULL;
997 const gchar *body_format = NULL;
998 gchar *mailto_from = NULL;
999 PrefsAccount *mailto_account = NULL;
1000 MsgInfo* dummyinfo = NULL;
1001 gint cursor_pos = -1;
1002 MailField mfield = NO_FIELD_PRESENT;
1006 /* check if mailto defines a from */
1007 if (mailto && *mailto != '\0') {
1008 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1009 /* mailto defines a from, check if we can get account prefs from it,
1010 if not, the account prefs will be guessed using other ways, but we'll keep
1013 mailto_account = account_find_from_address(mailto_from, TRUE);
1014 if (mailto_account == NULL) {
1016 Xstrdup_a(tmp_from, mailto_from, return NULL);
1017 extract_address(tmp_from);
1018 mailto_account = account_find_from_address(tmp_from, TRUE);
1022 account = mailto_account;
1025 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1026 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1027 account = account_find_from_id(item->prefs->default_account);
1029 /* if no account prefs set, fallback to the current one */
1030 if (!account) account = cur_account;
1031 cm_return_val_if_fail(account != NULL, NULL);
1033 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1035 /* override from name if mailto asked for it */
1037 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1038 g_free(mailto_from);
1040 /* override from name according to folder properties */
1041 if (item && item->prefs &&
1042 item->prefs->compose_with_format &&
1043 item->prefs->compose_override_from_format &&
1044 *item->prefs->compose_override_from_format != '\0') {
1049 dummyinfo = compose_msginfo_new_from_compose(compose);
1051 /* decode \-escape sequences in the internal representation of the quote format */
1052 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1053 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1056 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1057 compose->gtkaspell);
1059 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1061 quote_fmt_scan_string(tmp);
1064 buf = quote_fmt_get_buffer();
1066 alertpanel_error(_("New message From format error."));
1068 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1069 quote_fmt_reset_vartable();
1074 compose->replyinfo = NULL;
1075 compose->fwdinfo = NULL;
1077 textview = GTK_TEXT_VIEW(compose->text);
1078 textbuf = gtk_text_view_get_buffer(textview);
1079 compose_create_tags(textview, compose);
1081 undo_block(compose->undostruct);
1083 compose_set_dictionaries_from_folder_prefs(compose, item);
1086 if (account->auto_sig)
1087 compose_insert_sig(compose, FALSE);
1088 gtk_text_buffer_get_start_iter(textbuf, &iter);
1089 gtk_text_buffer_place_cursor(textbuf, &iter);
1091 if (account->protocol != A_NNTP) {
1092 if (mailto && *mailto != '\0') {
1093 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1096 compose_set_folder_prefs(compose, item, TRUE);
1098 if (item && item->ret_rcpt) {
1099 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1102 if (mailto && *mailto != '\0') {
1103 if (!strchr(mailto, '@'))
1104 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1106 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1107 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1108 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1109 mfield = TO_FIELD_PRESENT;
1112 * CLAWS: just don't allow return receipt request, even if the user
1113 * may want to send an email. simple but foolproof.
1115 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1117 compose_add_field_list( compose, listAddress );
1119 if (item && item->prefs && item->prefs->compose_with_format) {
1120 subject_format = item->prefs->compose_subject_format;
1121 body_format = item->prefs->compose_body_format;
1122 } else if (account->compose_with_format) {
1123 subject_format = account->compose_subject_format;
1124 body_format = account->compose_body_format;
1125 } else if (prefs_common.compose_with_format) {
1126 subject_format = prefs_common.compose_subject_format;
1127 body_format = prefs_common.compose_body_format;
1130 if (subject_format || body_format) {
1133 && *subject_format != '\0' )
1135 gchar *subject = NULL;
1140 dummyinfo = compose_msginfo_new_from_compose(compose);
1142 /* decode \-escape sequences in the internal representation of the quote format */
1143 tmp = g_malloc(strlen(subject_format)+1);
1144 pref_get_unescaped_pref(tmp, subject_format);
1146 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1148 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1149 compose->gtkaspell);
1151 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1153 quote_fmt_scan_string(tmp);
1156 buf = quote_fmt_get_buffer();
1158 alertpanel_error(_("New message subject format error."));
1160 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1161 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1162 quote_fmt_reset_vartable();
1166 mfield = SUBJECT_FIELD_PRESENT;
1170 && *body_format != '\0' )
1173 GtkTextBuffer *buffer;
1174 GtkTextIter start, end;
1178 dummyinfo = compose_msginfo_new_from_compose(compose);
1180 text = GTK_TEXT_VIEW(compose->text);
1181 buffer = gtk_text_view_get_buffer(text);
1182 gtk_text_buffer_get_start_iter(buffer, &start);
1183 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1184 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1186 compose_quote_fmt(compose, dummyinfo,
1188 NULL, tmp, FALSE, TRUE,
1189 _("The body of the \"New message\" template has an error at line %d."));
1190 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1191 quote_fmt_reset_vartable();
1195 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1196 gtkaspell_highlight_all(compose->gtkaspell);
1198 mfield = BODY_FIELD_PRESENT;
1202 procmsg_msginfo_free( dummyinfo );
1208 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1209 ainfo = (AttachInfo *) curr->data;
1210 compose_attach_append(compose, ainfo->file, ainfo->file,
1211 ainfo->content_type, ainfo->charset);
1215 compose_show_first_last_header(compose, TRUE);
1217 /* Set save folder */
1218 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1219 gchar *folderidentifier;
1221 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1222 folderidentifier = folder_item_get_identifier(item);
1223 compose_set_save_to(compose, folderidentifier);
1224 g_free(folderidentifier);
1227 /* Place cursor according to provided input (mfield) */
1229 case NO_FIELD_PRESENT:
1230 if (compose->header_last)
1231 gtk_widget_grab_focus(compose->header_last->entry);
1233 case TO_FIELD_PRESENT:
1234 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1236 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1239 gtk_widget_grab_focus(compose->subject_entry);
1241 case SUBJECT_FIELD_PRESENT:
1242 textview = GTK_TEXT_VIEW(compose->text);
1245 textbuf = gtk_text_view_get_buffer(textview);
1248 mark = gtk_text_buffer_get_insert(textbuf);
1249 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1250 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1252 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1253 * only defers where it comes to the variable body
1254 * is not null. If no body is present compose->text
1255 * will be null in which case you cannot place the
1256 * cursor inside the component so. An empty component
1257 * is therefore created before placing the cursor
1259 case BODY_FIELD_PRESENT:
1260 cursor_pos = quote_fmt_get_cursor_pos();
1261 if (cursor_pos == -1)
1262 gtk_widget_grab_focus(compose->header_last->entry);
1264 gtk_widget_grab_focus(compose->text);
1268 undo_unblock(compose->undostruct);
1270 if (prefs_common.auto_exteditor)
1271 compose_exec_ext_editor(compose);
1273 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1275 SCROLL_TO_CURSOR(compose);
1277 compose->modified = FALSE;
1278 compose_set_title(compose);
1280 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1285 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1286 gboolean override_pref, const gchar *system)
1288 const gchar *privacy = NULL;
1290 cm_return_if_fail(compose != NULL);
1291 cm_return_if_fail(account != NULL);
1293 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1296 if (account->default_privacy_system && strlen(account->default_privacy_system))
1297 privacy = account->default_privacy_system;
1301 GSList *privacy_avail = privacy_get_system_ids();
1302 if (privacy_avail && g_slist_length(privacy_avail)) {
1303 privacy = (gchar *)(privacy_avail->data);
1306 if (privacy != NULL) {
1308 g_free(compose->privacy_system);
1309 compose->privacy_system = NULL;
1311 if (compose->privacy_system == NULL)
1312 compose->privacy_system = g_strdup(privacy);
1313 else if (*(compose->privacy_system) == '\0') {
1314 g_free(compose->privacy_system);
1315 compose->privacy_system = g_strdup(privacy);
1317 compose_update_privacy_system_menu_item(compose, FALSE);
1318 compose_use_encryption(compose, TRUE);
1322 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1324 const gchar *privacy = NULL;
1326 if (account->default_privacy_system && strlen(account->default_privacy_system))
1327 privacy = account->default_privacy_system;
1331 GSList *privacy_avail = privacy_get_system_ids();
1332 if (privacy_avail && g_slist_length(privacy_avail)) {
1333 privacy = (gchar *)(privacy_avail->data);
1337 if (privacy != NULL) {
1339 g_free(compose->privacy_system);
1340 compose->privacy_system = NULL;
1342 if (compose->privacy_system == NULL)
1343 compose->privacy_system = g_strdup(privacy);
1344 compose_update_privacy_system_menu_item(compose, FALSE);
1345 compose_use_signing(compose, TRUE);
1349 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1353 Compose *compose = NULL;
1355 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1357 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1358 cm_return_val_if_fail(msginfo != NULL, NULL);
1360 list_len = g_slist_length(msginfo_list);
1364 case COMPOSE_REPLY_TO_ADDRESS:
1365 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1366 FALSE, prefs_common.default_reply_list, FALSE, body);
1368 case COMPOSE_REPLY_WITH_QUOTE:
1369 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1370 FALSE, prefs_common.default_reply_list, FALSE, body);
1372 case COMPOSE_REPLY_WITHOUT_QUOTE:
1373 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1374 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1376 case COMPOSE_REPLY_TO_SENDER:
1377 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1378 FALSE, FALSE, TRUE, body);
1380 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1381 compose = compose_followup_and_reply_to(msginfo,
1382 COMPOSE_QUOTE_CHECK,
1383 FALSE, FALSE, body);
1385 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1386 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1387 FALSE, FALSE, TRUE, body);
1389 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1391 FALSE, FALSE, TRUE, NULL);
1393 case COMPOSE_REPLY_TO_ALL:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1395 TRUE, FALSE, FALSE, body);
1397 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1399 TRUE, FALSE, FALSE, body);
1401 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1403 TRUE, FALSE, FALSE, NULL);
1405 case COMPOSE_REPLY_TO_LIST:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1407 FALSE, TRUE, FALSE, body);
1409 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1411 FALSE, TRUE, FALSE, body);
1413 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1414 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1415 FALSE, TRUE, FALSE, NULL);
1417 case COMPOSE_FORWARD:
1418 if (prefs_common.forward_as_attachment) {
1419 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1422 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1426 case COMPOSE_FORWARD_INLINE:
1427 /* check if we reply to more than one Message */
1428 if (list_len == 1) {
1429 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1432 /* more messages FALL THROUGH */
1433 case COMPOSE_FORWARD_AS_ATTACH:
1434 compose = compose_forward_multiple(NULL, msginfo_list);
1436 case COMPOSE_REDIRECT:
1437 compose = compose_redirect(NULL, msginfo, FALSE);
1440 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1443 if (compose == NULL) {
1444 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1448 compose->rmode = mode;
1449 switch (compose->rmode) {
1451 case COMPOSE_REPLY_WITH_QUOTE:
1452 case COMPOSE_REPLY_WITHOUT_QUOTE:
1453 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1454 debug_print("reply mode Normal\n");
1455 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1456 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1458 case COMPOSE_REPLY_TO_SENDER:
1459 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1460 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1461 debug_print("reply mode Sender\n");
1462 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1464 case COMPOSE_REPLY_TO_ALL:
1465 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1466 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1467 debug_print("reply mode All\n");
1468 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1470 case COMPOSE_REPLY_TO_LIST:
1471 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1472 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1473 debug_print("reply mode List\n");
1474 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1476 case COMPOSE_REPLY_TO_ADDRESS:
1477 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1485 static Compose *compose_reply(MsgInfo *msginfo,
1486 ComposeQuoteMode quote_mode,
1492 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1493 to_sender, FALSE, body);
1496 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1497 ComposeQuoteMode quote_mode,
1502 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1503 to_sender, TRUE, body);
1506 static void compose_extract_original_charset(Compose *compose)
1508 MsgInfo *info = NULL;
1509 if (compose->replyinfo) {
1510 info = compose->replyinfo;
1511 } else if (compose->fwdinfo) {
1512 info = compose->fwdinfo;
1513 } else if (compose->targetinfo) {
1514 info = compose->targetinfo;
1517 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1518 MimeInfo *partinfo = mimeinfo;
1519 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1520 partinfo = procmime_mimeinfo_next(partinfo);
1522 compose->orig_charset =
1523 g_strdup(procmime_mimeinfo_get_parameter(
1524 partinfo, "charset"));
1526 procmime_mimeinfo_free_all(mimeinfo);
1530 #define SIGNAL_BLOCK(buffer) { \
1531 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1532 G_CALLBACK(compose_changed_cb), \
1534 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1535 G_CALLBACK(text_inserted), \
1539 #define SIGNAL_UNBLOCK(buffer) { \
1540 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1541 G_CALLBACK(compose_changed_cb), \
1543 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1544 G_CALLBACK(text_inserted), \
1548 static Compose *compose_generic_reply(MsgInfo *msginfo,
1549 ComposeQuoteMode quote_mode,
1550 gboolean to_all, gboolean to_ml,
1552 gboolean followup_and_reply_to,
1556 PrefsAccount *account = NULL;
1557 GtkTextView *textview;
1558 GtkTextBuffer *textbuf;
1559 gboolean quote = FALSE;
1560 const gchar *qmark = NULL;
1561 const gchar *body_fmt = NULL;
1562 gchar *s_system = NULL;
1564 cm_return_val_if_fail(msginfo != NULL, NULL);
1565 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1567 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1569 cm_return_val_if_fail(account != NULL, NULL);
1571 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1573 compose->updating = TRUE;
1575 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1578 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1579 if (!compose->replyinfo)
1580 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1582 compose_extract_original_charset(compose);
1584 if (msginfo->folder && msginfo->folder->ret_rcpt)
1585 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1587 /* Set save folder */
1588 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1589 gchar *folderidentifier;
1591 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1592 folderidentifier = folder_item_get_identifier(msginfo->folder);
1593 compose_set_save_to(compose, folderidentifier);
1594 g_free(folderidentifier);
1597 if (compose_parse_header(compose, msginfo) < 0) {
1598 compose->updating = FALSE;
1599 compose_destroy(compose);
1603 /* override from name according to folder properties */
1604 if (msginfo->folder && msginfo->folder->prefs &&
1605 msginfo->folder->prefs->reply_with_format &&
1606 msginfo->folder->prefs->reply_override_from_format &&
1607 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1612 /* decode \-escape sequences in the internal representation of the quote format */
1613 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1614 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1617 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1618 compose->gtkaspell);
1620 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1622 quote_fmt_scan_string(tmp);
1625 buf = quote_fmt_get_buffer();
1627 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1629 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1630 quote_fmt_reset_vartable();
1635 textview = (GTK_TEXT_VIEW(compose->text));
1636 textbuf = gtk_text_view_get_buffer(textview);
1637 compose_create_tags(textview, compose);
1639 undo_block(compose->undostruct);
1641 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1642 gtkaspell_block_check(compose->gtkaspell);
1645 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1646 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1647 /* use the reply format of folder (if enabled), or the account's one
1648 (if enabled) or fallback to the global reply format, which is always
1649 enabled (even if empty), and use the relevant quotemark */
1651 if (msginfo->folder && msginfo->folder->prefs &&
1652 msginfo->folder->prefs->reply_with_format) {
1653 qmark = msginfo->folder->prefs->reply_quotemark;
1654 body_fmt = msginfo->folder->prefs->reply_body_format;
1656 } else if (account->reply_with_format) {
1657 qmark = account->reply_quotemark;
1658 body_fmt = account->reply_body_format;
1661 qmark = prefs_common.quotemark;
1662 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1663 body_fmt = gettext(prefs_common.quotefmt);
1670 /* empty quotemark is not allowed */
1671 if (qmark == NULL || *qmark == '\0')
1673 compose_quote_fmt(compose, compose->replyinfo,
1674 body_fmt, qmark, body, FALSE, TRUE,
1675 _("The body of the \"Reply\" template has an error at line %d."));
1676 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1677 quote_fmt_reset_vartable();
1680 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1681 compose_force_encryption(compose, account, FALSE, s_system);
1684 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1685 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1686 compose_force_signing(compose, account, s_system);
1690 SIGNAL_BLOCK(textbuf);
1692 if (account->auto_sig)
1693 compose_insert_sig(compose, FALSE);
1695 compose_wrap_all(compose);
1698 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1699 gtkaspell_highlight_all(compose->gtkaspell);
1700 gtkaspell_unblock_check(compose->gtkaspell);
1702 SIGNAL_UNBLOCK(textbuf);
1704 gtk_widget_grab_focus(compose->text);
1706 undo_unblock(compose->undostruct);
1708 if (prefs_common.auto_exteditor)
1709 compose_exec_ext_editor(compose);
1711 compose->modified = FALSE;
1712 compose_set_title(compose);
1714 compose->updating = FALSE;
1715 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1716 SCROLL_TO_CURSOR(compose);
1718 if (compose->deferred_destroy) {
1719 compose_destroy(compose);
1727 #define INSERT_FW_HEADER(var, hdr) \
1728 if (msginfo->var && *msginfo->var) { \
1729 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1731 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1734 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1735 gboolean as_attach, const gchar *body,
1736 gboolean no_extedit,
1740 GtkTextView *textview;
1741 GtkTextBuffer *textbuf;
1742 gint cursor_pos = -1;
1745 cm_return_val_if_fail(msginfo != NULL, NULL);
1746 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1749 !(account = compose_guess_forward_account_from_msginfo
1751 account = cur_account;
1753 if (!prefs_common.forward_as_attachment)
1754 mode = COMPOSE_FORWARD_INLINE;
1756 mode = COMPOSE_FORWARD;
1757 compose = compose_create(account, msginfo->folder, mode, batch);
1759 compose->updating = TRUE;
1760 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1761 if (!compose->fwdinfo)
1762 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1764 compose_extract_original_charset(compose);
1766 if (msginfo->subject && *msginfo->subject) {
1767 gchar *buf, *buf2, *p;
1769 buf = p = g_strdup(msginfo->subject);
1770 p += subject_get_prefix_length(p);
1771 memmove(buf, p, strlen(p) + 1);
1773 buf2 = g_strdup_printf("Fw: %s", buf);
1774 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1780 /* override from name according to folder properties */
1781 if (msginfo->folder && msginfo->folder->prefs &&
1782 msginfo->folder->prefs->forward_with_format &&
1783 msginfo->folder->prefs->forward_override_from_format &&
1784 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1788 MsgInfo *full_msginfo = NULL;
1791 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1793 full_msginfo = procmsg_msginfo_copy(msginfo);
1795 /* decode \-escape sequences in the internal representation of the quote format */
1796 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1797 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1800 gtkaspell_block_check(compose->gtkaspell);
1801 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1802 compose->gtkaspell);
1804 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1806 quote_fmt_scan_string(tmp);
1809 buf = quote_fmt_get_buffer();
1811 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1813 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1814 quote_fmt_reset_vartable();
1817 procmsg_msginfo_free(full_msginfo);
1820 textview = GTK_TEXT_VIEW(compose->text);
1821 textbuf = gtk_text_view_get_buffer(textview);
1822 compose_create_tags(textview, compose);
1824 undo_block(compose->undostruct);
1828 msgfile = procmsg_get_message_file(msginfo);
1829 if (!is_file_exist(msgfile))
1830 g_warning("%s: file not exist\n", msgfile);
1832 compose_attach_append(compose, msgfile, msgfile,
1833 "message/rfc822", NULL);
1837 const gchar *qmark = NULL;
1838 const gchar *body_fmt = NULL;
1839 MsgInfo *full_msginfo;
1841 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1843 full_msginfo = procmsg_msginfo_copy(msginfo);
1845 /* use the forward format of folder (if enabled), or the account's one
1846 (if enabled) or fallback to the global forward format, which is always
1847 enabled (even if empty), and use the relevant quotemark */
1848 if (msginfo->folder && msginfo->folder->prefs &&
1849 msginfo->folder->prefs->forward_with_format) {
1850 qmark = msginfo->folder->prefs->forward_quotemark;
1851 body_fmt = msginfo->folder->prefs->forward_body_format;
1853 } else if (account->forward_with_format) {
1854 qmark = account->forward_quotemark;
1855 body_fmt = account->forward_body_format;
1858 qmark = prefs_common.fw_quotemark;
1859 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1860 body_fmt = gettext(prefs_common.fw_quotefmt);
1865 /* empty quotemark is not allowed */
1866 if (qmark == NULL || *qmark == '\0')
1869 compose_quote_fmt(compose, full_msginfo,
1870 body_fmt, qmark, body, FALSE, TRUE,
1871 _("The body of the \"Forward\" template has an error at line %d."));
1872 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1873 quote_fmt_reset_vartable();
1874 compose_attach_parts(compose, msginfo);
1876 procmsg_msginfo_free(full_msginfo);
1879 SIGNAL_BLOCK(textbuf);
1881 if (account->auto_sig)
1882 compose_insert_sig(compose, FALSE);
1884 compose_wrap_all(compose);
1887 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1888 gtkaspell_highlight_all(compose->gtkaspell);
1889 gtkaspell_unblock_check(compose->gtkaspell);
1891 SIGNAL_UNBLOCK(textbuf);
1893 cursor_pos = quote_fmt_get_cursor_pos();
1894 if (cursor_pos == -1)
1895 gtk_widget_grab_focus(compose->header_last->entry);
1897 gtk_widget_grab_focus(compose->text);
1899 if (!no_extedit && prefs_common.auto_exteditor)
1900 compose_exec_ext_editor(compose);
1903 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1904 gchar *folderidentifier;
1906 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1907 folderidentifier = folder_item_get_identifier(msginfo->folder);
1908 compose_set_save_to(compose, folderidentifier);
1909 g_free(folderidentifier);
1912 undo_unblock(compose->undostruct);
1914 compose->modified = FALSE;
1915 compose_set_title(compose);
1917 compose->updating = FALSE;
1918 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1919 SCROLL_TO_CURSOR(compose);
1921 if (compose->deferred_destroy) {
1922 compose_destroy(compose);
1926 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1931 #undef INSERT_FW_HEADER
1933 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1936 GtkTextView *textview;
1937 GtkTextBuffer *textbuf;
1941 gboolean single_mail = TRUE;
1943 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1945 if (g_slist_length(msginfo_list) > 1)
1946 single_mail = FALSE;
1948 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1949 if (((MsgInfo *)msginfo->data)->folder == NULL)
1952 /* guess account from first selected message */
1954 !(account = compose_guess_forward_account_from_msginfo
1955 (msginfo_list->data)))
1956 account = cur_account;
1958 cm_return_val_if_fail(account != NULL, NULL);
1960 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1961 if (msginfo->data) {
1962 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1963 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1967 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1968 g_warning("no msginfo_list");
1972 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1974 compose->updating = TRUE;
1976 /* override from name according to folder properties */
1977 if (msginfo_list->data) {
1978 MsgInfo *msginfo = msginfo_list->data;
1980 if (msginfo->folder && msginfo->folder->prefs &&
1981 msginfo->folder->prefs->forward_with_format &&
1982 msginfo->folder->prefs->forward_override_from_format &&
1983 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1988 /* decode \-escape sequences in the internal representation of the quote format */
1989 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1990 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1993 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1994 compose->gtkaspell);
1996 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1998 quote_fmt_scan_string(tmp);
2001 buf = quote_fmt_get_buffer();
2003 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2005 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2006 quote_fmt_reset_vartable();
2012 textview = GTK_TEXT_VIEW(compose->text);
2013 textbuf = gtk_text_view_get_buffer(textview);
2014 compose_create_tags(textview, compose);
2016 undo_block(compose->undostruct);
2017 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2018 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2020 if (!is_file_exist(msgfile))
2021 g_warning("%s: file not exist\n", msgfile);
2023 compose_attach_append(compose, msgfile, msgfile,
2024 "message/rfc822", NULL);
2029 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2030 if (info->subject && *info->subject) {
2031 gchar *buf, *buf2, *p;
2033 buf = p = g_strdup(info->subject);
2034 p += subject_get_prefix_length(p);
2035 memmove(buf, p, strlen(p) + 1);
2037 buf2 = g_strdup_printf("Fw: %s", buf);
2038 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2044 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2045 _("Fw: multiple emails"));
2048 SIGNAL_BLOCK(textbuf);
2050 if (account->auto_sig)
2051 compose_insert_sig(compose, FALSE);
2053 compose_wrap_all(compose);
2055 SIGNAL_UNBLOCK(textbuf);
2057 gtk_text_buffer_get_start_iter(textbuf, &iter);
2058 gtk_text_buffer_place_cursor(textbuf, &iter);
2060 gtk_widget_grab_focus(compose->header_last->entry);
2061 undo_unblock(compose->undostruct);
2062 compose->modified = FALSE;
2063 compose_set_title(compose);
2065 compose->updating = FALSE;
2066 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2067 SCROLL_TO_CURSOR(compose);
2069 if (compose->deferred_destroy) {
2070 compose_destroy(compose);
2074 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2079 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2081 GtkTextIter start = *iter;
2082 GtkTextIter end_iter;
2083 int start_pos = gtk_text_iter_get_offset(&start);
2085 if (!compose->account->sig_sep)
2088 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2089 start_pos+strlen(compose->account->sig_sep));
2091 /* check sig separator */
2092 str = gtk_text_iter_get_text(&start, &end_iter);
2093 if (!strcmp(str, compose->account->sig_sep)) {
2095 /* check end of line (\n) */
2096 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2097 start_pos+strlen(compose->account->sig_sep));
2098 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2099 start_pos+strlen(compose->account->sig_sep)+1);
2100 tmp = gtk_text_iter_get_text(&start, &end_iter);
2101 if (!strcmp(tmp,"\n")) {
2113 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2115 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2116 Compose *compose = (Compose *)data;
2117 FolderItem *old_item = NULL;
2118 FolderItem *new_item = NULL;
2119 gchar *old_id, *new_id;
2121 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2122 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2125 old_item = hookdata->item;
2126 new_item = hookdata->item2;
2128 old_id = folder_item_get_identifier(old_item);
2129 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2131 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2132 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2133 compose->targetinfo->folder = new_item;
2136 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2137 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2138 compose->replyinfo->folder = new_item;
2141 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2142 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2143 compose->fwdinfo->folder = new_item;
2151 static void compose_colorize_signature(Compose *compose)
2153 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2155 GtkTextIter end_iter;
2156 gtk_text_buffer_get_start_iter(buffer, &iter);
2157 while (gtk_text_iter_forward_line(&iter))
2158 if (compose_is_sig_separator(compose, buffer, &iter)) {
2159 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2160 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2164 #define BLOCK_WRAP() { \
2165 prev_autowrap = compose->autowrap; \
2166 buffer = gtk_text_view_get_buffer( \
2167 GTK_TEXT_VIEW(compose->text)); \
2168 compose->autowrap = FALSE; \
2170 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2171 G_CALLBACK(compose_changed_cb), \
2173 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2174 G_CALLBACK(text_inserted), \
2177 #define UNBLOCK_WRAP() { \
2178 compose->autowrap = prev_autowrap; \
2179 if (compose->autowrap) { \
2180 gint old = compose->draft_timeout_tag; \
2181 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2182 compose_wrap_all(compose); \
2183 compose->draft_timeout_tag = old; \
2186 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2187 G_CALLBACK(compose_changed_cb), \
2189 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2190 G_CALLBACK(text_inserted), \
2194 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2196 Compose *compose = NULL;
2197 PrefsAccount *account = NULL;
2198 GtkTextView *textview;
2199 GtkTextBuffer *textbuf;
2203 gchar buf[BUFFSIZE];
2204 gboolean use_signing = FALSE;
2205 gboolean use_encryption = FALSE;
2206 gchar *privacy_system = NULL;
2207 int priority = PRIORITY_NORMAL;
2208 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2209 gboolean autowrap = prefs_common.autowrap;
2210 gboolean autoindent = prefs_common.auto_indent;
2211 HeaderEntry *manual_headers = NULL;
2213 cm_return_val_if_fail(msginfo != NULL, NULL);
2214 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2216 if (compose_put_existing_to_front(msginfo)) {
2220 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2221 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2222 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2223 gchar queueheader_buf[BUFFSIZE];
2226 /* Select Account from queue headers */
2227 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2228 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2229 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2230 account = account_find_from_id(id);
2232 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2233 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2234 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2235 account = account_find_from_id(id);
2237 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2238 sizeof(queueheader_buf), "NAID:")) {
2239 id = atoi(&queueheader_buf[strlen("NAID:")]);
2240 account = account_find_from_id(id);
2242 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2243 sizeof(queueheader_buf), "MAID:")) {
2244 id = atoi(&queueheader_buf[strlen("MAID:")]);
2245 account = account_find_from_id(id);
2247 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2248 sizeof(queueheader_buf), "S:")) {
2249 account = account_find_from_address(queueheader_buf, FALSE);
2251 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2252 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2253 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2254 use_signing = param;
2257 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2258 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2259 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2260 use_signing = param;
2263 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2264 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2265 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2266 use_encryption = param;
2268 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2269 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2270 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2271 use_encryption = param;
2273 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2274 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2275 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2278 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2279 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2280 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2283 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2284 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2285 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2287 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2288 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2289 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2291 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2292 sizeof(queueheader_buf), "X-Priority: ")) {
2293 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2296 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2297 sizeof(queueheader_buf), "RMID:")) {
2298 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2299 if (tokens[0] && tokens[1] && tokens[2]) {
2300 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2301 if (orig_item != NULL) {
2302 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2307 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2308 sizeof(queueheader_buf), "FMID:")) {
2309 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2310 if (tokens[0] && tokens[1] && tokens[2]) {
2311 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2312 if (orig_item != NULL) {
2313 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2318 /* Get manual headers */
2319 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2320 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2321 if (*listmh != '\0') {
2322 debug_print("Got manual headers: %s\n", listmh);
2323 manual_headers = procheader_entries_from_str(listmh);
2328 account = msginfo->folder->folder->account;
2331 if (!account && prefs_common.reedit_account_autosel) {
2332 gchar from[BUFFSIZE];
2333 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2334 extract_address(from);
2335 account = account_find_from_address(from, FALSE);
2339 account = cur_account;
2341 cm_return_val_if_fail(account != NULL, NULL);
2343 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2345 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2346 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2347 compose->autowrap = autowrap;
2348 compose->replyinfo = replyinfo;
2349 compose->fwdinfo = fwdinfo;
2351 compose->updating = TRUE;
2352 compose->priority = priority;
2354 if (privacy_system != NULL) {
2355 compose->privacy_system = privacy_system;
2356 compose_use_signing(compose, use_signing);
2357 compose_use_encryption(compose, use_encryption);
2358 compose_update_privacy_system_menu_item(compose, FALSE);
2360 activate_privacy_system(compose, account, FALSE);
2363 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2365 compose_extract_original_charset(compose);
2367 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2368 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2369 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2370 gchar queueheader_buf[BUFFSIZE];
2372 /* Set message save folder */
2373 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2374 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2375 compose_set_save_to(compose, &queueheader_buf[4]);
2377 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2378 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2380 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2385 if (compose_parse_header(compose, msginfo) < 0) {
2386 compose->updating = FALSE;
2387 compose_destroy(compose);
2390 compose_reedit_set_entry(compose, msginfo);
2392 textview = GTK_TEXT_VIEW(compose->text);
2393 textbuf = gtk_text_view_get_buffer(textview);
2394 compose_create_tags(textview, compose);
2396 mark = gtk_text_buffer_get_insert(textbuf);
2397 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2399 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2400 G_CALLBACK(compose_changed_cb),
2403 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2404 fp = procmime_get_first_encrypted_text_content(msginfo);
2406 compose_force_encryption(compose, account, TRUE, NULL);
2409 fp = procmime_get_first_text_content(msginfo);
2412 g_warning("Can't get text part\n");
2416 gboolean prev_autowrap;
2417 GtkTextBuffer *buffer;
2419 while (fgets(buf, sizeof(buf), fp) != NULL) {
2421 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2427 compose_attach_parts(compose, msginfo);
2429 compose_colorize_signature(compose);
2431 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2432 G_CALLBACK(compose_changed_cb),
2435 if (manual_headers != NULL) {
2436 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2437 procheader_entries_free(manual_headers);
2438 compose->updating = FALSE;
2439 compose_destroy(compose);
2442 procheader_entries_free(manual_headers);
2445 gtk_widget_grab_focus(compose->text);
2447 if (prefs_common.auto_exteditor) {
2448 compose_exec_ext_editor(compose);
2450 compose->modified = FALSE;
2451 compose_set_title(compose);
2453 compose->updating = FALSE;
2454 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2455 SCROLL_TO_CURSOR(compose);
2457 if (compose->deferred_destroy) {
2458 compose_destroy(compose);
2462 compose->sig_str = account_get_signature_str(compose->account);
2464 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2469 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2476 cm_return_val_if_fail(msginfo != NULL, NULL);
2479 account = account_get_reply_account(msginfo,
2480 prefs_common.reply_account_autosel);
2481 cm_return_val_if_fail(account != NULL, NULL);
2483 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2485 compose->updating = TRUE;
2487 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2488 compose->replyinfo = NULL;
2489 compose->fwdinfo = NULL;
2491 compose_show_first_last_header(compose, TRUE);
2493 gtk_widget_grab_focus(compose->header_last->entry);
2495 filename = procmsg_get_message_file(msginfo);
2497 if (filename == NULL) {
2498 compose->updating = FALSE;
2499 compose_destroy(compose);
2504 compose->redirect_filename = filename;
2506 /* Set save folder */
2507 item = msginfo->folder;
2508 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2509 gchar *folderidentifier;
2511 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2512 folderidentifier = folder_item_get_identifier(item);
2513 compose_set_save_to(compose, folderidentifier);
2514 g_free(folderidentifier);
2517 compose_attach_parts(compose, msginfo);
2519 if (msginfo->subject)
2520 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2522 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2524 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2525 _("The body of the \"Redirect\" template has an error at line %d."));
2526 quote_fmt_reset_vartable();
2527 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2529 compose_colorize_signature(compose);
2532 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2533 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2534 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2543 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2544 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2546 if (compose->toolbar->draft_btn)
2547 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2548 if (compose->toolbar->insert_btn)
2549 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2550 if (compose->toolbar->attach_btn)
2551 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2552 if (compose->toolbar->sig_btn)
2553 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2554 if (compose->toolbar->exteditor_btn)
2555 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2556 if (compose->toolbar->linewrap_current_btn)
2557 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2558 if (compose->toolbar->linewrap_all_btn)
2559 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2561 compose->modified = FALSE;
2562 compose_set_title(compose);
2563 compose->updating = FALSE;
2564 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2565 SCROLL_TO_CURSOR(compose);
2567 if (compose->deferred_destroy) {
2568 compose_destroy(compose);
2572 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2577 const GList *compose_get_compose_list(void)
2579 return compose_list;
2582 void compose_entry_append(Compose *compose, const gchar *address,
2583 ComposeEntryType type, ComposePrefType pref_type)
2585 const gchar *header;
2587 gboolean in_quote = FALSE;
2588 if (!address || *address == '\0') return;
2595 header = N_("Bcc:");
2597 case COMPOSE_REPLYTO:
2598 header = N_("Reply-To:");
2600 case COMPOSE_NEWSGROUPS:
2601 header = N_("Newsgroups:");
2603 case COMPOSE_FOLLOWUPTO:
2604 header = N_( "Followup-To:");
2606 case COMPOSE_INREPLYTO:
2607 header = N_( "In-Reply-To:");
2614 header = prefs_common_translated_header_name(header);
2616 cur = begin = (gchar *)address;
2618 /* we separate the line by commas, but not if we're inside a quoted
2620 while (*cur != '\0') {
2622 in_quote = !in_quote;
2623 if (*cur == ',' && !in_quote) {
2624 gchar *tmp = g_strdup(begin);
2626 tmp[cur-begin]='\0';
2629 while (*tmp == ' ' || *tmp == '\t')
2631 compose_add_header_entry(compose, header, tmp, pref_type);
2638 gchar *tmp = g_strdup(begin);
2640 tmp[cur-begin]='\0';
2641 while (*tmp == ' ' || *tmp == '\t')
2643 compose_add_header_entry(compose, header, tmp, pref_type);
2648 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2650 #if !GTK_CHECK_VERSION(3, 0, 0)
2651 static GdkColor yellow;
2652 static GdkColor black;
2653 static gboolean yellow_initialised = FALSE;
2655 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2656 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2661 #if !GTK_CHECK_VERSION(3, 0, 0)
2662 if (!yellow_initialised) {
2663 gdk_color_parse("#f5f6be", &yellow);
2664 gdk_color_parse("#000000", &black);
2665 yellow_initialised = gdk_colormap_alloc_color(
2666 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2667 yellow_initialised &= gdk_colormap_alloc_color(
2668 gdk_colormap_get_system(), &black, FALSE, TRUE);
2672 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2673 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2674 if (gtk_entry_get_text(entry) &&
2675 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2676 #if !GTK_CHECK_VERSION(3, 0, 0)
2677 if (yellow_initialised) {
2679 gtk_widget_modify_base(
2680 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2681 GTK_STATE_NORMAL, &yellow);
2682 gtk_widget_modify_text(
2683 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2684 GTK_STATE_NORMAL, &black);
2685 #if !GTK_CHECK_VERSION(3, 0, 0)
2692 void compose_toolbar_cb(gint action, gpointer data)
2694 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2695 Compose *compose = (Compose*)toolbar_item->parent;
2697 cm_return_if_fail(compose != NULL);
2701 compose_send_cb(NULL, compose);
2704 compose_send_later_cb(NULL, compose);
2707 compose_draft(compose, COMPOSE_QUIT_EDITING);
2710 compose_insert_file_cb(NULL, compose);
2713 compose_attach_cb(NULL, compose);
2716 compose_insert_sig(compose, FALSE);
2719 compose_insert_sig(compose, TRUE);
2722 compose_ext_editor_cb(NULL, compose);
2724 case A_LINEWRAP_CURRENT:
2725 compose_beautify_paragraph(compose, NULL, TRUE);
2727 case A_LINEWRAP_ALL:
2728 compose_wrap_all_full(compose, TRUE);
2731 compose_address_cb(NULL, compose);
2734 case A_CHECK_SPELLING:
2735 compose_check_all(NULL, compose);
2743 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2748 gchar *subject = NULL;
2752 gchar **attach = NULL;
2753 gchar *inreplyto = NULL;
2754 MailField mfield = NO_FIELD_PRESENT;
2756 /* get mailto parts but skip from */
2757 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2760 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2761 mfield = TO_FIELD_PRESENT;
2764 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2766 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2768 if (!g_utf8_validate (subject, -1, NULL)) {
2769 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2770 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2775 mfield = SUBJECT_FIELD_PRESENT;
2778 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2779 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2782 gboolean prev_autowrap = compose->autowrap;
2784 compose->autowrap = FALSE;
2786 mark = gtk_text_buffer_get_insert(buffer);
2787 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2789 if (!g_utf8_validate (body, -1, NULL)) {
2790 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2791 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2794 gtk_text_buffer_insert(buffer, &iter, body, -1);
2796 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2798 compose->autowrap = prev_autowrap;
2799 if (compose->autowrap)
2800 compose_wrap_all(compose);
2801 mfield = BODY_FIELD_PRESENT;
2805 gint i = 0, att = 0;
2806 gchar *warn_files = NULL;
2807 while (attach[i] != NULL) {
2808 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2809 if (utf8_filename) {
2810 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2811 gchar *tmp = g_strdup_printf("%s%s\n",
2812 warn_files?warn_files:"",
2818 g_free(utf8_filename);
2820 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2825 alertpanel_notice(ngettext(
2826 "The following file has been attached: \n%s",
2827 "The following files have been attached: \n%s", att), warn_files);
2832 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2845 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2847 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2848 {"Cc:", NULL, TRUE},
2849 {"References:", NULL, FALSE},
2850 {"Bcc:", NULL, TRUE},
2851 {"Newsgroups:", NULL, TRUE},
2852 {"Followup-To:", NULL, TRUE},
2853 {"List-Post:", NULL, FALSE},
2854 {"X-Priority:", NULL, FALSE},
2855 {NULL, NULL, FALSE}};
2871 cm_return_val_if_fail(msginfo != NULL, -1);
2873 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2874 procheader_get_header_fields(fp, hentry);
2877 if (hentry[H_REPLY_TO].body != NULL) {
2878 if (hentry[H_REPLY_TO].body[0] != '\0') {
2880 conv_unmime_header(hentry[H_REPLY_TO].body,
2883 g_free(hentry[H_REPLY_TO].body);
2884 hentry[H_REPLY_TO].body = NULL;
2886 if (hentry[H_CC].body != NULL) {
2887 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2888 g_free(hentry[H_CC].body);
2889 hentry[H_CC].body = NULL;
2891 if (hentry[H_REFERENCES].body != NULL) {
2892 if (compose->mode == COMPOSE_REEDIT)
2893 compose->references = hentry[H_REFERENCES].body;
2895 compose->references = compose_parse_references
2896 (hentry[H_REFERENCES].body, msginfo->msgid);
2897 g_free(hentry[H_REFERENCES].body);
2899 hentry[H_REFERENCES].body = NULL;
2901 if (hentry[H_BCC].body != NULL) {
2902 if (compose->mode == COMPOSE_REEDIT)
2904 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2905 g_free(hentry[H_BCC].body);
2906 hentry[H_BCC].body = NULL;
2908 if (hentry[H_NEWSGROUPS].body != NULL) {
2909 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2910 hentry[H_NEWSGROUPS].body = NULL;
2912 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2913 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2914 compose->followup_to =
2915 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2918 g_free(hentry[H_FOLLOWUP_TO].body);
2919 hentry[H_FOLLOWUP_TO].body = NULL;
2921 if (hentry[H_LIST_POST].body != NULL) {
2922 gchar *to = NULL, *start = NULL;
2924 extract_address(hentry[H_LIST_POST].body);
2925 if (hentry[H_LIST_POST].body[0] != '\0') {
2926 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2928 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2929 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2932 g_free(compose->ml_post);
2933 compose->ml_post = to;
2936 g_free(hentry[H_LIST_POST].body);
2937 hentry[H_LIST_POST].body = NULL;
2940 /* CLAWS - X-Priority */
2941 if (compose->mode == COMPOSE_REEDIT)
2942 if (hentry[H_X_PRIORITY].body != NULL) {
2945 priority = atoi(hentry[H_X_PRIORITY].body);
2946 g_free(hentry[H_X_PRIORITY].body);
2948 hentry[H_X_PRIORITY].body = NULL;
2950 if (priority < PRIORITY_HIGHEST ||
2951 priority > PRIORITY_LOWEST)
2952 priority = PRIORITY_NORMAL;
2954 compose->priority = priority;
2957 if (compose->mode == COMPOSE_REEDIT) {
2958 if (msginfo->inreplyto && *msginfo->inreplyto)
2959 compose->inreplyto = g_strdup(msginfo->inreplyto);
2963 if (msginfo->msgid && *msginfo->msgid)
2964 compose->inreplyto = g_strdup(msginfo->msgid);
2966 if (!compose->references) {
2967 if (msginfo->msgid && *msginfo->msgid) {
2968 if (msginfo->inreplyto && *msginfo->inreplyto)
2969 compose->references =
2970 g_strdup_printf("<%s>\n\t<%s>",
2974 compose->references =
2975 g_strconcat("<", msginfo->msgid, ">",
2977 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2978 compose->references =
2979 g_strconcat("<", msginfo->inreplyto, ">",
2987 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2992 cm_return_val_if_fail(msginfo != NULL, -1);
2994 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2995 procheader_get_header_fields(fp, entries);
2999 while (he != NULL && he->name != NULL) {
3001 GtkListStore *model = NULL;
3003 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3004 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3005 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3006 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3007 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3014 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3016 GSList *ref_id_list, *cur;
3020 ref_id_list = references_list_append(NULL, ref);
3021 if (!ref_id_list) return NULL;
3022 if (msgid && *msgid)
3023 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3028 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3029 /* "<" + Message-ID + ">" + CR+LF+TAB */
3030 len += strlen((gchar *)cur->data) + 5;
3032 if (len > MAX_REFERENCES_LEN) {
3033 /* remove second message-ID */
3034 if (ref_id_list && ref_id_list->next &&
3035 ref_id_list->next->next) {
3036 g_free(ref_id_list->next->data);
3037 ref_id_list = g_slist_remove
3038 (ref_id_list, ref_id_list->next->data);
3040 slist_free_strings_full(ref_id_list);
3047 new_ref = g_string_new("");
3048 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3049 if (new_ref->len > 0)
3050 g_string_append(new_ref, "\n\t");
3051 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3054 slist_free_strings_full(ref_id_list);
3056 new_ref_str = new_ref->str;
3057 g_string_free(new_ref, FALSE);
3062 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3063 const gchar *fmt, const gchar *qmark,
3064 const gchar *body, gboolean rewrap,
3065 gboolean need_unescape,
3066 const gchar *err_msg)
3068 MsgInfo* dummyinfo = NULL;
3069 gchar *quote_str = NULL;
3071 gboolean prev_autowrap;
3072 const gchar *trimmed_body = body;
3073 gint cursor_pos = -1;
3074 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3075 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3080 SIGNAL_BLOCK(buffer);
3083 dummyinfo = compose_msginfo_new_from_compose(compose);
3084 msginfo = dummyinfo;
3087 if (qmark != NULL) {
3089 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3090 compose->gtkaspell);
3092 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3094 quote_fmt_scan_string(qmark);
3097 buf = quote_fmt_get_buffer();
3099 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3101 Xstrdup_a(quote_str, buf, goto error)
3104 if (fmt && *fmt != '\0') {
3107 while (*trimmed_body == '\n')
3111 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3112 compose->gtkaspell);
3114 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3116 if (need_unescape) {
3119 /* decode \-escape sequences in the internal representation of the quote format */
3120 tmp = g_malloc(strlen(fmt)+1);
3121 pref_get_unescaped_pref(tmp, fmt);
3122 quote_fmt_scan_string(tmp);
3126 quote_fmt_scan_string(fmt);
3130 buf = quote_fmt_get_buffer();
3132 gint line = quote_fmt_get_line();
3133 alertpanel_error(err_msg, line);
3139 prev_autowrap = compose->autowrap;
3140 compose->autowrap = FALSE;
3142 mark = gtk_text_buffer_get_insert(buffer);
3143 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3144 if (g_utf8_validate(buf, -1, NULL)) {
3145 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3147 gchar *tmpout = NULL;
3148 tmpout = conv_codeset_strdup
3149 (buf, conv_get_locale_charset_str_no_utf8(),
3151 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3153 tmpout = g_malloc(strlen(buf)*2+1);
3154 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3156 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3160 cursor_pos = quote_fmt_get_cursor_pos();
3161 if (cursor_pos == -1)
3162 cursor_pos = gtk_text_iter_get_offset(&iter);
3163 compose->set_cursor_pos = cursor_pos;
3165 gtk_text_buffer_get_start_iter(buffer, &iter);
3166 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3167 gtk_text_buffer_place_cursor(buffer, &iter);
3169 compose->autowrap = prev_autowrap;
3170 if (compose->autowrap && rewrap)
3171 compose_wrap_all(compose);
3178 SIGNAL_UNBLOCK(buffer);
3180 procmsg_msginfo_free( dummyinfo );
3185 /* if ml_post is of type addr@host and from is of type
3186 * addr-anything@host, return TRUE
3188 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3190 gchar *left_ml = NULL;
3191 gchar *right_ml = NULL;
3192 gchar *left_from = NULL;
3193 gchar *right_from = NULL;
3194 gboolean result = FALSE;
3196 if (!ml_post || !from)
3199 left_ml = g_strdup(ml_post);
3200 if (strstr(left_ml, "@")) {
3201 right_ml = strstr(left_ml, "@")+1;
3202 *(strstr(left_ml, "@")) = '\0';
3205 left_from = g_strdup(from);
3206 if (strstr(left_from, "@")) {
3207 right_from = strstr(left_from, "@")+1;
3208 *(strstr(left_from, "@")) = '\0';
3211 if (right_ml && right_from
3212 && !strncmp(left_from, left_ml, strlen(left_ml))
3213 && !strcmp(right_from, right_ml)) {
3222 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3223 gboolean respect_default_to)
3227 if (!folder || !folder->prefs)
3230 if (respect_default_to && folder->prefs->enable_default_to) {
3231 compose_entry_append(compose, folder->prefs->default_to,
3232 COMPOSE_TO, PREF_FOLDER);
3233 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3235 if (folder->prefs->enable_default_cc)
3236 compose_entry_append(compose, folder->prefs->default_cc,
3237 COMPOSE_CC, PREF_FOLDER);
3238 if (folder->prefs->enable_default_bcc)
3239 compose_entry_append(compose, folder->prefs->default_bcc,
3240 COMPOSE_BCC, PREF_FOLDER);
3241 if (folder->prefs->enable_default_replyto)
3242 compose_entry_append(compose, folder->prefs->default_replyto,
3243 COMPOSE_REPLYTO, PREF_FOLDER);
3246 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3251 if (!compose || !msginfo)
3254 if (msginfo->subject && *msginfo->subject) {
3255 buf = p = g_strdup(msginfo->subject);
3256 p += subject_get_prefix_length(p);
3257 memmove(buf, p, strlen(p) + 1);
3259 buf2 = g_strdup_printf("Re: %s", buf);
3260 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3265 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3268 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3269 gboolean to_all, gboolean to_ml,
3271 gboolean followup_and_reply_to)
3273 GSList *cc_list = NULL;
3276 gchar *replyto = NULL;
3277 gchar *ac_email = NULL;
3279 gboolean reply_to_ml = FALSE;
3280 gboolean default_reply_to = FALSE;
3282 cm_return_if_fail(compose->account != NULL);
3283 cm_return_if_fail(msginfo != NULL);
3285 reply_to_ml = to_ml && compose->ml_post;
3287 default_reply_to = msginfo->folder &&
3288 msginfo->folder->prefs->enable_default_reply_to;
3290 if (compose->account->protocol != A_NNTP) {
3291 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3293 if (reply_to_ml && !default_reply_to) {
3295 gboolean is_subscr = is_subscription(compose->ml_post,
3298 /* normal answer to ml post with a reply-to */
3299 compose_entry_append(compose,
3301 COMPOSE_TO, PREF_ML);
3302 if (compose->replyto)
3303 compose_entry_append(compose,
3305 COMPOSE_CC, PREF_ML);
3307 /* answer to subscription confirmation */
3308 if (compose->replyto)
3309 compose_entry_append(compose,
3311 COMPOSE_TO, PREF_ML);
3312 else if (msginfo->from)
3313 compose_entry_append(compose,
3315 COMPOSE_TO, PREF_ML);
3318 else if (!(to_all || to_sender) && default_reply_to) {
3319 compose_entry_append(compose,
3320 msginfo->folder->prefs->default_reply_to,
3321 COMPOSE_TO, PREF_FOLDER);
3322 compose_entry_mark_default_to(compose,
3323 msginfo->folder->prefs->default_reply_to);
3329 compose_entry_append(compose, msginfo->from,
3330 COMPOSE_TO, PREF_NONE);
3332 Xstrdup_a(tmp1, msginfo->from, return);
3333 extract_address(tmp1);
3334 compose_entry_append(compose,
3335 (!account_find_from_address(tmp1, FALSE))
3338 COMPOSE_TO, PREF_NONE);
3340 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3341 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3342 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3343 if (compose->replyto) {
3344 compose_entry_append(compose,
3346 COMPOSE_TO, PREF_NONE);
3348 compose_entry_append(compose,
3349 msginfo->from ? msginfo->from : "",
3350 COMPOSE_TO, PREF_NONE);
3353 /* replying to own mail, use original recp */
3354 compose_entry_append(compose,
3355 msginfo->to ? msginfo->to : "",
3356 COMPOSE_TO, PREF_NONE);
3357 compose_entry_append(compose,
3358 msginfo->cc ? msginfo->cc : "",
3359 COMPOSE_CC, PREF_NONE);
3364 if (to_sender || (compose->followup_to &&
3365 !strncmp(compose->followup_to, "poster", 6)))
3366 compose_entry_append
3368 (compose->replyto ? compose->replyto :
3369 msginfo->from ? msginfo->from : ""),
3370 COMPOSE_TO, PREF_NONE);
3372 else if (followup_and_reply_to || to_all) {
3373 compose_entry_append
3375 (compose->replyto ? compose->replyto :
3376 msginfo->from ? msginfo->from : ""),
3377 COMPOSE_TO, PREF_NONE);
3379 compose_entry_append
3381 compose->followup_to ? compose->followup_to :
3382 compose->newsgroups ? compose->newsgroups : "",
3383 COMPOSE_NEWSGROUPS, PREF_NONE);
3386 compose_entry_append
3388 compose->followup_to ? compose->followup_to :
3389 compose->newsgroups ? compose->newsgroups : "",
3390 COMPOSE_NEWSGROUPS, PREF_NONE);
3392 compose_reply_set_subject(compose, msginfo);
3394 if (to_ml && compose->ml_post) return;
3395 if (!to_all || compose->account->protocol == A_NNTP) return;
3397 if (compose->replyto) {
3398 Xstrdup_a(replyto, compose->replyto, return);
3399 extract_address(replyto);
3401 if (msginfo->from) {
3402 Xstrdup_a(from, msginfo->from, return);
3403 extract_address(from);
3406 if (replyto && from)
3407 cc_list = address_list_append_with_comments(cc_list, from);
3408 if (to_all && msginfo->folder &&
3409 msginfo->folder->prefs->enable_default_reply_to)
3410 cc_list = address_list_append_with_comments(cc_list,
3411 msginfo->folder->prefs->default_reply_to);
3412 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3413 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3415 ac_email = g_utf8_strdown(compose->account->address, -1);
3418 for (cur = cc_list; cur != NULL; cur = cur->next) {
3419 gchar *addr = g_utf8_strdown(cur->data, -1);
3420 extract_address(addr);
3422 if (strcmp(ac_email, addr))
3423 compose_entry_append(compose, (gchar *)cur->data,
3424 COMPOSE_CC, PREF_NONE);
3426 debug_print("Cc address same as compose account's, ignoring\n");
3431 slist_free_strings_full(cc_list);
3437 #define SET_ENTRY(entry, str) \
3440 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3443 #define SET_ADDRESS(type, str) \
3446 compose_entry_append(compose, str, type, PREF_NONE); \
3449 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3451 cm_return_if_fail(msginfo != NULL);
3453 SET_ENTRY(subject_entry, msginfo->subject);
3454 SET_ENTRY(from_name, msginfo->from);
3455 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3456 SET_ADDRESS(COMPOSE_CC, compose->cc);
3457 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3458 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3459 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3460 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3462 compose_update_priority_menu_item(compose);
3463 compose_update_privacy_system_menu_item(compose, FALSE);
3464 compose_show_first_last_header(compose, TRUE);
3470 static void compose_insert_sig(Compose *compose, gboolean replace)
3472 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3473 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3475 GtkTextIter iter, iter_end;
3476 gint cur_pos, ins_pos;
3477 gboolean prev_autowrap;
3478 gboolean found = FALSE;
3479 gboolean exists = FALSE;
3481 cm_return_if_fail(compose->account != NULL);
3485 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3486 G_CALLBACK(compose_changed_cb),
3489 mark = gtk_text_buffer_get_insert(buffer);
3490 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3491 cur_pos = gtk_text_iter_get_offset (&iter);
3494 gtk_text_buffer_get_end_iter(buffer, &iter);
3496 exists = (compose->sig_str != NULL);
3499 GtkTextIter first_iter, start_iter, end_iter;
3501 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3503 if (!exists || compose->sig_str[0] == '\0')
3506 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3507 compose->signature_tag);
3510 /* include previous \n\n */
3511 gtk_text_iter_backward_chars(&first_iter, 1);
3512 start_iter = first_iter;
3513 end_iter = first_iter;
3515 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3516 compose->signature_tag);
3517 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3518 compose->signature_tag);
3520 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3526 g_free(compose->sig_str);
3527 compose->sig_str = account_get_signature_str(compose->account);
3529 cur_pos = gtk_text_iter_get_offset(&iter);
3531 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3532 g_free(compose->sig_str);
3533 compose->sig_str = NULL;
3535 if (compose->sig_inserted == FALSE)
3536 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3537 compose->sig_inserted = TRUE;
3539 cur_pos = gtk_text_iter_get_offset(&iter);
3540 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3542 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3543 gtk_text_iter_forward_chars(&iter, 1);
3544 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3545 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3547 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3548 cur_pos = gtk_text_buffer_get_char_count (buffer);
3551 /* put the cursor where it should be
3552 * either where the quote_fmt says, either where it was */
3553 if (compose->set_cursor_pos < 0)
3554 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3556 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3557 compose->set_cursor_pos);
3559 compose->set_cursor_pos = -1;
3560 gtk_text_buffer_place_cursor(buffer, &iter);
3561 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3562 G_CALLBACK(compose_changed_cb),
3568 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3571 GtkTextBuffer *buffer;
3574 const gchar *cur_encoding;
3575 gchar buf[BUFFSIZE];
3578 gboolean prev_autowrap;
3579 struct stat file_stat;
3581 GString *file_contents = NULL;
3582 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3584 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3586 /* get the size of the file we are about to insert */
3587 ret = g_stat(file, &file_stat);
3589 gchar *shortfile = g_path_get_basename(file);
3590 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3592 return COMPOSE_INSERT_NO_FILE;
3593 } else if (prefs_common.warn_large_insert == TRUE) {
3595 /* ask user for confirmation if the file is large */
3596 if (prefs_common.warn_large_insert_size < 0 ||
3597 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3601 msg = g_strdup_printf(_("You are about to insert a file of %s "
3602 "in the message body. Are you sure you want to do that?"),
3603 to_human_readable(file_stat.st_size));
3604 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3605 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3608 /* do we ask for confirmation next time? */
3609 if (aval & G_ALERTDISABLE) {
3610 /* no confirmation next time, disable feature in preferences */
3611 aval &= ~G_ALERTDISABLE;
3612 prefs_common.warn_large_insert = FALSE;
3615 /* abort file insertion if user canceled action */
3616 if (aval != G_ALERTALTERNATE) {
3617 return COMPOSE_INSERT_NO_FILE;
3623 if ((fp = g_fopen(file, "rb")) == NULL) {
3624 FILE_OP_ERROR(file, "fopen");
3625 return COMPOSE_INSERT_READ_ERROR;
3628 prev_autowrap = compose->autowrap;
3629 compose->autowrap = FALSE;
3631 text = GTK_TEXT_VIEW(compose->text);
3632 buffer = gtk_text_view_get_buffer(text);
3633 mark = gtk_text_buffer_get_insert(buffer);
3634 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3636 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3637 G_CALLBACK(text_inserted),
3640 cur_encoding = conv_get_locale_charset_str_no_utf8();
3642 file_contents = g_string_new("");
3643 while (fgets(buf, sizeof(buf), fp) != NULL) {
3646 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3647 str = g_strdup(buf);
3649 codeconv_set_strict(TRUE);
3650 str = conv_codeset_strdup
3651 (buf, cur_encoding, CS_INTERNAL);
3652 codeconv_set_strict(FALSE);
3655 result = COMPOSE_INSERT_INVALID_CHARACTER;
3661 /* strip <CR> if DOS/Windows file,
3662 replace <CR> with <LF> if Macintosh file. */
3665 if (len > 0 && str[len - 1] != '\n') {
3667 if (str[len] == '\r') str[len] = '\n';
3670 file_contents = g_string_append(file_contents, str);
3674 if (result == COMPOSE_INSERT_SUCCESS) {
3675 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3677 compose_changed_cb(NULL, compose);
3678 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3679 G_CALLBACK(text_inserted),
3681 compose->autowrap = prev_autowrap;
3682 if (compose->autowrap)
3683 compose_wrap_all(compose);
3686 g_string_free(file_contents, TRUE);
3692 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3693 const gchar *filename,
3694 const gchar *content_type,
3695 const gchar *charset)
3703 GtkListStore *store;
3705 gboolean has_binary = FALSE;
3707 if (!is_file_exist(file)) {
3708 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3709 gboolean result = FALSE;
3710 if (file_from_uri && is_file_exist(file_from_uri)) {
3711 result = compose_attach_append(
3712 compose, file_from_uri,
3713 filename, content_type,
3716 g_free(file_from_uri);
3719 alertpanel_error("File %s doesn't exist\n", filename);
3722 if ((size = get_file_size(file)) < 0) {
3723 alertpanel_error("Can't get file size of %s\n", filename);
3727 /* In batch mode, we allow 0-length files to be attached no questions asked */
3728 if (size == 0 && !compose->batch) {
3729 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3730 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3731 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3732 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3735 if (aval != G_ALERTALTERNATE) {
3739 if ((fp = g_fopen(file, "rb")) == NULL) {
3740 alertpanel_error(_("Can't read %s."), filename);
3745 ainfo = g_new0(AttachInfo, 1);
3746 auto_ainfo = g_auto_pointer_new_with_free
3747 (ainfo, (GFreeFunc) compose_attach_info_free);
3748 ainfo->file = g_strdup(file);
3751 ainfo->content_type = g_strdup(content_type);
3752 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3754 MsgFlags flags = {0, 0};
3756 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3757 ainfo->encoding = ENC_7BIT;
3759 ainfo->encoding = ENC_8BIT;
3761 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3762 if (msginfo && msginfo->subject)
3763 name = g_strdup(msginfo->subject);
3765 name = g_path_get_basename(filename ? filename : file);
3767 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3769 procmsg_msginfo_free(msginfo);
3771 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3772 ainfo->charset = g_strdup(charset);
3773 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3775 ainfo->encoding = ENC_BASE64;
3777 name = g_path_get_basename(filename ? filename : file);
3778 ainfo->name = g_strdup(name);
3782 ainfo->content_type = procmime_get_mime_type(file);
3783 if (!ainfo->content_type) {
3784 ainfo->content_type =
3785 g_strdup("application/octet-stream");
3786 ainfo->encoding = ENC_BASE64;
3787 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3789 procmime_get_encoding_for_text_file(file, &has_binary);
3791 ainfo->encoding = ENC_BASE64;
3792 name = g_path_get_basename(filename ? filename : file);
3793 ainfo->name = g_strdup(name);
3797 if (ainfo->name != NULL
3798 && !strcmp(ainfo->name, ".")) {
3799 g_free(ainfo->name);
3803 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3804 g_free(ainfo->content_type);
3805 ainfo->content_type = g_strdup("application/octet-stream");
3806 g_free(ainfo->charset);
3807 ainfo->charset = NULL;
3810 ainfo->size = (goffset)size;
3811 size_text = to_human_readable((goffset)size);
3813 store = GTK_LIST_STORE(gtk_tree_view_get_model
3814 (GTK_TREE_VIEW(compose->attach_clist)));
3816 gtk_list_store_append(store, &iter);
3817 gtk_list_store_set(store, &iter,
3818 COL_MIMETYPE, ainfo->content_type,
3819 COL_SIZE, size_text,
3820 COL_NAME, ainfo->name,
3821 COL_CHARSET, ainfo->charset,
3823 COL_AUTODATA, auto_ainfo,
3826 g_auto_pointer_free(auto_ainfo);
3827 compose_attach_update_label(compose);
3831 static void compose_use_signing(Compose *compose, gboolean use_signing)
3833 compose->use_signing = use_signing;
3834 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3837 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3839 compose->use_encryption = use_encryption;
3840 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3843 #define NEXT_PART_NOT_CHILD(info) \
3845 node = info->node; \
3846 while (node->children) \
3847 node = g_node_last_child(node); \
3848 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3851 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3855 MimeInfo *firsttext = NULL;
3856 MimeInfo *encrypted = NULL;
3859 const gchar *partname = NULL;
3861 mimeinfo = procmime_scan_message(msginfo);
3862 if (!mimeinfo) return;
3864 if (mimeinfo->node->children == NULL) {
3865 procmime_mimeinfo_free_all(mimeinfo);
3869 /* find first content part */
3870 child = (MimeInfo *) mimeinfo->node->children->data;
3871 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3872 child = (MimeInfo *)child->node->children->data;
3875 if (child->type == MIMETYPE_TEXT) {
3877 debug_print("First text part found\n");
3878 } else if (compose->mode == COMPOSE_REEDIT &&
3879 child->type == MIMETYPE_APPLICATION &&
3880 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3881 encrypted = (MimeInfo *)child->node->parent->data;
3884 child = (MimeInfo *) mimeinfo->node->children->data;
3885 while (child != NULL) {
3888 if (child == encrypted) {
3889 /* skip this part of tree */
3890 NEXT_PART_NOT_CHILD(child);
3894 if (child->type == MIMETYPE_MULTIPART) {
3895 /* get the actual content */
3896 child = procmime_mimeinfo_next(child);
3900 if (child == firsttext) {
3901 child = procmime_mimeinfo_next(child);
3905 outfile = procmime_get_tmp_file_name(child);
3906 if ((err = procmime_get_part(outfile, child)) < 0)
3907 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3909 gchar *content_type;
3911 content_type = procmime_get_content_type_str(child->type, child->subtype);
3913 /* if we meet a pgp signature, we don't attach it, but
3914 * we force signing. */
3915 if ((strcmp(content_type, "application/pgp-signature") &&
3916 strcmp(content_type, "application/pkcs7-signature") &&
3917 strcmp(content_type, "application/x-pkcs7-signature"))
3918 || compose->mode == COMPOSE_REDIRECT) {
3919 partname = procmime_mimeinfo_get_parameter(child, "filename");
3920 if (partname == NULL)
3921 partname = procmime_mimeinfo_get_parameter(child, "name");
3922 if (partname == NULL)
3924 compose_attach_append(compose, outfile,
3925 partname, content_type,
3926 procmime_mimeinfo_get_parameter(child, "charset"));
3928 compose_force_signing(compose, compose->account, NULL);
3930 g_free(content_type);
3933 NEXT_PART_NOT_CHILD(child);
3935 procmime_mimeinfo_free_all(mimeinfo);
3938 #undef NEXT_PART_NOT_CHILD
3943 WAIT_FOR_INDENT_CHAR,
3944 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3947 /* return indent length, we allow:
3948 indent characters followed by indent characters or spaces/tabs,
3949 alphabets and numbers immediately followed by indent characters,
3950 and the repeating sequences of the above
3951 If quote ends with multiple spaces, only the first one is included. */
3952 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3953 const GtkTextIter *start, gint *len)
3955 GtkTextIter iter = *start;
3959 IndentState state = WAIT_FOR_INDENT_CHAR;
3962 gint alnum_count = 0;
3963 gint space_count = 0;
3966 if (prefs_common.quote_chars == NULL) {
3970 while (!gtk_text_iter_ends_line(&iter)) {
3971 wc = gtk_text_iter_get_char(&iter);
3972 if (g_unichar_iswide(wc))
3974 clen = g_unichar_to_utf8(wc, ch);
3978 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3979 is_space = g_unichar_isspace(wc);
3981 if (state == WAIT_FOR_INDENT_CHAR) {
3982 if (!is_indent && !g_unichar_isalnum(wc))
3985 quote_len += alnum_count + space_count + 1;
3986 alnum_count = space_count = 0;
3987 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3990 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3991 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3995 else if (is_indent) {
3996 quote_len += alnum_count + space_count + 1;
3997 alnum_count = space_count = 0;
4000 state = WAIT_FOR_INDENT_CHAR;
4004 gtk_text_iter_forward_char(&iter);
4007 if (quote_len > 0 && space_count > 0)
4013 if (quote_len > 0) {
4015 gtk_text_iter_forward_chars(&iter, quote_len);
4016 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4022 /* return >0 if the line is itemized */
4023 static int compose_itemized_length(GtkTextBuffer *buffer,
4024 const GtkTextIter *start)
4026 GtkTextIter iter = *start;
4031 if (gtk_text_iter_ends_line(&iter))
4036 wc = gtk_text_iter_get_char(&iter);
4037 if (!g_unichar_isspace(wc))
4039 gtk_text_iter_forward_char(&iter);
4040 if (gtk_text_iter_ends_line(&iter))
4044 clen = g_unichar_to_utf8(wc, ch);
4048 if (!strchr("*-+", ch[0]))
4051 gtk_text_iter_forward_char(&iter);
4052 if (gtk_text_iter_ends_line(&iter))
4054 wc = gtk_text_iter_get_char(&iter);
4055 if (g_unichar_isspace(wc)) {
4061 /* return the string at the start of the itemization */
4062 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4063 const GtkTextIter *start)
4065 GtkTextIter iter = *start;
4068 GString *item_chars = g_string_new("");
4071 if (gtk_text_iter_ends_line(&iter))
4076 wc = gtk_text_iter_get_char(&iter);
4077 if (!g_unichar_isspace(wc))
4079 gtk_text_iter_forward_char(&iter);
4080 if (gtk_text_iter_ends_line(&iter))
4082 g_string_append_unichar(item_chars, wc);
4085 str = item_chars->str;
4086 g_string_free(item_chars, FALSE);
4090 /* return the number of spaces at a line's start */
4091 static int compose_left_offset_length(GtkTextBuffer *buffer,
4092 const GtkTextIter *start)
4094 GtkTextIter iter = *start;
4097 if (gtk_text_iter_ends_line(&iter))
4101 wc = gtk_text_iter_get_char(&iter);
4102 if (!g_unichar_isspace(wc))
4105 gtk_text_iter_forward_char(&iter);
4106 if (gtk_text_iter_ends_line(&iter))
4110 gtk_text_iter_forward_char(&iter);
4111 if (gtk_text_iter_ends_line(&iter))
4116 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4117 const GtkTextIter *start,
4118 GtkTextIter *break_pos,
4122 GtkTextIter iter = *start, line_end = *start;
4123 PangoLogAttr *attrs;
4130 gboolean can_break = FALSE;
4131 gboolean do_break = FALSE;
4132 gboolean was_white = FALSE;
4133 gboolean prev_dont_break = FALSE;
4135 gtk_text_iter_forward_to_line_end(&line_end);
4136 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4137 len = g_utf8_strlen(str, -1);
4141 g_warning("compose_get_line_break_pos: len = 0!\n");
4145 /* g_print("breaking line: %d: %s (len = %d)\n",
4146 gtk_text_iter_get_line(&iter), str, len); */
4148 attrs = g_new(PangoLogAttr, len + 1);
4150 pango_default_break(str, -1, NULL, attrs, len + 1);
4154 /* skip quote and leading spaces */
4155 for (i = 0; *p != '\0' && i < len; i++) {
4158 wc = g_utf8_get_char(p);
4159 if (i >= quote_len && !g_unichar_isspace(wc))
4161 if (g_unichar_iswide(wc))
4163 else if (*p == '\t')
4167 p = g_utf8_next_char(p);
4170 for (; *p != '\0' && i < len; i++) {
4171 PangoLogAttr *attr = attrs + i;
4175 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4178 was_white = attr->is_white;
4180 /* don't wrap URI */
4181 if ((uri_len = get_uri_len(p)) > 0) {
4183 if (pos > 0 && col > max_col) {
4193 wc = g_utf8_get_char(p);
4194 if (g_unichar_iswide(wc)) {
4196 if (prev_dont_break && can_break && attr->is_line_break)
4198 } else if (*p == '\t')
4202 if (pos > 0 && col > max_col) {
4207 if (*p == '-' || *p == '/')
4208 prev_dont_break = TRUE;
4210 prev_dont_break = FALSE;
4212 p = g_utf8_next_char(p);
4216 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4221 *break_pos = *start;
4222 gtk_text_iter_set_line_offset(break_pos, pos);
4227 static gboolean compose_join_next_line(Compose *compose,
4228 GtkTextBuffer *buffer,
4230 const gchar *quote_str)
4232 GtkTextIter iter_ = *iter, cur, prev, next, end;
4233 PangoLogAttr attrs[3];
4235 gchar *next_quote_str;
4238 gboolean keep_cursor = FALSE;
4240 if (!gtk_text_iter_forward_line(&iter_) ||
4241 gtk_text_iter_ends_line(&iter_)) {
4244 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4246 if ((quote_str || next_quote_str) &&
4247 strcmp2(quote_str, next_quote_str) != 0) {
4248 g_free(next_quote_str);
4251 g_free(next_quote_str);
4254 if (quote_len > 0) {
4255 gtk_text_iter_forward_chars(&end, quote_len);
4256 if (gtk_text_iter_ends_line(&end)) {
4261 /* don't join itemized lines */
4262 if (compose_itemized_length(buffer, &end) > 0) {
4266 /* don't join signature separator */
4267 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4270 /* delete quote str */
4272 gtk_text_buffer_delete(buffer, &iter_, &end);
4274 /* don't join line breaks put by the user */
4276 gtk_text_iter_backward_char(&cur);
4277 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4278 gtk_text_iter_forward_char(&cur);
4282 gtk_text_iter_forward_char(&cur);
4283 /* delete linebreak and extra spaces */
4284 while (gtk_text_iter_backward_char(&cur)) {
4285 wc1 = gtk_text_iter_get_char(&cur);
4286 if (!g_unichar_isspace(wc1))
4291 while (!gtk_text_iter_ends_line(&cur)) {
4292 wc1 = gtk_text_iter_get_char(&cur);
4293 if (!g_unichar_isspace(wc1))
4295 gtk_text_iter_forward_char(&cur);
4298 if (!gtk_text_iter_equal(&prev, &next)) {
4301 mark = gtk_text_buffer_get_insert(buffer);
4302 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4303 if (gtk_text_iter_equal(&prev, &cur))
4305 gtk_text_buffer_delete(buffer, &prev, &next);
4309 /* insert space if required */
4310 gtk_text_iter_backward_char(&prev);
4311 wc1 = gtk_text_iter_get_char(&prev);
4312 wc2 = gtk_text_iter_get_char(&next);
4313 gtk_text_iter_forward_char(&next);
4314 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4315 pango_default_break(str, -1, NULL, attrs, 3);
4316 if (!attrs[1].is_line_break ||
4317 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4318 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4320 gtk_text_iter_backward_char(&iter_);
4321 gtk_text_buffer_place_cursor(buffer, &iter_);
4330 #define ADD_TXT_POS(bp_, ep_, pti_) \
4331 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4332 last = last->next; \
4333 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4334 last->next = NULL; \
4336 g_warning("alloc error scanning URIs\n"); \
4339 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4341 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4342 GtkTextBuffer *buffer;
4343 GtkTextIter iter, break_pos, end_of_line;
4344 gchar *quote_str = NULL;
4346 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4347 gboolean prev_autowrap = compose->autowrap;
4348 gint startq_offset = -1, noq_offset = -1;
4349 gint uri_start = -1, uri_stop = -1;
4350 gint nouri_start = -1, nouri_stop = -1;
4351 gint num_blocks = 0;
4352 gint quotelevel = -1;
4353 gboolean modified = force;
4354 gboolean removed = FALSE;
4355 gboolean modified_before_remove = FALSE;
4357 gboolean start = TRUE;
4358 gint itemized_len = 0, rem_item_len = 0;
4359 gchar *itemized_chars = NULL;
4360 gboolean item_continuation = FALSE;
4365 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4369 compose->autowrap = FALSE;
4371 buffer = gtk_text_view_get_buffer(text);
4372 undo_wrapping(compose->undostruct, TRUE);
4377 mark = gtk_text_buffer_get_insert(buffer);
4378 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4382 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4383 if (gtk_text_iter_ends_line(&iter)) {
4384 while (gtk_text_iter_ends_line(&iter) &&
4385 gtk_text_iter_forward_line(&iter))
4388 while (gtk_text_iter_backward_line(&iter)) {
4389 if (gtk_text_iter_ends_line(&iter)) {
4390 gtk_text_iter_forward_line(&iter);
4396 /* move to line start */
4397 gtk_text_iter_set_line_offset(&iter, 0);
4400 itemized_len = compose_itemized_length(buffer, &iter);
4402 if (!itemized_len) {
4403 itemized_len = compose_left_offset_length(buffer, &iter);
4404 item_continuation = TRUE;
4408 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4410 /* go until paragraph end (empty line) */
4411 while (start || !gtk_text_iter_ends_line(&iter)) {
4412 gchar *scanpos = NULL;
4413 /* parse table - in order of priority */
4415 const gchar *needle; /* token */
4417 /* token search function */
4418 gchar *(*search) (const gchar *haystack,
4419 const gchar *needle);
4420 /* part parsing function */
4421 gboolean (*parse) (const gchar *start,
4422 const gchar *scanpos,
4426 /* part to URI function */
4427 gchar *(*build_uri) (const gchar *bp,
4431 static struct table parser[] = {
4432 {"http://", strcasestr, get_uri_part, make_uri_string},
4433 {"https://", strcasestr, get_uri_part, make_uri_string},
4434 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4435 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4436 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4437 {"www.", strcasestr, get_uri_part, make_http_string},
4438 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4439 {"@", strcasestr, get_email_part, make_email_string}
4441 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4442 gint last_index = PARSE_ELEMS;
4444 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4448 if (!prev_autowrap && num_blocks == 0) {
4450 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4451 G_CALLBACK(text_inserted),
4454 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4457 uri_start = uri_stop = -1;
4459 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4462 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4463 if (startq_offset == -1)
4464 startq_offset = gtk_text_iter_get_offset(&iter);
4465 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4466 if (quotelevel > 2) {
4467 /* recycle colors */
4468 if (prefs_common.recycle_quote_colors)
4477 if (startq_offset == -1)
4478 noq_offset = gtk_text_iter_get_offset(&iter);
4482 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4485 if (gtk_text_iter_ends_line(&iter)) {
4487 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4488 prefs_common.linewrap_len,
4490 GtkTextIter prev, next, cur;
4491 if (prev_autowrap != FALSE || force) {
4492 compose->automatic_break = TRUE;
4494 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4495 compose->automatic_break = FALSE;
4496 if (itemized_len && compose->autoindent) {
4497 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4498 if (!item_continuation)
4499 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4501 } else if (quote_str && wrap_quote) {
4502 compose->automatic_break = TRUE;
4504 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4505 compose->automatic_break = FALSE;
4506 if (itemized_len && compose->autoindent) {
4507 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4508 if (!item_continuation)
4509 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4513 /* remove trailing spaces */
4515 rem_item_len = itemized_len;
4516 while (compose->autoindent && rem_item_len-- > 0)
4517 gtk_text_iter_backward_char(&cur);
4518 gtk_text_iter_backward_char(&cur);
4521 while (!gtk_text_iter_starts_line(&cur)) {
4524 gtk_text_iter_backward_char(&cur);
4525 wc = gtk_text_iter_get_char(&cur);
4526 if (!g_unichar_isspace(wc))
4530 if (!gtk_text_iter_equal(&prev, &next)) {
4531 gtk_text_buffer_delete(buffer, &prev, &next);
4533 gtk_text_iter_forward_char(&break_pos);
4537 gtk_text_buffer_insert(buffer, &break_pos,
4541 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4543 /* move iter to current line start */
4544 gtk_text_iter_set_line_offset(&iter, 0);
4551 /* move iter to next line start */
4557 if (!prev_autowrap && num_blocks > 0) {
4559 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4560 G_CALLBACK(text_inserted),
4564 while (!gtk_text_iter_ends_line(&end_of_line)) {
4565 gtk_text_iter_forward_char(&end_of_line);
4567 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4569 nouri_start = gtk_text_iter_get_offset(&iter);
4570 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4572 walk_pos = gtk_text_iter_get_offset(&iter);
4573 /* FIXME: this looks phony. scanning for anything in the parse table */
4574 for (n = 0; n < PARSE_ELEMS; n++) {
4577 tmp = parser[n].search(walk, parser[n].needle);
4579 if (scanpos == NULL || tmp < scanpos) {
4588 /* check if URI can be parsed */
4589 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4590 (const gchar **)&ep, FALSE)
4591 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4595 strlen(parser[last_index].needle);
4598 uri_start = walk_pos + (bp - o_walk);
4599 uri_stop = walk_pos + (ep - o_walk);
4603 gtk_text_iter_forward_line(&iter);
4606 if (startq_offset != -1) {
4607 GtkTextIter startquote, endquote;
4608 gtk_text_buffer_get_iter_at_offset(
4609 buffer, &startquote, startq_offset);
4612 switch (quotelevel) {
4614 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4615 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4616 gtk_text_buffer_apply_tag_by_name(
4617 buffer, "quote0", &startquote, &endquote);
4618 gtk_text_buffer_remove_tag_by_name(
4619 buffer, "quote1", &startquote, &endquote);
4620 gtk_text_buffer_remove_tag_by_name(
4621 buffer, "quote2", &startquote, &endquote);
4626 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4627 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4628 gtk_text_buffer_apply_tag_by_name(
4629 buffer, "quote1", &startquote, &endquote);
4630 gtk_text_buffer_remove_tag_by_name(
4631 buffer, "quote0", &startquote, &endquote);
4632 gtk_text_buffer_remove_tag_by_name(
4633 buffer, "quote2", &startquote, &endquote);
4638 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4639 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4640 gtk_text_buffer_apply_tag_by_name(
4641 buffer, "quote2", &startquote, &endquote);
4642 gtk_text_buffer_remove_tag_by_name(
4643 buffer, "quote0", &startquote, &endquote);
4644 gtk_text_buffer_remove_tag_by_name(
4645 buffer, "quote1", &startquote, &endquote);
4651 } else if (noq_offset != -1) {
4652 GtkTextIter startnoquote, endnoquote;
4653 gtk_text_buffer_get_iter_at_offset(
4654 buffer, &startnoquote, noq_offset);
4657 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4658 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4659 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4660 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4661 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4662 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4663 gtk_text_buffer_remove_tag_by_name(
4664 buffer, "quote0", &startnoquote, &endnoquote);
4665 gtk_text_buffer_remove_tag_by_name(
4666 buffer, "quote1", &startnoquote, &endnoquote);
4667 gtk_text_buffer_remove_tag_by_name(
4668 buffer, "quote2", &startnoquote, &endnoquote);
4674 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4675 GtkTextIter nouri_start_iter, nouri_end_iter;
4676 gtk_text_buffer_get_iter_at_offset(
4677 buffer, &nouri_start_iter, nouri_start);
4678 gtk_text_buffer_get_iter_at_offset(
4679 buffer, &nouri_end_iter, nouri_stop);
4680 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4681 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4684 modified_before_remove = modified;
4689 if (uri_start >= 0 && uri_stop > 0) {
4690 GtkTextIter uri_start_iter, uri_end_iter, back;
4691 gtk_text_buffer_get_iter_at_offset(
4692 buffer, &uri_start_iter, uri_start);
4693 gtk_text_buffer_get_iter_at_offset(
4694 buffer, &uri_end_iter, uri_stop);
4695 back = uri_end_iter;
4696 gtk_text_iter_backward_char(&back);
4697 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4698 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4699 gtk_text_buffer_apply_tag_by_name(
4700 buffer, "link", &uri_start_iter, &uri_end_iter);
4702 if (removed && !modified_before_remove) {
4708 // debug_print("not modified, out after %d lines\n", lines);
4712 // debug_print("modified, out after %d lines\n", lines);
4714 g_free(itemized_chars);
4717 undo_wrapping(compose->undostruct, FALSE);
4718 compose->autowrap = prev_autowrap;
4723 void compose_action_cb(void *data)
4725 Compose *compose = (Compose *)data;
4726 compose_wrap_all(compose);
4729 static void compose_wrap_all(Compose *compose)
4731 compose_wrap_all_full(compose, FALSE);
4734 static void compose_wrap_all_full(Compose *compose, gboolean force)
4736 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4737 GtkTextBuffer *buffer;
4739 gboolean modified = TRUE;
4741 buffer = gtk_text_view_get_buffer(text);
4743 gtk_text_buffer_get_start_iter(buffer, &iter);
4745 undo_wrapping(compose->undostruct, TRUE);
4747 while (!gtk_text_iter_is_end(&iter) && modified)
4748 modified = compose_beautify_paragraph(compose, &iter, force);
4750 undo_wrapping(compose->undostruct, FALSE);
4754 static void compose_set_title(Compose *compose)
4760 edited = compose->modified ? _(" [Edited]") : "";
4762 subject = gtk_editable_get_chars(
4763 GTK_EDITABLE(compose->subject_entry), 0, -1);
4765 #ifndef GENERIC_UMPC
4766 if (subject && strlen(subject))
4767 str = g_strdup_printf(_("%s - Compose message%s"),
4770 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4772 str = g_strdup(_("Compose message"));
4775 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4781 * compose_current_mail_account:
4783 * Find a current mail account (the currently selected account, or the
4784 * default account, if a news account is currently selected). If a
4785 * mail account cannot be found, display an error message.
4787 * Return value: Mail account, or NULL if not found.
4789 static PrefsAccount *
4790 compose_current_mail_account(void)
4794 if (cur_account && cur_account->protocol != A_NNTP)
4797 ac = account_get_default();
4798 if (!ac || ac->protocol == A_NNTP) {
4799 alertpanel_error(_("Account for sending mail is not specified.\n"
4800 "Please select a mail account before sending."));
4807 #define QUOTE_IF_REQUIRED(out, str) \
4809 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4813 len = strlen(str) + 3; \
4814 if ((__tmp = alloca(len)) == NULL) { \
4815 g_warning("can't allocate memory\n"); \
4816 g_string_free(header, TRUE); \
4819 g_snprintf(__tmp, len, "\"%s\"", str); \
4824 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4825 g_warning("can't allocate memory\n"); \
4826 g_string_free(header, TRUE); \
4829 strcpy(__tmp, str); \
4835 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4837 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4841 len = strlen(str) + 3; \
4842 if ((__tmp = alloca(len)) == NULL) { \
4843 g_warning("can't allocate memory\n"); \
4846 g_snprintf(__tmp, len, "\"%s\"", str); \
4851 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4852 g_warning("can't allocate memory\n"); \
4855 strcpy(__tmp, str); \
4861 static void compose_select_account(Compose *compose, PrefsAccount *account,
4864 gchar *from = NULL, *header = NULL;
4865 ComposeHeaderEntry *header_entry;
4866 #if GTK_CHECK_VERSION(2, 24, 0)
4870 cm_return_if_fail(account != NULL);
4872 compose->account = account;
4873 if (account->name && *account->name) {
4875 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4876 qbuf = escape_internal_quotes(buf, '"');
4877 from = g_strdup_printf("%s <%s>",
4878 qbuf, account->address);
4881 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4883 from = g_strdup_printf("<%s>",
4885 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4890 compose_set_title(compose);
4892 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4893 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4895 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4896 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4897 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4899 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4901 activate_privacy_system(compose, account, FALSE);
4903 if (!init && compose->mode != COMPOSE_REDIRECT) {
4904 undo_block(compose->undostruct);
4905 compose_insert_sig(compose, TRUE);
4906 undo_unblock(compose->undostruct);
4909 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4910 #if !GTK_CHECK_VERSION(2, 24, 0)
4911 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4913 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4914 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4915 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4918 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4919 if (account->protocol == A_NNTP) {
4920 if (!strcmp(header, _("To:")))
4921 combobox_select_by_text(
4922 GTK_COMBO_BOX(header_entry->combo),
4925 if (!strcmp(header, _("Newsgroups:")))
4926 combobox_select_by_text(
4927 GTK_COMBO_BOX(header_entry->combo),
4935 /* use account's dict info if set */
4936 if (compose->gtkaspell) {
4937 if (account->enable_default_dictionary)
4938 gtkaspell_change_dict(compose->gtkaspell,
4939 account->default_dictionary, FALSE);
4940 if (account->enable_default_alt_dictionary)
4941 gtkaspell_change_alt_dict(compose->gtkaspell,
4942 account->default_alt_dictionary);
4943 if (account->enable_default_dictionary
4944 || account->enable_default_alt_dictionary)
4945 compose_spell_menu_changed(compose);
4950 gboolean compose_check_for_valid_recipient(Compose *compose) {
4951 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4952 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4953 gboolean recipient_found = FALSE;
4957 /* free to and newsgroup list */
4958 slist_free_strings_full(compose->to_list);
4959 compose->to_list = NULL;
4961 slist_free_strings_full(compose->newsgroup_list);
4962 compose->newsgroup_list = NULL;
4964 /* search header entries for to and newsgroup entries */
4965 for (list = compose->header_list; list; list = list->next) {
4968 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4969 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4972 if (entry[0] != '\0') {
4973 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4974 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4975 compose->to_list = address_list_append(compose->to_list, entry);
4976 recipient_found = TRUE;
4979 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4980 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4981 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4982 recipient_found = TRUE;
4989 return recipient_found;
4992 static gboolean compose_check_for_set_recipients(Compose *compose)
4994 if (compose->account->set_autocc && compose->account->auto_cc) {
4995 gboolean found_other = FALSE;
4997 /* search header entries for to and newsgroup entries */
4998 for (list = compose->header_list; list; list = list->next) {
5001 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5002 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5005 if (strcmp(entry, compose->account->auto_cc)
5006 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5016 if (compose->batch) {
5017 gtk_widget_show_all(compose->window);
5019 aval = alertpanel(_("Send"),
5020 _("The only recipient is the default CC address. Send anyway?"),
5021 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5022 if (aval != G_ALERTALTERNATE)
5026 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5027 gboolean found_other = FALSE;
5029 /* search header entries for to and newsgroup entries */
5030 for (list = compose->header_list; list; list = list->next) {
5033 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5034 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5037 if (strcmp(entry, compose->account->auto_bcc)
5038 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5048 if (compose->batch) {
5049 gtk_widget_show_all(compose->window);
5051 aval = alertpanel(_("Send"),
5052 _("The only recipient is the default BCC address. Send anyway?"),
5053 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5054 if (aval != G_ALERTALTERNATE)
5061 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5065 if (compose_check_for_valid_recipient(compose) == FALSE) {
5066 if (compose->batch) {
5067 gtk_widget_show_all(compose->window);
5069 alertpanel_error(_("Recipient is not specified."));
5073 if (compose_check_for_set_recipients(compose) == FALSE) {
5077 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5078 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5079 if (*str == '\0' && check_everything == TRUE &&
5080 compose->mode != COMPOSE_REDIRECT) {
5082 gchar *button_label;
5085 if (compose->sending)
5086 button_label = _("+_Send");
5088 button_label = _("+_Queue");
5089 message = g_strdup_printf(_("Subject is empty. %s"),
5090 compose->sending?_("Send it anyway?"):
5091 _("Queue it anyway?"));
5093 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5094 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5095 ALERT_QUESTION, G_ALERTDEFAULT);
5097 if (aval & G_ALERTDISABLE) {
5098 aval &= ~G_ALERTDISABLE;
5099 prefs_common.warn_empty_subj = FALSE;
5101 if (aval != G_ALERTALTERNATE)
5106 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5112 gint compose_send(Compose *compose)
5115 FolderItem *folder = NULL;
5117 gchar *msgpath = NULL;
5118 gboolean discard_window = FALSE;
5119 gchar *errstr = NULL;
5120 gchar *tmsgid = NULL;
5121 MainWindow *mainwin = mainwindow_get_mainwindow();
5122 gboolean queued_removed = FALSE;
5124 if (prefs_common.send_dialog_invisible
5125 || compose->batch == TRUE)
5126 discard_window = TRUE;
5128 compose_allow_user_actions (compose, FALSE);
5129 compose->sending = TRUE;
5131 if (compose_check_entries(compose, TRUE) == FALSE) {
5132 if (compose->batch) {
5133 gtk_widget_show_all(compose->window);
5139 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5142 if (compose->batch) {
5143 gtk_widget_show_all(compose->window);
5146 alertpanel_error(_("Could not queue message for sending:\n\n"
5147 "Charset conversion failed."));
5148 } else if (val == -5) {
5149 alertpanel_error(_("Could not queue message for sending:\n\n"
5150 "Couldn't get recipient encryption key."));
5151 } else if (val == -6) {
5153 } else if (val == -3) {
5154 if (privacy_peek_error())
5155 alertpanel_error(_("Could not queue message for sending:\n\n"
5156 "Signature failed: %s"), privacy_get_error());
5157 } else if (val == -2 && errno != 0) {
5158 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5160 alertpanel_error(_("Could not queue message for sending."));
5165 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5166 if (discard_window) {
5167 compose->sending = FALSE;
5168 compose_close(compose);
5169 /* No more compose access in the normal codepath
5170 * after this point! */
5175 alertpanel_error(_("The message was queued but could not be "
5176 "sent.\nUse \"Send queued messages\" from "
5177 "the main window to retry."));
5178 if (!discard_window) {
5185 if (msgpath == NULL) {
5186 msgpath = folder_item_fetch_msg(folder, msgnum);
5187 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5190 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5191 claws_unlink(msgpath);
5194 if (!discard_window) {
5196 if (!queued_removed)
5197 folder_item_remove_msg(folder, msgnum);
5198 folder_item_scan(folder);
5200 /* make sure we delete that */
5201 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5203 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5204 folder_item_remove_msg(folder, tmp->msgnum);
5205 procmsg_msginfo_free(tmp);
5212 if (!queued_removed)
5213 folder_item_remove_msg(folder, msgnum);
5214 folder_item_scan(folder);
5216 /* make sure we delete that */
5217 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5219 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5220 folder_item_remove_msg(folder, tmp->msgnum);
5221 procmsg_msginfo_free(tmp);
5224 if (!discard_window) {
5225 compose->sending = FALSE;
5226 compose_allow_user_actions (compose, TRUE);
5227 compose_close(compose);
5231 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5232 "the main window to retry."), errstr);
5235 alertpanel_error_log(_("The message was queued but could not be "
5236 "sent.\nUse \"Send queued messages\" from "
5237 "the main window to retry."));
5239 if (!discard_window) {
5248 toolbar_main_set_sensitive(mainwin);
5249 main_window_set_menu_sensitive(mainwin);
5255 compose_allow_user_actions (compose, TRUE);
5256 compose->sending = FALSE;
5257 compose->modified = TRUE;
5258 toolbar_main_set_sensitive(mainwin);
5259 main_window_set_menu_sensitive(mainwin);
5264 static gboolean compose_use_attach(Compose *compose)
5266 GtkTreeModel *model = gtk_tree_view_get_model
5267 (GTK_TREE_VIEW(compose->attach_clist));
5268 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5271 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5274 gchar buf[BUFFSIZE];
5276 gboolean first_to_address;
5277 gboolean first_cc_address;
5279 ComposeHeaderEntry *headerentry;
5280 const gchar *headerentryname;
5281 const gchar *cc_hdr;
5282 const gchar *to_hdr;
5283 gboolean err = FALSE;
5285 debug_print("Writing redirect header\n");
5287 cc_hdr = prefs_common_translated_header_name("Cc:");
5288 to_hdr = prefs_common_translated_header_name("To:");
5290 first_to_address = TRUE;
5291 for (list = compose->header_list; list; list = list->next) {
5292 headerentry = ((ComposeHeaderEntry *)list->data);
5293 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5295 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5296 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5297 Xstrdup_a(str, entstr, return -1);
5299 if (str[0] != '\0') {
5300 compose_convert_header
5301 (compose, buf, sizeof(buf), str,
5302 strlen("Resent-To") + 2, TRUE);
5304 if (first_to_address) {
5305 err |= (fprintf(fp, "Resent-To: ") < 0);
5306 first_to_address = FALSE;
5308 err |= (fprintf(fp, ",") < 0);
5310 err |= (fprintf(fp, "%s", buf) < 0);
5314 if (!first_to_address) {
5315 err |= (fprintf(fp, "\n") < 0);
5318 first_cc_address = TRUE;
5319 for (list = compose->header_list; list; list = list->next) {
5320 headerentry = ((ComposeHeaderEntry *)list->data);
5321 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5323 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5324 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5325 Xstrdup_a(str, strg, return -1);
5327 if (str[0] != '\0') {
5328 compose_convert_header
5329 (compose, buf, sizeof(buf), str,
5330 strlen("Resent-Cc") + 2, TRUE);
5332 if (first_cc_address) {
5333 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5334 first_cc_address = FALSE;
5336 err |= (fprintf(fp, ",") < 0);
5338 err |= (fprintf(fp, "%s", buf) < 0);
5342 if (!first_cc_address) {
5343 err |= (fprintf(fp, "\n") < 0);
5346 return (err ? -1:0);
5349 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5351 gchar buf[BUFFSIZE];
5353 const gchar *entstr;
5354 /* struct utsname utsbuf; */
5355 gboolean err = FALSE;
5357 cm_return_val_if_fail(fp != NULL, -1);
5358 cm_return_val_if_fail(compose->account != NULL, -1);
5359 cm_return_val_if_fail(compose->account->address != NULL, -1);
5362 get_rfc822_date(buf, sizeof(buf));
5363 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5366 if (compose->account->name && *compose->account->name) {
5367 compose_convert_header
5368 (compose, buf, sizeof(buf), compose->account->name,
5369 strlen("From: "), TRUE);
5370 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5371 buf, compose->account->address) < 0);
5373 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5376 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5377 if (*entstr != '\0') {
5378 Xstrdup_a(str, entstr, return -1);
5381 compose_convert_header(compose, buf, sizeof(buf), str,
5382 strlen("Subject: "), FALSE);
5383 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5387 /* Resent-Message-ID */
5388 if (compose->account->set_domain && compose->account->domain) {
5389 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5390 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5391 g_snprintf(buf, sizeof(buf), "%s",
5392 strchr(compose->account->address, '@') ?
5393 strchr(compose->account->address, '@')+1 :
5394 compose->account->address);
5396 g_snprintf(buf, sizeof(buf), "%s", "");
5399 if (compose->account->gen_msgid) {
5401 if (compose->account->msgid_with_addr) {
5402 addr = compose->account->address;
5404 generate_msgid(buf, sizeof(buf), addr);
5405 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5407 g_free(compose->msgid);
5408 compose->msgid = g_strdup(buf);
5410 compose->msgid = NULL;
5413 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5416 /* separator between header and body */
5417 err |= (fputs("\n", fp) == EOF);
5419 return (err ? -1:0);
5422 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5426 gchar buf[BUFFSIZE];
5428 gboolean skip = FALSE;
5429 gboolean err = FALSE;
5430 gchar *not_included[]={
5431 "Return-Path:", "Delivered-To:", "Received:",
5432 "Subject:", "X-UIDL:", "AF:",
5433 "NF:", "PS:", "SRH:",
5434 "SFN:", "DSR:", "MID:",
5435 "CFG:", "PT:", "S:",
5436 "RQ:", "SSV:", "NSV:",
5437 "SSH:", "R:", "MAID:",
5438 "NAID:", "RMID:", "FMID:",
5439 "SCF:", "RRCPT:", "NG:",
5440 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5441 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5442 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5443 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5444 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5447 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5448 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5452 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5454 for (i = 0; not_included[i] != NULL; i++) {
5455 if (g_ascii_strncasecmp(buf, not_included[i],
5456 strlen(not_included[i])) == 0) {
5463 if (fputs(buf, fdest) == -1)
5466 if (!prefs_common.redirect_keep_from) {
5467 if (g_ascii_strncasecmp(buf, "From:",
5468 strlen("From:")) == 0) {
5469 err |= (fputs(" (by way of ", fdest) == EOF);
5470 if (compose->account->name
5471 && *compose->account->name) {
5472 compose_convert_header
5473 (compose, buf, sizeof(buf),
5474 compose->account->name,
5477 err |= (fprintf(fdest, "%s <%s>",
5479 compose->account->address) < 0);
5481 err |= (fprintf(fdest, "%s",
5482 compose->account->address) < 0);
5483 err |= (fputs(")", fdest) == EOF);
5487 if (fputs("\n", fdest) == -1)
5494 if (compose_redirect_write_headers(compose, fdest))
5497 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5498 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5511 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5513 GtkTextBuffer *buffer;
5514 GtkTextIter start, end;
5517 const gchar *out_codeset;
5518 EncodingType encoding = ENC_UNKNOWN;
5519 MimeInfo *mimemsg, *mimetext;
5521 const gchar *src_codeset = CS_INTERNAL;
5522 gchar *from_addr = NULL;
5523 gchar *from_name = NULL;
5525 if (action == COMPOSE_WRITE_FOR_SEND)
5526 attach_parts = TRUE;
5528 /* create message MimeInfo */
5529 mimemsg = procmime_mimeinfo_new();
5530 mimemsg->type = MIMETYPE_MESSAGE;
5531 mimemsg->subtype = g_strdup("rfc822");
5532 mimemsg->content = MIMECONTENT_MEM;
5533 mimemsg->tmp = TRUE; /* must free content later */
5534 mimemsg->data.mem = compose_get_header(compose);
5536 /* Create text part MimeInfo */
5537 /* get all composed text */
5538 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5539 gtk_text_buffer_get_start_iter(buffer, &start);
5540 gtk_text_buffer_get_end_iter(buffer, &end);
5541 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5543 out_codeset = conv_get_charset_str(compose->out_encoding);
5545 if (!out_codeset && is_ascii_str(chars)) {
5546 out_codeset = CS_US_ASCII;
5547 } else if (prefs_common.outgoing_fallback_to_ascii &&
5548 is_ascii_str(chars)) {
5549 out_codeset = CS_US_ASCII;
5550 encoding = ENC_7BIT;
5554 gchar *test_conv_global_out = NULL;
5555 gchar *test_conv_reply = NULL;
5557 /* automatic mode. be automatic. */
5558 codeconv_set_strict(TRUE);
5560 out_codeset = conv_get_outgoing_charset_str();
5562 debug_print("trying to convert to %s\n", out_codeset);
5563 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5566 if (!test_conv_global_out && compose->orig_charset
5567 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5568 out_codeset = compose->orig_charset;
5569 debug_print("failure; trying to convert to %s\n", out_codeset);
5570 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5573 if (!test_conv_global_out && !test_conv_reply) {
5575 out_codeset = CS_INTERNAL;
5576 debug_print("failure; finally using %s\n", out_codeset);
5578 g_free(test_conv_global_out);
5579 g_free(test_conv_reply);
5580 codeconv_set_strict(FALSE);
5583 if (encoding == ENC_UNKNOWN) {
5584 if (prefs_common.encoding_method == CTE_BASE64)
5585 encoding = ENC_BASE64;
5586 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5587 encoding = ENC_QUOTED_PRINTABLE;
5588 else if (prefs_common.encoding_method == CTE_8BIT)
5589 encoding = ENC_8BIT;
5591 encoding = procmime_get_encoding_for_charset(out_codeset);
5594 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5595 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5597 if (action == COMPOSE_WRITE_FOR_SEND) {
5598 codeconv_set_strict(TRUE);
5599 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5600 codeconv_set_strict(FALSE);
5606 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5607 "to the specified %s charset.\n"
5608 "Send it as %s?"), out_codeset, src_codeset);
5609 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5610 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5613 if (aval != G_ALERTALTERNATE) {
5618 out_codeset = src_codeset;
5624 out_codeset = src_codeset;
5629 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5630 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5631 strstr(buf, "\nFrom ") != NULL) {
5632 encoding = ENC_QUOTED_PRINTABLE;
5636 mimetext = procmime_mimeinfo_new();
5637 mimetext->content = MIMECONTENT_MEM;
5638 mimetext->tmp = TRUE; /* must free content later */
5639 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5640 * and free the data, which we need later. */
5641 mimetext->data.mem = g_strdup(buf);
5642 mimetext->type = MIMETYPE_TEXT;
5643 mimetext->subtype = g_strdup("plain");
5644 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5645 g_strdup(out_codeset));
5647 /* protect trailing spaces when signing message */
5648 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5649 privacy_system_can_sign(compose->privacy_system)) {
5650 encoding = ENC_QUOTED_PRINTABLE;
5653 debug_print("main text: %zd bytes encoded as %s in %d\n",
5654 strlen(buf), out_codeset, encoding);
5656 /* check for line length limit */
5657 if (action == COMPOSE_WRITE_FOR_SEND &&
5658 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5659 check_line_length(buf, 1000, &line) < 0) {
5663 msg = g_strdup_printf
5664 (_("Line %d exceeds the line length limit (998 bytes).\n"
5665 "The contents of the message might be broken on the way to the delivery.\n"
5667 "Send it anyway?"), line + 1);
5668 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5670 if (aval != G_ALERTALTERNATE) {
5676 if (encoding != ENC_UNKNOWN)
5677 procmime_encode_content(mimetext, encoding);
5679 /* append attachment parts */
5680 if (compose_use_attach(compose) && attach_parts) {
5681 MimeInfo *mimempart;
5682 gchar *boundary = NULL;
5683 mimempart = procmime_mimeinfo_new();
5684 mimempart->content = MIMECONTENT_EMPTY;
5685 mimempart->type = MIMETYPE_MULTIPART;
5686 mimempart->subtype = g_strdup("mixed");
5690 boundary = generate_mime_boundary(NULL);
5691 } while (strstr(buf, boundary) != NULL);
5693 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5696 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5698 g_node_append(mimempart->node, mimetext->node);
5699 g_node_append(mimemsg->node, mimempart->node);
5701 if (compose_add_attachments(compose, mimempart) < 0)
5704 g_node_append(mimemsg->node, mimetext->node);
5708 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5709 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5710 /* extract name and address */
5711 if (strstr(spec, " <") && strstr(spec, ">")) {
5712 from_addr = g_strdup(strrchr(spec, '<')+1);
5713 *(strrchr(from_addr, '>')) = '\0';
5714 from_name = g_strdup(spec);
5715 *(strrchr(from_name, '<')) = '\0';
5722 /* sign message if sending */
5723 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5724 privacy_system_can_sign(compose->privacy_system))
5725 if (!privacy_sign(compose->privacy_system, mimemsg,
5726 compose->account, from_addr)) {
5733 procmime_write_mimeinfo(mimemsg, fp);
5735 procmime_mimeinfo_free_all(mimemsg);
5740 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5742 GtkTextBuffer *buffer;
5743 GtkTextIter start, end;
5748 if ((fp = g_fopen(file, "wb")) == NULL) {
5749 FILE_OP_ERROR(file, "fopen");
5753 /* chmod for security */
5754 if (change_file_mode_rw(fp, file) < 0) {
5755 FILE_OP_ERROR(file, "chmod");
5756 g_warning("can't change file mode\n");
5759 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5760 gtk_text_buffer_get_start_iter(buffer, &start);
5761 gtk_text_buffer_get_end_iter(buffer, &end);
5762 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5764 chars = conv_codeset_strdup
5765 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5774 len = strlen(chars);
5775 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5776 FILE_OP_ERROR(file, "fwrite");
5785 if (fclose(fp) == EOF) {
5786 FILE_OP_ERROR(file, "fclose");
5793 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5796 MsgInfo *msginfo = compose->targetinfo;
5798 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5799 if (!msginfo) return -1;
5801 if (!force && MSG_IS_LOCKED(msginfo->flags))
5804 item = msginfo->folder;
5805 cm_return_val_if_fail(item != NULL, -1);
5807 if (procmsg_msg_exist(msginfo) &&
5808 (folder_has_parent_of_type(item, F_QUEUE) ||
5809 folder_has_parent_of_type(item, F_DRAFT)
5810 || msginfo == compose->autosaved_draft)) {
5811 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5812 g_warning("can't remove the old message\n");
5815 debug_print("removed reedit target %d\n", msginfo->msgnum);
5822 static void compose_remove_draft(Compose *compose)
5825 MsgInfo *msginfo = compose->targetinfo;
5826 drafts = account_get_special_folder(compose->account, F_DRAFT);
5828 if (procmsg_msg_exist(msginfo)) {
5829 folder_item_remove_msg(drafts, msginfo->msgnum);
5834 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5835 gboolean remove_reedit_target)
5837 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5840 static gboolean compose_warn_encryption(Compose *compose)
5842 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5843 AlertValue val = G_ALERTALTERNATE;
5845 if (warning == NULL)
5848 val = alertpanel_full(_("Encryption warning"), warning,
5849 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5850 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5851 if (val & G_ALERTDISABLE) {
5852 val &= ~G_ALERTDISABLE;
5853 if (val == G_ALERTALTERNATE)
5854 privacy_inhibit_encrypt_warning(compose->privacy_system,
5858 if (val == G_ALERTALTERNATE) {
5865 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5866 gchar **msgpath, gboolean check_subject,
5867 gboolean remove_reedit_target)
5874 PrefsAccount *mailac = NULL, *newsac = NULL;
5875 gboolean err = FALSE;
5877 debug_print("queueing message...\n");
5878 cm_return_val_if_fail(compose->account != NULL, -1);
5880 if (compose_check_entries(compose, check_subject) == FALSE) {
5881 if (compose->batch) {
5882 gtk_widget_show_all(compose->window);
5887 if (!compose->to_list && !compose->newsgroup_list) {
5888 g_warning("can't get recipient list.");
5892 if (compose->to_list) {
5893 if (compose->account->protocol != A_NNTP)
5894 mailac = compose->account;
5895 else if (cur_account && cur_account->protocol != A_NNTP)
5896 mailac = cur_account;
5897 else if (!(mailac = compose_current_mail_account())) {
5898 alertpanel_error(_("No account for sending mails available!"));
5903 if (compose->newsgroup_list) {
5904 if (compose->account->protocol == A_NNTP)
5905 newsac = compose->account;
5907 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5912 /* write queue header */
5913 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5914 G_DIR_SEPARATOR, compose, (guint) rand());
5915 debug_print("queuing to %s\n", tmp);
5916 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5917 FILE_OP_ERROR(tmp, "fopen");
5922 if (change_file_mode_rw(fp, tmp) < 0) {
5923 FILE_OP_ERROR(tmp, "chmod");
5924 g_warning("can't change file mode\n");
5927 /* queueing variables */
5928 err |= (fprintf(fp, "AF:\n") < 0);
5929 err |= (fprintf(fp, "NF:0\n") < 0);
5930 err |= (fprintf(fp, "PS:10\n") < 0);
5931 err |= (fprintf(fp, "SRH:1\n") < 0);
5932 err |= (fprintf(fp, "SFN:\n") < 0);
5933 err |= (fprintf(fp, "DSR:\n") < 0);
5935 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5937 err |= (fprintf(fp, "MID:\n") < 0);
5938 err |= (fprintf(fp, "CFG:\n") < 0);
5939 err |= (fprintf(fp, "PT:0\n") < 0);
5940 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5941 err |= (fprintf(fp, "RQ:\n") < 0);
5943 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5945 err |= (fprintf(fp, "SSV:\n") < 0);
5947 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5949 err |= (fprintf(fp, "NSV:\n") < 0);
5950 err |= (fprintf(fp, "SSH:\n") < 0);
5951 /* write recepient list */
5952 if (compose->to_list) {
5953 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5954 for (cur = compose->to_list->next; cur != NULL;
5956 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5957 err |= (fprintf(fp, "\n") < 0);
5959 /* write newsgroup list */
5960 if (compose->newsgroup_list) {
5961 err |= (fprintf(fp, "NG:") < 0);
5962 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5963 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5964 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5965 err |= (fprintf(fp, "\n") < 0);
5967 /* Sylpheed account IDs */
5969 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5971 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5974 if (compose->privacy_system != NULL) {
5975 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5976 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5977 if (compose->use_encryption) {
5979 if (!compose_warn_encryption(compose)) {
5985 if (mailac && mailac->encrypt_to_self) {
5986 GSList *tmp_list = g_slist_copy(compose->to_list);
5987 tmp_list = g_slist_append(tmp_list, compose->account->address);
5988 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5989 g_slist_free(tmp_list);
5991 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5993 if (encdata != NULL) {
5994 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5995 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5996 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5998 } /* else we finally dont want to encrypt */
6000 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6001 /* and if encdata was null, it means there's been a problem in
6004 g_warning("failed to write queue message");
6014 /* Save copy folder */
6015 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6016 gchar *savefolderid;
6018 savefolderid = compose_get_save_to(compose);
6019 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6020 g_free(savefolderid);
6022 /* Save copy folder */
6023 if (compose->return_receipt) {
6024 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6026 /* Message-ID of message replying to */
6027 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6028 gchar *folderid = NULL;
6030 if (compose->replyinfo->folder)
6031 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6032 if (folderid == NULL)
6033 folderid = g_strdup("NULL");
6035 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6038 /* Message-ID of message forwarding to */
6039 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6040 gchar *folderid = NULL;
6042 if (compose->fwdinfo->folder)
6043 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6044 if (folderid == NULL)
6045 folderid = g_strdup("NULL");
6047 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6051 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6052 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6054 /* end of headers */
6055 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6057 if (compose->redirect_filename != NULL) {
6058 if (compose_redirect_write_to_file(compose, fp) < 0) {
6066 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6070 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6074 g_warning("failed to write queue message\n");
6080 if (fclose(fp) == EOF) {
6081 FILE_OP_ERROR(tmp, "fclose");
6087 if (item && *item) {
6090 queue = account_get_special_folder(compose->account, F_QUEUE);
6093 g_warning("can't find queue folder\n");
6098 folder_item_scan(queue);
6099 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6100 g_warning("can't queue the message\n");
6106 if (msgpath == NULL) {
6112 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6113 compose_remove_reedit_target(compose, FALSE);
6116 if ((msgnum != NULL) && (item != NULL)) {
6124 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6127 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6129 struct stat statbuf;
6130 gchar *type, *subtype;
6131 GtkTreeModel *model;
6134 model = gtk_tree_view_get_model(tree_view);
6136 if (!gtk_tree_model_get_iter_first(model, &iter))
6139 gtk_tree_model_get(model, &iter,
6143 if (!is_file_exist(ainfo->file)) {
6144 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6145 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6146 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6148 if (val == G_ALERTDEFAULT) {
6153 if (g_stat(ainfo->file, &statbuf) < 0)
6156 mimepart = procmime_mimeinfo_new();
6157 mimepart->content = MIMECONTENT_FILE;
6158 mimepart->data.filename = g_strdup(ainfo->file);
6159 mimepart->tmp = FALSE; /* or we destroy our attachment */
6160 mimepart->offset = 0;
6161 mimepart->length = statbuf.st_size;
6163 type = g_strdup(ainfo->content_type);
6165 if (!strchr(type, '/')) {
6167 type = g_strdup("application/octet-stream");
6170 subtype = strchr(type, '/') + 1;
6171 *(subtype - 1) = '\0';
6172 mimepart->type = procmime_get_media_type(type);
6173 mimepart->subtype = g_strdup(subtype);
6176 if (mimepart->type == MIMETYPE_MESSAGE &&
6177 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6178 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6179 } else if (mimepart->type == MIMETYPE_TEXT) {
6180 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6181 /* Text parts with no name come from multipart/alternative
6182 * forwards. Make sure the recipient won't look at the
6183 * original HTML part by mistake. */
6184 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6185 ainfo->name = g_strdup_printf(_("Original %s part"),
6189 g_hash_table_insert(mimepart->typeparameters,
6190 g_strdup("charset"), g_strdup(ainfo->charset));
6192 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6193 if (mimepart->type == MIMETYPE_APPLICATION &&
6194 !strcmp2(mimepart->subtype, "octet-stream"))
6195 g_hash_table_insert(mimepart->typeparameters,
6196 g_strdup("name"), g_strdup(ainfo->name));
6197 g_hash_table_insert(mimepart->dispositionparameters,
6198 g_strdup("filename"), g_strdup(ainfo->name));
6199 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6202 if (mimepart->type == MIMETYPE_MESSAGE
6203 || mimepart->type == MIMETYPE_MULTIPART)
6204 ainfo->encoding = ENC_BINARY;
6205 else if (compose->use_signing) {
6206 if (ainfo->encoding == ENC_7BIT)
6207 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6208 else if (ainfo->encoding == ENC_8BIT)
6209 ainfo->encoding = ENC_BASE64;
6214 procmime_encode_content(mimepart, ainfo->encoding);
6216 g_node_append(parent->node, mimepart->node);
6217 } while (gtk_tree_model_iter_next(model, &iter));
6222 static gchar *compose_quote_list_of_addresses(gchar *str)
6224 GSList *list = NULL, *item = NULL;
6225 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6227 list = address_list_append_with_comments(list, str);
6228 for (item = list; item != NULL; item = item->next) {
6229 gchar *spec = item->data;
6230 gchar *endofname = strstr(spec, " <");
6231 if (endofname != NULL) {
6234 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6235 qqname = escape_internal_quotes(qname, '"');
6237 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6238 gchar *addr = g_strdup(endofname);
6239 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6240 faddr = g_strconcat(name, addr, NULL);
6243 debug_print("new auto-quoted address: '%s'", faddr);
6247 result = g_strdup((faddr != NULL)? faddr: spec);
6249 result = g_strconcat(result,
6251 (faddr != NULL)? faddr: spec,
6254 if (faddr != NULL) {
6259 slist_free_strings_full(list);
6264 #define IS_IN_CUSTOM_HEADER(header) \
6265 (compose->account->add_customhdr && \
6266 custom_header_find(compose->account->customhdr_list, header) != NULL)
6268 static void compose_add_headerfield_from_headerlist(Compose *compose,
6270 const gchar *fieldname,
6271 const gchar *seperator)
6273 gchar *str, *fieldname_w_colon;
6274 gboolean add_field = FALSE;
6276 ComposeHeaderEntry *headerentry;
6277 const gchar *headerentryname;
6278 const gchar *trans_fieldname;
6281 if (IS_IN_CUSTOM_HEADER(fieldname))
6284 debug_print("Adding %s-fields\n", fieldname);
6286 fieldstr = g_string_sized_new(64);
6288 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6289 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6291 for (list = compose->header_list; list; list = list->next) {
6292 headerentry = ((ComposeHeaderEntry *)list->data);
6293 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6295 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6296 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6298 str = compose_quote_list_of_addresses(ustr);
6300 if (str != NULL && str[0] != '\0') {
6302 g_string_append(fieldstr, seperator);
6303 g_string_append(fieldstr, str);
6312 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6313 compose_convert_header
6314 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6315 strlen(fieldname) + 2, TRUE);
6316 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6320 g_free(fieldname_w_colon);
6321 g_string_free(fieldstr, TRUE);
6326 static gchar *compose_get_manual_headers_info(Compose *compose)
6328 GString *sh_header = g_string_new(" ");
6330 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6332 for (list = compose->header_list; list; list = list->next) {
6333 ComposeHeaderEntry *headerentry;
6336 gchar *headername_wcolon;
6337 const gchar *headername_trans;
6339 gboolean standard_header = FALSE;
6341 headerentry = ((ComposeHeaderEntry *)list->data);
6343 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6345 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6350 if (!strstr(tmp, ":")) {
6351 headername_wcolon = g_strconcat(tmp, ":", NULL);
6352 headername = g_strdup(tmp);
6354 headername_wcolon = g_strdup(tmp);
6355 headername = g_strdup(strtok(tmp, ":"));
6359 string = std_headers;
6360 while (*string != NULL) {
6361 headername_trans = prefs_common_translated_header_name(*string);
6362 if (!strcmp(headername_trans, headername_wcolon))
6363 standard_header = TRUE;
6366 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6367 g_string_append_printf(sh_header, "%s ", headername);
6369 g_free(headername_wcolon);
6371 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6372 return g_string_free(sh_header, FALSE);
6375 static gchar *compose_get_header(Compose *compose)
6377 gchar buf[BUFFSIZE];
6378 const gchar *entry_str;
6382 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6384 gchar *from_name = NULL, *from_address = NULL;
6387 cm_return_val_if_fail(compose->account != NULL, NULL);
6388 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6390 header = g_string_sized_new(64);
6393 get_rfc822_date(buf, sizeof(buf));
6394 g_string_append_printf(header, "Date: %s\n", buf);
6398 if (compose->account->name && *compose->account->name) {
6400 QUOTE_IF_REQUIRED(buf, compose->account->name);
6401 tmp = g_strdup_printf("%s <%s>",
6402 buf, compose->account->address);
6404 tmp = g_strdup_printf("%s",
6405 compose->account->address);
6407 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6408 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6410 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6411 from_address = g_strdup(compose->account->address);
6413 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6414 /* extract name and address */
6415 if (strstr(spec, " <") && strstr(spec, ">")) {
6416 from_address = g_strdup(strrchr(spec, '<')+1);
6417 *(strrchr(from_address, '>')) = '\0';
6418 from_name = g_strdup(spec);
6419 *(strrchr(from_name, '<')) = '\0';
6422 from_address = g_strdup(spec);
6429 if (from_name && *from_name) {
6431 compose_convert_header
6432 (compose, buf, sizeof(buf), from_name,
6433 strlen("From: "), TRUE);
6434 QUOTE_IF_REQUIRED(name, buf);
6435 qname = escape_internal_quotes(name, '"');
6437 g_string_append_printf(header, "From: %s <%s>\n",
6438 qname, from_address);
6442 g_string_append_printf(header, "From: %s\n", from_address);
6445 g_free(from_address);
6448 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6451 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6454 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6458 * If this account is a NNTP account remove Bcc header from
6459 * message body since it otherwise will be publicly shown
6461 if (compose->account->protocol != A_NNTP)
6462 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6465 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6467 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6470 compose_convert_header(compose, buf, sizeof(buf), str,
6471 strlen("Subject: "), FALSE);
6472 g_string_append_printf(header, "Subject: %s\n", buf);
6478 if (compose->account->set_domain && compose->account->domain) {
6479 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6480 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6481 g_snprintf(buf, sizeof(buf), "%s",
6482 strchr(compose->account->address, '@') ?
6483 strchr(compose->account->address, '@')+1 :
6484 compose->account->address);
6486 g_snprintf(buf, sizeof(buf), "%s", "");
6489 if (compose->account->gen_msgid) {
6491 if (compose->account->msgid_with_addr) {
6492 addr = compose->account->address;
6494 generate_msgid(buf, sizeof(buf), addr);
6495 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6497 g_free(compose->msgid);
6498 compose->msgid = g_strdup(buf);
6500 compose->msgid = NULL;
6503 if (compose->remove_references == FALSE) {
6505 if (compose->inreplyto && compose->to_list)
6506 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6509 if (compose->references)
6510 g_string_append_printf(header, "References: %s\n", compose->references);
6514 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6517 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6520 if (compose->account->organization &&
6521 strlen(compose->account->organization) &&
6522 !IS_IN_CUSTOM_HEADER("Organization")) {
6523 compose_convert_header(compose, buf, sizeof(buf),
6524 compose->account->organization,
6525 strlen("Organization: "), FALSE);
6526 g_string_append_printf(header, "Organization: %s\n", buf);
6529 /* Program version and system info */
6530 if (compose->account->gen_xmailer &&
6531 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6532 !compose->newsgroup_list) {
6533 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6535 gtk_major_version, gtk_minor_version, gtk_micro_version,
6538 if (compose->account->gen_xmailer &&
6539 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6540 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6542 gtk_major_version, gtk_minor_version, gtk_micro_version,
6546 /* custom headers */
6547 if (compose->account->add_customhdr) {
6550 for (cur = compose->account->customhdr_list; cur != NULL;
6552 CustomHeader *chdr = (CustomHeader *)cur->data;
6554 if (custom_header_is_allowed(chdr->name)
6555 && chdr->value != NULL
6556 && *(chdr->value) != '\0') {
6557 compose_convert_header
6558 (compose, buf, sizeof(buf),
6560 strlen(chdr->name) + 2, FALSE);
6561 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6566 /* Automatic Faces and X-Faces */
6567 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6568 g_string_append_printf(header, "X-Face: %s\n", buf);
6570 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6571 g_string_append_printf(header, "X-Face: %s\n", buf);
6573 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6574 g_string_append_printf(header, "Face: %s\n", buf);
6576 else if (get_default_face (buf, sizeof(buf)) == 0) {
6577 g_string_append_printf(header, "Face: %s\n", buf);
6581 switch (compose->priority) {
6582 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6583 "X-Priority: 1 (Highest)\n");
6585 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6586 "X-Priority: 2 (High)\n");
6588 case PRIORITY_NORMAL: break;
6589 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6590 "X-Priority: 4 (Low)\n");
6592 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6593 "X-Priority: 5 (Lowest)\n");
6595 default: debug_print("compose: priority unknown : %d\n",
6599 /* Request Return Receipt */
6600 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6601 if (compose->return_receipt) {
6602 if (compose->account->name
6603 && *compose->account->name) {
6604 compose_convert_header(compose, buf, sizeof(buf),
6605 compose->account->name,
6606 strlen("Disposition-Notification-To: "),
6608 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6610 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6614 /* get special headers */
6615 for (list = compose->header_list; list; list = list->next) {
6616 ComposeHeaderEntry *headerentry;
6619 gchar *headername_wcolon;
6620 const gchar *headername_trans;
6623 gboolean standard_header = FALSE;
6625 headerentry = ((ComposeHeaderEntry *)list->data);
6627 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6629 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6634 if (!strstr(tmp, ":")) {
6635 headername_wcolon = g_strconcat(tmp, ":", NULL);
6636 headername = g_strdup(tmp);
6638 headername_wcolon = g_strdup(tmp);
6639 headername = g_strdup(strtok(tmp, ":"));
6643 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6644 Xstrdup_a(headervalue, entry_str, return NULL);
6645 subst_char(headervalue, '\r', ' ');
6646 subst_char(headervalue, '\n', ' ');
6647 string = std_headers;
6648 while (*string != NULL) {
6649 headername_trans = prefs_common_translated_header_name(*string);
6650 if (!strcmp(headername_trans, headername_wcolon))
6651 standard_header = TRUE;
6654 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6655 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6658 g_free(headername_wcolon);
6662 g_string_free(header, FALSE);
6667 #undef IS_IN_CUSTOM_HEADER
6669 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6670 gint header_len, gboolean addr_field)
6672 gchar *tmpstr = NULL;
6673 const gchar *out_codeset = NULL;
6675 cm_return_if_fail(src != NULL);
6676 cm_return_if_fail(dest != NULL);
6678 if (len < 1) return;
6680 tmpstr = g_strdup(src);
6682 subst_char(tmpstr, '\n', ' ');
6683 subst_char(tmpstr, '\r', ' ');
6686 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6687 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6688 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6693 codeconv_set_strict(TRUE);
6694 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6695 conv_get_charset_str(compose->out_encoding));
6696 codeconv_set_strict(FALSE);
6698 if (!dest || *dest == '\0') {
6699 gchar *test_conv_global_out = NULL;
6700 gchar *test_conv_reply = NULL;
6702 /* automatic mode. be automatic. */
6703 codeconv_set_strict(TRUE);
6705 out_codeset = conv_get_outgoing_charset_str();
6707 debug_print("trying to convert to %s\n", out_codeset);
6708 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6711 if (!test_conv_global_out && compose->orig_charset
6712 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6713 out_codeset = compose->orig_charset;
6714 debug_print("failure; trying to convert to %s\n", out_codeset);
6715 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6718 if (!test_conv_global_out && !test_conv_reply) {
6720 out_codeset = CS_INTERNAL;
6721 debug_print("finally using %s\n", out_codeset);
6723 g_free(test_conv_global_out);
6724 g_free(test_conv_reply);
6725 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6727 codeconv_set_strict(FALSE);
6732 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6736 cm_return_if_fail(user_data != NULL);
6738 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6739 g_strstrip(address);
6740 if (*address != '\0') {
6741 gchar *name = procheader_get_fromname(address);
6742 extract_address(address);
6743 #ifndef USE_NEW_ADDRBOOK
6744 addressbook_add_contact(name, address, NULL, NULL);
6746 debug_print("%s: %s\n", name, address);
6747 if (addressadd_selection(name, address, NULL, NULL)) {
6748 debug_print( "addressbook_add_contact - added\n" );
6755 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6757 GtkWidget *menuitem;
6760 cm_return_if_fail(menu != NULL);
6761 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6763 menuitem = gtk_separator_menu_item_new();
6764 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6765 gtk_widget_show(menuitem);
6767 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6768 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6770 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6771 g_strstrip(address);
6772 if (*address == '\0') {
6773 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6776 g_signal_connect(G_OBJECT(menuitem), "activate",
6777 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6778 gtk_widget_show(menuitem);
6781 void compose_add_extra_header(gchar *header, GtkListStore *model)
6784 if (strcmp(header, "")) {
6785 COMBOBOX_ADD(model, header, COMPOSE_TO);
6789 void compose_add_extra_header_entries(GtkListStore *model)
6793 gchar buf[BUFFSIZE];
6796 if (extra_headers == NULL) {
6797 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6798 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6799 debug_print("extra headers file not found\n");
6800 goto extra_headers_done;
6802 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6803 lastc = strlen(buf) - 1; /* remove trailing control chars */
6804 while (lastc >= 0 && buf[lastc] != ':')
6805 buf[lastc--] = '\0';
6806 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6807 buf[lastc] = '\0'; /* remove trailing : for comparison */
6808 if (custom_header_is_allowed(buf)) {
6810 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6813 g_message("disallowed extra header line: %s\n", buf);
6817 g_message("invalid extra header line: %s\n", buf);
6823 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6824 extra_headers = g_slist_reverse(extra_headers);
6826 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6829 static void compose_create_header_entry(Compose *compose)
6831 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6838 const gchar *header = NULL;
6839 ComposeHeaderEntry *headerentry;
6840 gboolean standard_header = FALSE;
6841 GtkListStore *model;
6844 headerentry = g_new0(ComposeHeaderEntry, 1);
6846 /* Combo box model */
6847 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6848 #if !GTK_CHECK_VERSION(2, 24, 0)
6849 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6851 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6853 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6855 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6857 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6858 COMPOSE_NEWSGROUPS);
6859 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6861 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6862 COMPOSE_FOLLOWUPTO);
6863 compose_add_extra_header_entries(model);
6866 #if GTK_CHECK_VERSION(2, 24, 0)
6867 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6868 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6869 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6870 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6871 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6873 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6874 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6875 G_CALLBACK(compose_grab_focus_cb), compose);
6876 gtk_widget_show(combo);
6878 /* Putting only the combobox child into focus chain of its parent causes
6879 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6880 * This eliminates need to pres Tab twice in order to really get from the
6881 * combobox to next widget. */
6883 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6884 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6887 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6888 compose->header_nextrow, compose->header_nextrow+1,
6889 GTK_SHRINK, GTK_FILL, 0, 0);
6890 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6891 const gchar *last_header_entry = gtk_entry_get_text(
6892 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6894 while (*string != NULL) {
6895 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6896 standard_header = TRUE;
6899 if (standard_header)
6900 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6902 if (!compose->header_last || !standard_header) {
6903 switch(compose->account->protocol) {
6905 header = prefs_common_translated_header_name("Newsgroups:");
6908 header = prefs_common_translated_header_name("To:");
6913 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6915 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6916 G_CALLBACK(compose_grab_focus_cb), compose);
6918 /* Entry field with cleanup button */
6919 button = gtk_button_new();
6920 gtk_button_set_image(GTK_BUTTON(button),
6921 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6922 gtk_widget_show(button);
6923 CLAWS_SET_TIP(button,
6924 _("Delete entry contents"));
6925 entry = gtk_entry_new();
6926 gtk_widget_show(entry);
6927 CLAWS_SET_TIP(entry,
6928 _("Use <tab> to autocomplete from addressbook"));
6929 hbox = gtk_hbox_new (FALSE, 0);
6930 gtk_widget_show(hbox);
6931 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6932 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6933 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6934 compose->header_nextrow, compose->header_nextrow+1,
6935 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6937 g_signal_connect(G_OBJECT(entry), "key-press-event",
6938 G_CALLBACK(compose_headerentry_key_press_event_cb),
6940 g_signal_connect(G_OBJECT(entry), "changed",
6941 G_CALLBACK(compose_headerentry_changed_cb),
6943 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6944 G_CALLBACK(compose_grab_focus_cb), compose);
6946 g_signal_connect(G_OBJECT(button), "clicked",
6947 G_CALLBACK(compose_headerentry_button_clicked_cb),
6951 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6952 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6953 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6954 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6955 G_CALLBACK(compose_header_drag_received_cb),
6957 g_signal_connect(G_OBJECT(entry), "drag-drop",
6958 G_CALLBACK(compose_drag_drop),
6960 g_signal_connect(G_OBJECT(entry), "populate-popup",
6961 G_CALLBACK(compose_entry_popup_extend),
6964 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6966 headerentry->compose = compose;
6967 headerentry->combo = combo;
6968 headerentry->entry = entry;
6969 headerentry->button = button;
6970 headerentry->hbox = hbox;
6971 headerentry->headernum = compose->header_nextrow;
6972 headerentry->type = PREF_NONE;
6974 compose->header_nextrow++;
6975 compose->header_last = headerentry;
6976 compose->header_list =
6977 g_slist_append(compose->header_list,
6981 static void compose_add_header_entry(Compose *compose, const gchar *header,
6982 gchar *text, ComposePrefType pref_type)
6984 ComposeHeaderEntry *last_header = compose->header_last;
6985 gchar *tmp = g_strdup(text), *email;
6986 gboolean replyto_hdr;
6988 replyto_hdr = (!strcasecmp(header,
6989 prefs_common_translated_header_name("Reply-To:")) ||
6991 prefs_common_translated_header_name("Followup-To:")) ||
6993 prefs_common_translated_header_name("In-Reply-To:")));
6995 extract_address(tmp);
6996 email = g_utf8_strdown(tmp, -1);
6998 if (replyto_hdr == FALSE &&
6999 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7001 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7002 header, text, (gint) pref_type);
7008 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7009 gtk_entry_set_text(GTK_ENTRY(
7010 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7012 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7013 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7014 last_header->type = pref_type;
7016 if (replyto_hdr == FALSE)
7017 g_hash_table_insert(compose->email_hashtable, email,
7018 GUINT_TO_POINTER(1));
7025 static void compose_destroy_headerentry(Compose *compose,
7026 ComposeHeaderEntry *headerentry)
7028 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7031 extract_address(text);
7032 email = g_utf8_strdown(text, -1);
7033 g_hash_table_remove(compose->email_hashtable, email);
7037 gtk_widget_destroy(headerentry->combo);
7038 gtk_widget_destroy(headerentry->entry);
7039 gtk_widget_destroy(headerentry->button);
7040 gtk_widget_destroy(headerentry->hbox);
7041 g_free(headerentry);
7044 static void compose_remove_header_entries(Compose *compose)
7047 for (list = compose->header_list; list; list = list->next)
7048 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7050 compose->header_last = NULL;
7051 g_slist_free(compose->header_list);
7052 compose->header_list = NULL;
7053 compose->header_nextrow = 1;
7054 compose_create_header_entry(compose);
7057 static GtkWidget *compose_create_header(Compose *compose)
7059 GtkWidget *from_optmenu_hbox;
7060 GtkWidget *header_scrolledwin_main;
7061 GtkWidget *header_table_main;
7062 GtkWidget *header_scrolledwin;
7063 GtkWidget *header_table;
7065 /* parent with account selection and from header */
7066 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7067 gtk_widget_show(header_scrolledwin_main);
7068 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7070 header_table_main = gtk_table_new(2, 2, FALSE);
7071 gtk_widget_show(header_table_main);
7072 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7073 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7074 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7076 from_optmenu_hbox = compose_account_option_menu_create(compose);
7077 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7078 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7080 /* child with header labels and entries */
7081 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7082 gtk_widget_show(header_scrolledwin);
7083 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7085 header_table = gtk_table_new(2, 2, FALSE);
7086 gtk_widget_show(header_table);
7087 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7088 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7089 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7090 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7091 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7093 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7094 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7096 compose->header_table = header_table;
7097 compose->header_list = NULL;
7098 compose->header_nextrow = 0;
7100 compose_create_header_entry(compose);
7102 compose->table = NULL;
7104 return header_scrolledwin_main;
7107 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7109 Compose *compose = (Compose *)data;
7110 GdkEventButton event;
7113 event.time = gtk_get_current_event_time();
7115 return attach_button_pressed(compose->attach_clist, &event, compose);
7118 static GtkWidget *compose_create_attach(Compose *compose)
7120 GtkWidget *attach_scrwin;
7121 GtkWidget *attach_clist;
7123 GtkListStore *store;
7124 GtkCellRenderer *renderer;
7125 GtkTreeViewColumn *column;
7126 GtkTreeSelection *selection;
7128 /* attachment list */
7129 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7130 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7131 GTK_POLICY_AUTOMATIC,
7132 GTK_POLICY_AUTOMATIC);
7133 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7135 store = gtk_list_store_new(N_ATTACH_COLS,
7141 G_TYPE_AUTO_POINTER,
7143 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7144 (GTK_TREE_MODEL(store)));
7145 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7146 g_object_unref(store);
7148 renderer = gtk_cell_renderer_text_new();
7149 column = gtk_tree_view_column_new_with_attributes
7150 (_("Mime type"), renderer, "text",
7151 COL_MIMETYPE, NULL);
7152 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7154 renderer = gtk_cell_renderer_text_new();
7155 column = gtk_tree_view_column_new_with_attributes
7156 (_("Size"), renderer, "text",
7158 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7160 renderer = gtk_cell_renderer_text_new();
7161 column = gtk_tree_view_column_new_with_attributes
7162 (_("Name"), renderer, "text",
7164 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7166 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7167 prefs_common.use_stripes_everywhere);
7168 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7169 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7171 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7172 G_CALLBACK(attach_selected), compose);
7173 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7174 G_CALLBACK(attach_button_pressed), compose);
7175 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7176 G_CALLBACK(popup_attach_button_pressed), compose);
7177 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7178 G_CALLBACK(attach_key_pressed), compose);
7181 gtk_drag_dest_set(attach_clist,
7182 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7183 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7184 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7185 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7186 G_CALLBACK(compose_attach_drag_received_cb),
7188 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7189 G_CALLBACK(compose_drag_drop),
7192 compose->attach_scrwin = attach_scrwin;
7193 compose->attach_clist = attach_clist;
7195 return attach_scrwin;
7198 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7199 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7201 static GtkWidget *compose_create_others(Compose *compose)
7204 GtkWidget *savemsg_checkbtn;
7205 GtkWidget *savemsg_combo;
7206 GtkWidget *savemsg_select;
7209 gchar *folderidentifier;
7211 /* Table for settings */
7212 table = gtk_table_new(3, 1, FALSE);
7213 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7214 gtk_widget_show(table);
7215 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7218 /* Save Message to folder */
7219 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7220 gtk_widget_show(savemsg_checkbtn);
7221 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7222 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7223 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7225 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7226 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7228 #if !GTK_CHECK_VERSION(2, 24, 0)
7229 savemsg_combo = gtk_combo_box_entry_new_text();
7231 savemsg_combo = gtk_combo_box_text_new_with_entry();
7233 compose->savemsg_checkbtn = savemsg_checkbtn;
7234 compose->savemsg_combo = savemsg_combo;
7235 gtk_widget_show(savemsg_combo);
7237 if (prefs_common.compose_save_to_history)
7238 #if !GTK_CHECK_VERSION(2, 24, 0)
7239 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7240 prefs_common.compose_save_to_history);
7242 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7243 prefs_common.compose_save_to_history);
7245 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7246 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7247 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7248 G_CALLBACK(compose_grab_focus_cb), compose);
7249 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7250 folderidentifier = folder_item_get_identifier(account_get_special_folder
7251 (compose->account, F_OUTBOX));
7252 compose_set_save_to(compose, folderidentifier);
7253 g_free(folderidentifier);
7256 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7257 gtk_widget_show(savemsg_select);
7258 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7259 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7260 G_CALLBACK(compose_savemsg_select_cb),
7266 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7268 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7269 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7272 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7277 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7280 path = folder_item_get_identifier(dest);
7282 compose_set_save_to(compose, path);
7286 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7287 GdkAtom clip, GtkTextIter *insert_place);
7290 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7294 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7296 if (event->button == 3) {
7298 GtkTextIter sel_start, sel_end;
7299 gboolean stuff_selected;
7301 /* move the cursor to allow GtkAspell to check the word
7302 * under the mouse */
7303 if (event->x && event->y) {
7304 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7305 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7307 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7310 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7311 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7314 stuff_selected = gtk_text_buffer_get_selection_bounds(
7316 &sel_start, &sel_end);
7318 gtk_text_buffer_place_cursor (buffer, &iter);
7319 /* reselect stuff */
7321 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7322 gtk_text_buffer_select_range(buffer,
7323 &sel_start, &sel_end);
7325 return FALSE; /* pass the event so that the right-click goes through */
7328 if (event->button == 2) {
7333 /* get the middle-click position to paste at the correct place */
7334 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7335 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7337 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7340 entry_paste_clipboard(compose, text,
7341 prefs_common.linewrap_pastes,
7342 GDK_SELECTION_PRIMARY, &iter);
7350 static void compose_spell_menu_changed(void *data)
7352 Compose *compose = (Compose *)data;
7354 GtkWidget *menuitem;
7355 GtkWidget *parent_item;
7356 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7359 if (compose->gtkaspell == NULL)
7362 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7363 "/Menu/Spelling/Options");
7365 /* setting the submenu removes /Spelling/Options from the factory
7366 * so we need to save it */
7368 if (parent_item == NULL) {
7369 parent_item = compose->aspell_options_menu;
7370 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7372 compose->aspell_options_menu = parent_item;
7374 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7376 spell_menu = g_slist_reverse(spell_menu);
7377 for (items = spell_menu;
7378 items; items = items->next) {
7379 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7380 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7381 gtk_widget_show(GTK_WIDGET(menuitem));
7383 g_slist_free(spell_menu);
7385 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7386 gtk_widget_show(parent_item);
7389 static void compose_dict_changed(void *data)
7391 Compose *compose = (Compose *) data;
7393 if(!compose->gtkaspell)
7395 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7398 gtkaspell_highlight_all(compose->gtkaspell);
7399 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7403 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7405 Compose *compose = (Compose *)data;
7406 GdkEventButton event;
7409 event.time = gtk_get_current_event_time();
7413 return text_clicked(compose->text, &event, compose);
7416 static gboolean compose_force_window_origin = TRUE;
7417 static Compose *compose_create(PrefsAccount *account,
7426 GtkWidget *handlebox;
7428 GtkWidget *notebook;
7430 GtkWidget *attach_hbox;
7431 GtkWidget *attach_lab1;
7432 GtkWidget *attach_lab2;
7437 GtkWidget *subject_hbox;
7438 GtkWidget *subject_frame;
7439 GtkWidget *subject_entry;
7443 GtkWidget *edit_vbox;
7444 GtkWidget *ruler_hbox;
7446 GtkWidget *scrolledwin;
7448 GtkTextBuffer *buffer;
7449 GtkClipboard *clipboard;
7451 UndoMain *undostruct;
7453 GtkWidget *popupmenu;
7454 GtkWidget *tmpl_menu;
7455 GtkActionGroup *action_group = NULL;
7458 GtkAspell * gtkaspell = NULL;
7461 static GdkGeometry geometry;
7463 cm_return_val_if_fail(account != NULL, NULL);
7465 debug_print("Creating compose window...\n");
7466 compose = g_new0(Compose, 1);
7468 compose->batch = batch;
7469 compose->account = account;
7470 compose->folder = folder;
7472 compose->mutex = cm_mutex_new();
7473 compose->set_cursor_pos = -1;
7475 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7477 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7478 gtk_widget_set_size_request(window, prefs_common.compose_width,
7479 prefs_common.compose_height);
7481 if (!geometry.max_width) {
7482 geometry.max_width = gdk_screen_width();
7483 geometry.max_height = gdk_screen_height();
7486 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7487 &geometry, GDK_HINT_MAX_SIZE);
7488 if (!geometry.min_width) {
7489 geometry.min_width = 600;
7490 geometry.min_height = 440;
7492 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7493 &geometry, GDK_HINT_MIN_SIZE);
7495 #ifndef GENERIC_UMPC
7496 if (compose_force_window_origin)
7497 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7498 prefs_common.compose_y);
7500 g_signal_connect(G_OBJECT(window), "delete_event",
7501 G_CALLBACK(compose_delete_cb), compose);
7502 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7503 gtk_widget_realize(window);
7505 gtkut_widget_set_composer_icon(window);
7507 vbox = gtk_vbox_new(FALSE, 0);
7508 gtk_container_add(GTK_CONTAINER(window), vbox);
7510 compose->ui_manager = gtk_ui_manager_new();
7511 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7512 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7513 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7514 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7515 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7516 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7517 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7518 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7519 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7520 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_1, "Options/Encoding/Western/"CS_ISO_8859_1, GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_ISO_8859_15, "Options/Encoding/Western/"CS_ISO_8859_15, GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_13, "Options/Encoding/Baltic/"CS_ISO_8859_13, GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Baltic", CS_ISO_8859_4, "Options/Encoding/Baltic/"CS_ISO_8859_4, GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_ISO_8859_8, "Options/Encoding/Hebrew/"CS_ISO_8859_8, GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_ISO_8859_6, "Options/Encoding/Arabic/"CS_ISO_8859_6, GTK_UI_MANAGER_MENUITEM)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_ISO_8859_5, "Options/Encoding/Cyrillic/"CS_ISO_8859_5, GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP, "Options/Encoding/Japanese/"CS_ISO_2022_JP, GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_ISO_2022_JP_2, "Options/Encoding/Japanese/"CS_ISO_2022_JP_2, GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_ISO_2022_KR, "Options/Encoding/Korean/"CS_ISO_2022_KR, GTK_UI_MANAGER_MENUITEM)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7701 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7702 gtk_widget_show_all(menubar);
7704 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7705 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7707 if (prefs_common.toolbar_detachable) {
7708 handlebox = gtk_handle_box_new();
7710 handlebox = gtk_hbox_new(FALSE, 0);
7712 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7714 gtk_widget_realize(handlebox);
7715 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7718 vbox2 = gtk_vbox_new(FALSE, 2);
7719 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7720 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7723 notebook = gtk_notebook_new();
7724 gtk_widget_show(notebook);
7726 /* header labels and entries */
7727 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7728 compose_create_header(compose),
7729 gtk_label_new_with_mnemonic(_("Hea_der")));
7730 /* attachment list */
7731 attach_hbox = gtk_hbox_new(FALSE, 0);
7732 gtk_widget_show(attach_hbox);
7734 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7735 gtk_widget_show(attach_lab1);
7736 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7738 attach_lab2 = gtk_label_new("");
7739 gtk_widget_show(attach_lab2);
7740 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7742 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7743 compose_create_attach(compose),
7746 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7747 compose_create_others(compose),
7748 gtk_label_new_with_mnemonic(_("Othe_rs")));
7751 subject_hbox = gtk_hbox_new(FALSE, 0);
7752 gtk_widget_show(subject_hbox);
7754 subject_frame = gtk_frame_new(NULL);
7755 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7756 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7757 gtk_widget_show(subject_frame);
7759 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7760 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7761 gtk_widget_show(subject);
7763 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7764 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7765 gtk_widget_show(label);
7768 subject_entry = claws_spell_entry_new();
7770 subject_entry = gtk_entry_new();
7772 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7773 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7774 G_CALLBACK(compose_grab_focus_cb), compose);
7775 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7776 gtk_widget_show(subject_entry);
7777 compose->subject_entry = subject_entry;
7778 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7780 edit_vbox = gtk_vbox_new(FALSE, 0);
7782 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7785 ruler_hbox = gtk_hbox_new(FALSE, 0);
7786 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7788 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7789 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7790 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7794 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7795 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7796 GTK_POLICY_AUTOMATIC,
7797 GTK_POLICY_AUTOMATIC);
7798 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7800 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7802 text = gtk_text_view_new();
7803 if (prefs_common.show_compose_margin) {
7804 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7805 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7807 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7808 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7809 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7810 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7811 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7813 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7814 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7815 G_CALLBACK(compose_edit_size_alloc),
7817 g_signal_connect(G_OBJECT(buffer), "changed",
7818 G_CALLBACK(compose_changed_cb), compose);
7819 g_signal_connect(G_OBJECT(text), "grab_focus",
7820 G_CALLBACK(compose_grab_focus_cb), compose);
7821 g_signal_connect(G_OBJECT(buffer), "insert_text",
7822 G_CALLBACK(text_inserted), compose);
7823 g_signal_connect(G_OBJECT(text), "button_press_event",
7824 G_CALLBACK(text_clicked), compose);
7825 g_signal_connect(G_OBJECT(text), "popup-menu",
7826 G_CALLBACK(compose_popup_menu), compose);
7827 g_signal_connect(G_OBJECT(subject_entry), "changed",
7828 G_CALLBACK(compose_changed_cb), compose);
7829 g_signal_connect(G_OBJECT(subject_entry), "activate",
7830 G_CALLBACK(compose_subject_entry_activated), compose);
7833 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7834 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7835 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7836 g_signal_connect(G_OBJECT(text), "drag_data_received",
7837 G_CALLBACK(compose_insert_drag_received_cb),
7839 g_signal_connect(G_OBJECT(text), "drag-drop",
7840 G_CALLBACK(compose_drag_drop),
7842 g_signal_connect(G_OBJECT(text), "key-press-event",
7843 G_CALLBACK(completion_set_focus_to_subject),
7845 gtk_widget_show_all(vbox);
7847 /* pane between attach clist and text */
7848 paned = gtk_vpaned_new();
7849 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7850 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7851 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7852 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7853 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7854 G_CALLBACK(compose_notebook_size_alloc), paned);
7856 gtk_widget_show_all(paned);
7859 if (prefs_common.textfont) {
7860 PangoFontDescription *font_desc;
7862 font_desc = pango_font_description_from_string
7863 (prefs_common.textfont);
7865 gtk_widget_modify_font(text, font_desc);
7866 pango_font_description_free(font_desc);
7870 gtk_action_group_add_actions(action_group, compose_popup_entries,
7871 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7874 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7875 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7876 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7877 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7879 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7881 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7882 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7883 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7885 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7887 undostruct = undo_init(text);
7888 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7891 address_completion_start(window);
7893 compose->window = window;
7894 compose->vbox = vbox;
7895 compose->menubar = menubar;
7896 compose->handlebox = handlebox;
7898 compose->vbox2 = vbox2;
7900 compose->paned = paned;
7902 compose->attach_label = attach_lab2;
7904 compose->notebook = notebook;
7905 compose->edit_vbox = edit_vbox;
7906 compose->ruler_hbox = ruler_hbox;
7907 compose->ruler = ruler;
7908 compose->scrolledwin = scrolledwin;
7909 compose->text = text;
7911 compose->focused_editable = NULL;
7913 compose->popupmenu = popupmenu;
7915 compose->tmpl_menu = tmpl_menu;
7917 compose->mode = mode;
7918 compose->rmode = mode;
7920 compose->targetinfo = NULL;
7921 compose->replyinfo = NULL;
7922 compose->fwdinfo = NULL;
7924 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7925 g_str_equal, (GDestroyNotify) g_free, NULL);
7927 compose->replyto = NULL;
7929 compose->bcc = NULL;
7930 compose->followup_to = NULL;
7932 compose->ml_post = NULL;
7934 compose->inreplyto = NULL;
7935 compose->references = NULL;
7936 compose->msgid = NULL;
7937 compose->boundary = NULL;
7939 compose->autowrap = prefs_common.autowrap;
7940 compose->autoindent = prefs_common.auto_indent;
7941 compose->use_signing = FALSE;
7942 compose->use_encryption = FALSE;
7943 compose->privacy_system = NULL;
7945 compose->modified = FALSE;
7947 compose->return_receipt = FALSE;
7949 compose->to_list = NULL;
7950 compose->newsgroup_list = NULL;
7952 compose->undostruct = undostruct;
7954 compose->sig_str = NULL;
7956 compose->exteditor_file = NULL;
7957 compose->exteditor_pid = -1;
7958 compose->exteditor_tag = -1;
7959 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7961 compose->folder_update_callback_id =
7962 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
7963 compose_update_folder_hook,
7964 (gpointer) compose);
7967 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7968 if (mode != COMPOSE_REDIRECT) {
7969 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7970 strcmp(prefs_common.dictionary, "")) {
7971 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7972 prefs_common.alt_dictionary,
7973 conv_get_locale_charset_str(),
7974 prefs_common.misspelled_col,
7975 prefs_common.check_while_typing,
7976 prefs_common.recheck_when_changing_dict,
7977 prefs_common.use_alternate,
7978 prefs_common.use_both_dicts,
7979 GTK_TEXT_VIEW(text),
7980 GTK_WINDOW(compose->window),
7981 compose_dict_changed,
7982 compose_spell_menu_changed,
7985 alertpanel_error(_("Spell checker could not "
7987 gtkaspell_checkers_strerror());
7988 gtkaspell_checkers_reset_error();
7990 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7994 compose->gtkaspell = gtkaspell;
7995 compose_spell_menu_changed(compose);
7996 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7999 compose_select_account(compose, account, TRUE);
8001 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8002 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8004 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8005 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8007 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8008 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8010 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8011 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8013 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8014 if (account->protocol != A_NNTP)
8015 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8016 prefs_common_translated_header_name("To:"));
8018 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8019 prefs_common_translated_header_name("Newsgroups:"));
8021 #ifndef USE_NEW_ADDRBOOK
8022 addressbook_set_target_compose(compose);
8024 if (mode != COMPOSE_REDIRECT)
8025 compose_set_template_menu(compose);
8027 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8030 compose_list = g_list_append(compose_list, compose);
8032 if (!prefs_common.show_ruler)
8033 gtk_widget_hide(ruler_hbox);
8035 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8038 compose->priority = PRIORITY_NORMAL;
8039 compose_update_priority_menu_item(compose);
8041 compose_set_out_encoding(compose);
8044 compose_update_actions_menu(compose);
8046 /* Privacy Systems menu */
8047 compose_update_privacy_systems_menu(compose);
8049 activate_privacy_system(compose, account, TRUE);
8050 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8052 gtk_widget_realize(window);
8054 gtk_widget_show(window);
8060 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8065 GtkWidget *optmenubox;
8066 GtkWidget *fromlabel;
8069 GtkWidget *from_name = NULL;
8071 gint num = 0, def_menu = 0;
8073 accounts = account_get_list();
8074 cm_return_val_if_fail(accounts != NULL, NULL);
8076 optmenubox = gtk_event_box_new();
8077 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8078 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8080 hbox = gtk_hbox_new(FALSE, 4);
8081 from_name = gtk_entry_new();
8083 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8084 G_CALLBACK(compose_grab_focus_cb), compose);
8085 g_signal_connect_after(G_OBJECT(from_name), "activate",
8086 G_CALLBACK(from_name_activate_cb), optmenu);
8088 for (; accounts != NULL; accounts = accounts->next, num++) {
8089 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8090 gchar *name, *from = NULL;
8092 if (ac == compose->account) def_menu = num;
8094 name = g_markup_printf_escaped(_("<i>%s</i>"),
8097 if (ac == compose->account) {
8098 if (ac->name && *ac->name) {
8100 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8101 from = g_strdup_printf("%s <%s>",
8103 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8105 from = g_strdup_printf("%s",
8107 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8110 COMBOBOX_ADD(menu, name, ac->account_id);
8115 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8117 g_signal_connect(G_OBJECT(optmenu), "changed",
8118 G_CALLBACK(account_activated),
8120 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8121 G_CALLBACK(compose_entry_popup_extend),
8124 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8125 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8127 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8128 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8129 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8131 /* Putting only the GtkEntry into focus chain of parent hbox causes
8132 * the account selector combobox next to it to be unreachable when
8133 * navigating widgets in GtkTable with up/down arrow keys.
8134 * Note: gtk_widget_set_can_focus() was not enough. */
8136 l = g_list_prepend(l, from_name);
8137 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8140 CLAWS_SET_TIP(optmenubox,
8141 _("Account to use for this email"));
8142 CLAWS_SET_TIP(from_name,
8143 _("Sender address to be used"));
8145 compose->account_combo = optmenu;
8146 compose->from_name = from_name;
8151 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8153 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8154 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8155 Compose *compose = (Compose *) data;
8157 compose->priority = value;
8161 static void compose_reply_change_mode(Compose *compose,
8164 gboolean was_modified = compose->modified;
8166 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8168 cm_return_if_fail(compose->replyinfo != NULL);
8170 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8172 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8174 if (action == COMPOSE_REPLY_TO_ALL)
8176 if (action == COMPOSE_REPLY_TO_SENDER)
8178 if (action == COMPOSE_REPLY_TO_LIST)
8181 compose_remove_header_entries(compose);
8182 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8183 if (compose->account->set_autocc && compose->account->auto_cc)
8184 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8186 if (compose->account->set_autobcc && compose->account->auto_bcc)
8187 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8189 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8190 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8191 compose_show_first_last_header(compose, TRUE);
8192 compose->modified = was_modified;
8193 compose_set_title(compose);
8196 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8198 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8199 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8200 Compose *compose = (Compose *) data;
8203 compose_reply_change_mode(compose, value);
8206 static void compose_update_priority_menu_item(Compose * compose)
8208 GtkWidget *menuitem = NULL;
8209 switch (compose->priority) {
8210 case PRIORITY_HIGHEST:
8211 menuitem = gtk_ui_manager_get_widget
8212 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8215 menuitem = gtk_ui_manager_get_widget
8216 (compose->ui_manager, "/Menu/Options/Priority/High");
8218 case PRIORITY_NORMAL:
8219 menuitem = gtk_ui_manager_get_widget
8220 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8223 menuitem = gtk_ui_manager_get_widget
8224 (compose->ui_manager, "/Menu/Options/Priority/Low");
8226 case PRIORITY_LOWEST:
8227 menuitem = gtk_ui_manager_get_widget
8228 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8231 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8234 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8236 Compose *compose = (Compose *) data;
8238 gboolean can_sign = FALSE, can_encrypt = FALSE;
8240 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8242 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8245 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8246 g_free(compose->privacy_system);
8247 compose->privacy_system = NULL;
8248 if (systemid != NULL) {
8249 compose->privacy_system = g_strdup(systemid);
8251 can_sign = privacy_system_can_sign(systemid);
8252 can_encrypt = privacy_system_can_encrypt(systemid);
8255 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8257 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8258 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8261 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8263 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8264 GtkWidget *menuitem = NULL;
8265 GList *children, *amenu;
8266 gboolean can_sign = FALSE, can_encrypt = FALSE;
8267 gboolean found = FALSE;
8269 if (compose->privacy_system != NULL) {
8271 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8272 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8273 cm_return_if_fail(menuitem != NULL);
8275 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8278 while (amenu != NULL) {
8279 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8280 if (systemid != NULL) {
8281 if (strcmp(systemid, compose->privacy_system) == 0 &&
8282 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8283 menuitem = GTK_WIDGET(amenu->data);
8285 can_sign = privacy_system_can_sign(systemid);
8286 can_encrypt = privacy_system_can_encrypt(systemid);
8290 } else if (strlen(compose->privacy_system) == 0 &&
8291 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8292 menuitem = GTK_WIDGET(amenu->data);
8295 can_encrypt = FALSE;
8300 amenu = amenu->next;
8302 g_list_free(children);
8303 if (menuitem != NULL)
8304 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8306 if (warn && !found && strlen(compose->privacy_system)) {
8307 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8308 "will not be able to sign or encrypt this message."),
8309 compose->privacy_system);
8313 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8314 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8317 static void compose_set_out_encoding(Compose *compose)
8319 CharSet out_encoding;
8320 const gchar *branch = NULL;
8321 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8323 switch(out_encoding) {
8324 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8325 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8326 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8327 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8328 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8329 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8330 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8331 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8332 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8333 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8334 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8335 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8336 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8337 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8338 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8339 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8340 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8341 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8342 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8343 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8344 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8345 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8346 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8347 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8348 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8349 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8350 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8351 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8352 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8353 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8354 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8355 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8356 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8357 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8359 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8362 static void compose_set_template_menu(Compose *compose)
8364 GSList *tmpl_list, *cur;
8368 tmpl_list = template_get_config();
8370 menu = gtk_menu_new();
8372 gtk_menu_set_accel_group (GTK_MENU (menu),
8373 gtk_ui_manager_get_accel_group(compose->ui_manager));
8374 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8375 Template *tmpl = (Template *)cur->data;
8376 gchar *accel_path = NULL;
8377 item = gtk_menu_item_new_with_label(tmpl->name);
8378 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8379 g_signal_connect(G_OBJECT(item), "activate",
8380 G_CALLBACK(compose_template_activate_cb),
8382 g_object_set_data(G_OBJECT(item), "template", tmpl);
8383 gtk_widget_show(item);
8384 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8385 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8389 gtk_widget_show(menu);
8390 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8393 void compose_update_actions_menu(Compose *compose)
8395 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8398 static void compose_update_privacy_systems_menu(Compose *compose)
8400 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8401 GSList *systems, *cur;
8403 GtkWidget *system_none;
8405 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8406 GtkWidget *privacy_menu = gtk_menu_new();
8408 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8409 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8411 g_signal_connect(G_OBJECT(system_none), "activate",
8412 G_CALLBACK(compose_set_privacy_system_cb), compose);
8414 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8415 gtk_widget_show(system_none);
8417 systems = privacy_get_system_ids();
8418 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8419 gchar *systemid = cur->data;
8421 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8422 widget = gtk_radio_menu_item_new_with_label(group,
8423 privacy_system_get_name(systemid));
8424 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8425 g_strdup(systemid), g_free);
8426 g_signal_connect(G_OBJECT(widget), "activate",
8427 G_CALLBACK(compose_set_privacy_system_cb), compose);
8429 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8430 gtk_widget_show(widget);
8433 g_slist_free(systems);
8434 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8435 gtk_widget_show_all(privacy_menu);
8436 gtk_widget_show_all(privacy_menuitem);
8439 void compose_reflect_prefs_all(void)
8444 for (cur = compose_list; cur != NULL; cur = cur->next) {
8445 compose = (Compose *)cur->data;
8446 compose_set_template_menu(compose);
8450 void compose_reflect_prefs_pixmap_theme(void)
8455 for (cur = compose_list; cur != NULL; cur = cur->next) {
8456 compose = (Compose *)cur->data;
8457 toolbar_update(TOOLBAR_COMPOSE, compose);
8461 static const gchar *compose_quote_char_from_context(Compose *compose)
8463 const gchar *qmark = NULL;
8465 cm_return_val_if_fail(compose != NULL, NULL);
8467 switch (compose->mode) {
8468 /* use forward-specific quote char */
8469 case COMPOSE_FORWARD:
8470 case COMPOSE_FORWARD_AS_ATTACH:
8471 case COMPOSE_FORWARD_INLINE:
8472 if (compose->folder && compose->folder->prefs &&
8473 compose->folder->prefs->forward_with_format)
8474 qmark = compose->folder->prefs->forward_quotemark;
8475 else if (compose->account->forward_with_format)
8476 qmark = compose->account->forward_quotemark;
8478 qmark = prefs_common.fw_quotemark;
8481 /* use reply-specific quote char in all other modes */
8483 if (compose->folder && compose->folder->prefs &&
8484 compose->folder->prefs->reply_with_format)
8485 qmark = compose->folder->prefs->reply_quotemark;
8486 else if (compose->account->reply_with_format)
8487 qmark = compose->account->reply_quotemark;
8489 qmark = prefs_common.quotemark;
8493 if (qmark == NULL || *qmark == '\0')
8499 static void compose_template_apply(Compose *compose, Template *tmpl,
8503 GtkTextBuffer *buffer;
8507 gchar *parsed_str = NULL;
8508 gint cursor_pos = 0;
8509 const gchar *err_msg = _("The body of the template has an error at line %d.");
8512 /* process the body */
8514 text = GTK_TEXT_VIEW(compose->text);
8515 buffer = gtk_text_view_get_buffer(text);
8518 qmark = compose_quote_char_from_context(compose);
8520 if (compose->replyinfo != NULL) {
8523 gtk_text_buffer_set_text(buffer, "", -1);
8524 mark = gtk_text_buffer_get_insert(buffer);
8525 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8527 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8528 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8530 } else if (compose->fwdinfo != NULL) {
8533 gtk_text_buffer_set_text(buffer, "", -1);
8534 mark = gtk_text_buffer_get_insert(buffer);
8535 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8537 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8538 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8541 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8543 GtkTextIter start, end;
8546 gtk_text_buffer_get_start_iter(buffer, &start);
8547 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8548 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8550 /* clear the buffer now */
8552 gtk_text_buffer_set_text(buffer, "", -1);
8554 parsed_str = compose_quote_fmt(compose, dummyinfo,
8555 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8556 procmsg_msginfo_free( dummyinfo );
8562 gtk_text_buffer_set_text(buffer, "", -1);
8563 mark = gtk_text_buffer_get_insert(buffer);
8564 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8567 if (replace && parsed_str && compose->account->auto_sig)
8568 compose_insert_sig(compose, FALSE);
8570 if (replace && parsed_str) {
8571 gtk_text_buffer_get_start_iter(buffer, &iter);
8572 gtk_text_buffer_place_cursor(buffer, &iter);
8576 cursor_pos = quote_fmt_get_cursor_pos();
8577 compose->set_cursor_pos = cursor_pos;
8578 if (cursor_pos == -1)
8580 gtk_text_buffer_get_start_iter(buffer, &iter);
8581 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8582 gtk_text_buffer_place_cursor(buffer, &iter);
8585 /* process the other fields */
8587 compose_template_apply_fields(compose, tmpl);
8588 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8589 quote_fmt_reset_vartable();
8590 compose_changed_cb(NULL, compose);
8593 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8594 gtkaspell_highlight_all(compose->gtkaspell);
8598 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8600 MsgInfo* dummyinfo = NULL;
8601 MsgInfo *msginfo = NULL;
8604 if (compose->replyinfo != NULL)
8605 msginfo = compose->replyinfo;
8606 else if (compose->fwdinfo != NULL)
8607 msginfo = compose->fwdinfo;
8609 dummyinfo = compose_msginfo_new_from_compose(compose);
8610 msginfo = dummyinfo;
8613 if (tmpl->from && *tmpl->from != '\0') {
8615 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8616 compose->gtkaspell);
8618 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8620 quote_fmt_scan_string(tmpl->from);
8623 buf = quote_fmt_get_buffer();
8625 alertpanel_error(_("Template From format error."));
8627 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8631 if (tmpl->to && *tmpl->to != '\0') {
8633 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8634 compose->gtkaspell);
8636 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8638 quote_fmt_scan_string(tmpl->to);
8641 buf = quote_fmt_get_buffer();
8643 alertpanel_error(_("Template To format error."));
8645 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8649 if (tmpl->cc && *tmpl->cc != '\0') {
8651 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8652 compose->gtkaspell);
8654 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8656 quote_fmt_scan_string(tmpl->cc);
8659 buf = quote_fmt_get_buffer();
8661 alertpanel_error(_("Template Cc format error."));
8663 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8667 if (tmpl->bcc && *tmpl->bcc != '\0') {
8669 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8670 compose->gtkaspell);
8672 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8674 quote_fmt_scan_string(tmpl->bcc);
8677 buf = quote_fmt_get_buffer();
8679 alertpanel_error(_("Template Bcc format error."));
8681 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8685 if (tmpl->replyto && *tmpl->replyto != '\0') {
8687 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8688 compose->gtkaspell);
8690 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8692 quote_fmt_scan_string(tmpl->replyto);
8695 buf = quote_fmt_get_buffer();
8697 alertpanel_error(_("Template Reply-To format error."));
8699 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8703 /* process the subject */
8704 if (tmpl->subject && *tmpl->subject != '\0') {
8706 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8707 compose->gtkaspell);
8709 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8711 quote_fmt_scan_string(tmpl->subject);
8714 buf = quote_fmt_get_buffer();
8716 alertpanel_error(_("Template subject format error."));
8718 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8722 procmsg_msginfo_free( dummyinfo );
8725 static void compose_destroy(Compose *compose)
8727 GtkAllocation allocation;
8728 GtkTextBuffer *buffer;
8729 GtkClipboard *clipboard;
8731 compose_list = g_list_remove(compose_list, compose);
8733 if (compose->updating) {
8734 debug_print("danger, not destroying anything now\n");
8735 compose->deferred_destroy = TRUE;
8739 /* NOTE: address_completion_end() does nothing with the window
8740 * however this may change. */
8741 address_completion_end(compose->window);
8743 slist_free_strings_full(compose->to_list);
8744 slist_free_strings_full(compose->newsgroup_list);
8745 slist_free_strings_full(compose->header_list);
8747 slist_free_strings_full(extra_headers);
8748 extra_headers = NULL;
8750 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8752 g_hash_table_destroy(compose->email_hashtable);
8754 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8755 compose->folder_update_callback_id);
8757 procmsg_msginfo_free(compose->targetinfo);
8758 procmsg_msginfo_free(compose->replyinfo);
8759 procmsg_msginfo_free(compose->fwdinfo);
8761 g_free(compose->replyto);
8762 g_free(compose->cc);
8763 g_free(compose->bcc);
8764 g_free(compose->newsgroups);
8765 g_free(compose->followup_to);
8767 g_free(compose->ml_post);
8769 g_free(compose->inreplyto);
8770 g_free(compose->references);
8771 g_free(compose->msgid);
8772 g_free(compose->boundary);
8774 g_free(compose->redirect_filename);
8775 if (compose->undostruct)
8776 undo_destroy(compose->undostruct);
8778 g_free(compose->sig_str);
8780 g_free(compose->exteditor_file);
8782 g_free(compose->orig_charset);
8784 g_free(compose->privacy_system);
8786 #ifndef USE_NEW_ADDRBOOK
8787 if (addressbook_get_target_compose() == compose)
8788 addressbook_set_target_compose(NULL);
8791 if (compose->gtkaspell) {
8792 gtkaspell_delete(compose->gtkaspell);
8793 compose->gtkaspell = NULL;
8797 if (!compose->batch) {
8798 gtk_widget_get_allocation(compose->window, &allocation);
8799 prefs_common.compose_width = allocation.width;
8800 prefs_common.compose_height = allocation.height;
8803 if (!gtk_widget_get_parent(compose->paned))
8804 gtk_widget_destroy(compose->paned);
8805 gtk_widget_destroy(compose->popupmenu);
8807 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8808 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8809 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8811 gtk_widget_destroy(compose->window);
8812 toolbar_destroy(compose->toolbar);
8813 g_free(compose->toolbar);
8814 cm_mutex_free(compose->mutex);
8818 static void compose_attach_info_free(AttachInfo *ainfo)
8820 g_free(ainfo->file);
8821 g_free(ainfo->content_type);
8822 g_free(ainfo->name);
8823 g_free(ainfo->charset);
8827 static void compose_attach_update_label(Compose *compose)
8832 GtkTreeModel *model;
8837 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8838 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8839 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8843 while(gtk_tree_model_iter_next(model, &iter))
8846 text = g_strdup_printf("(%d)", i);
8847 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8851 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8853 Compose *compose = (Compose *)data;
8854 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8855 GtkTreeSelection *selection;
8857 GtkTreeModel *model;
8859 selection = gtk_tree_view_get_selection(tree_view);
8860 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8865 for (cur = sel; cur != NULL; cur = cur->next) {
8866 GtkTreePath *path = cur->data;
8867 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8870 gtk_tree_path_free(path);
8873 for (cur = sel; cur != NULL; cur = cur->next) {
8874 GtkTreeRowReference *ref = cur->data;
8875 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8878 if (gtk_tree_model_get_iter(model, &iter, path))
8879 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8881 gtk_tree_path_free(path);
8882 gtk_tree_row_reference_free(ref);
8886 compose_attach_update_label(compose);
8889 static struct _AttachProperty
8892 GtkWidget *mimetype_entry;
8893 GtkWidget *encoding_optmenu;
8894 GtkWidget *path_entry;
8895 GtkWidget *filename_entry;
8897 GtkWidget *cancel_btn;
8900 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8902 gtk_tree_path_free((GtkTreePath *)ptr);
8905 static void compose_attach_property(GtkAction *action, gpointer data)
8907 Compose *compose = (Compose *)data;
8908 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8910 GtkComboBox *optmenu;
8911 GtkTreeSelection *selection;
8913 GtkTreeModel *model;
8916 static gboolean cancelled;
8918 /* only if one selected */
8919 selection = gtk_tree_view_get_selection(tree_view);
8920 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8923 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8927 path = (GtkTreePath *) sel->data;
8928 gtk_tree_model_get_iter(model, &iter, path);
8929 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8932 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8938 if (!attach_prop.window)
8939 compose_attach_property_create(&cancelled);
8940 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8941 gtk_widget_grab_focus(attach_prop.ok_btn);
8942 gtk_widget_show(attach_prop.window);
8943 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8944 GTK_WINDOW(compose->window));
8946 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8947 if (ainfo->encoding == ENC_UNKNOWN)
8948 combobox_select_by_data(optmenu, ENC_BASE64);
8950 combobox_select_by_data(optmenu, ainfo->encoding);
8952 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8953 ainfo->content_type ? ainfo->content_type : "");
8954 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8955 ainfo->file ? ainfo->file : "");
8956 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8957 ainfo->name ? ainfo->name : "");
8960 const gchar *entry_text;
8962 gchar *cnttype = NULL;
8969 gtk_widget_hide(attach_prop.window);
8970 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8975 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8976 if (*entry_text != '\0') {
8979 text = g_strstrip(g_strdup(entry_text));
8980 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8981 cnttype = g_strdup(text);
8984 alertpanel_error(_("Invalid MIME type."));
8990 ainfo->encoding = combobox_get_active_data(optmenu);
8992 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8993 if (*entry_text != '\0') {
8994 if (is_file_exist(entry_text) &&
8995 (size = get_file_size(entry_text)) > 0)
8996 file = g_strdup(entry_text);
8999 (_("File doesn't exist or is empty."));
9005 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9006 if (*entry_text != '\0') {
9007 g_free(ainfo->name);
9008 ainfo->name = g_strdup(entry_text);
9012 g_free(ainfo->content_type);
9013 ainfo->content_type = cnttype;
9016 g_free(ainfo->file);
9020 ainfo->size = (goffset)size;
9022 /* update tree store */
9023 text = to_human_readable(ainfo->size);
9024 gtk_tree_model_get_iter(model, &iter, path);
9025 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9026 COL_MIMETYPE, ainfo->content_type,
9028 COL_NAME, ainfo->name,
9029 COL_CHARSET, ainfo->charset,
9035 gtk_tree_path_free(path);
9038 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9040 label = gtk_label_new(str); \
9041 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9042 GTK_FILL, 0, 0, 0); \
9043 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9045 entry = gtk_entry_new(); \
9046 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9047 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9050 static void compose_attach_property_create(gboolean *cancelled)
9056 GtkWidget *mimetype_entry;
9059 GtkListStore *optmenu_menu;
9060 GtkWidget *path_entry;
9061 GtkWidget *filename_entry;
9064 GtkWidget *cancel_btn;
9065 GList *mime_type_list, *strlist;
9068 debug_print("Creating attach_property window...\n");
9070 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9071 gtk_widget_set_size_request(window, 480, -1);
9072 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9073 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9074 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9075 g_signal_connect(G_OBJECT(window), "delete_event",
9076 G_CALLBACK(attach_property_delete_event),
9078 g_signal_connect(G_OBJECT(window), "key_press_event",
9079 G_CALLBACK(attach_property_key_pressed),
9082 vbox = gtk_vbox_new(FALSE, 8);
9083 gtk_container_add(GTK_CONTAINER(window), vbox);
9085 table = gtk_table_new(4, 2, FALSE);
9086 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9087 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9088 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9090 label = gtk_label_new(_("MIME type"));
9091 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9093 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9094 #if !GTK_CHECK_VERSION(2, 24, 0)
9095 mimetype_entry = gtk_combo_box_entry_new_text();
9097 mimetype_entry = gtk_combo_box_text_new_with_entry();
9099 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9100 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9102 /* stuff with list */
9103 mime_type_list = procmime_get_mime_type_list();
9105 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9106 MimeType *type = (MimeType *) mime_type_list->data;
9109 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9111 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9114 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9115 (GCompareFunc)strcmp2);
9118 for (mime_type_list = strlist; mime_type_list != NULL;
9119 mime_type_list = mime_type_list->next) {
9120 #if !GTK_CHECK_VERSION(2, 24, 0)
9121 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9123 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9125 g_free(mime_type_list->data);
9127 g_list_free(strlist);
9128 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9129 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9131 label = gtk_label_new(_("Encoding"));
9132 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9134 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9136 hbox = gtk_hbox_new(FALSE, 0);
9137 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9138 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9140 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9141 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9143 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9144 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9145 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9146 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9147 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9149 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9151 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9152 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9154 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9155 &ok_btn, GTK_STOCK_OK,
9157 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9158 gtk_widget_grab_default(ok_btn);
9160 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9161 G_CALLBACK(attach_property_ok),
9163 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9164 G_CALLBACK(attach_property_cancel),
9167 gtk_widget_show_all(vbox);
9169 attach_prop.window = window;
9170 attach_prop.mimetype_entry = mimetype_entry;
9171 attach_prop.encoding_optmenu = optmenu;
9172 attach_prop.path_entry = path_entry;
9173 attach_prop.filename_entry = filename_entry;
9174 attach_prop.ok_btn = ok_btn;
9175 attach_prop.cancel_btn = cancel_btn;
9178 #undef SET_LABEL_AND_ENTRY
9180 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9186 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9192 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9193 gboolean *cancelled)
9201 static gboolean attach_property_key_pressed(GtkWidget *widget,
9203 gboolean *cancelled)
9205 if (event && event->keyval == GDK_KEY_Escape) {
9209 if (event && event->keyval == GDK_KEY_Return) {
9217 static void compose_exec_ext_editor(Compose *compose)
9224 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9225 G_DIR_SEPARATOR, compose);
9227 if (pipe(pipe_fds) < 0) {
9233 if ((pid = fork()) < 0) {
9240 /* close the write side of the pipe */
9243 compose->exteditor_file = g_strdup(tmp);
9244 compose->exteditor_pid = pid;
9246 compose_set_ext_editor_sensitive(compose, FALSE);
9249 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9251 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9253 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9257 } else { /* process-monitoring process */
9263 /* close the read side of the pipe */
9266 if (compose_write_body_to_file(compose, tmp) < 0) {
9267 fd_write_all(pipe_fds[1], "2\n", 2);
9271 pid_ed = compose_exec_ext_editor_real(tmp);
9273 fd_write_all(pipe_fds[1], "1\n", 2);
9277 /* wait until editor is terminated */
9278 waitpid(pid_ed, NULL, 0);
9280 fd_write_all(pipe_fds[1], "0\n", 2);
9287 #endif /* G_OS_UNIX */
9291 static gint compose_exec_ext_editor_real(const gchar *file)
9298 cm_return_val_if_fail(file != NULL, -1);
9300 if ((pid = fork()) < 0) {
9305 if (pid != 0) return pid;
9307 /* grandchild process */
9309 if (setpgid(0, getppid()))
9312 if (prefs_common_get_ext_editor_cmd() &&
9313 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9314 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9315 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9317 if (prefs_common_get_ext_editor_cmd())
9318 g_warning("External editor command-line is invalid: '%s'\n",
9319 prefs_common_get_ext_editor_cmd());
9320 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9323 cmdline = strsplit_with_quote(buf, " ", 1024);
9324 execvp(cmdline[0], cmdline);
9327 g_strfreev(cmdline);
9332 static gboolean compose_ext_editor_kill(Compose *compose)
9334 pid_t pgid = compose->exteditor_pid * -1;
9337 ret = kill(pgid, 0);
9339 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9343 msg = g_strdup_printf
9344 (_("The external editor is still working.\n"
9345 "Force terminating the process?\n"
9346 "process group id: %d"), -pgid);
9347 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9348 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9352 if (val == G_ALERTALTERNATE) {
9353 g_source_remove(compose->exteditor_tag);
9354 g_io_channel_shutdown(compose->exteditor_ch,
9356 g_io_channel_unref(compose->exteditor_ch);
9358 if (kill(pgid, SIGTERM) < 0) perror("kill");
9359 waitpid(compose->exteditor_pid, NULL, 0);
9361 g_warning("Terminated process group id: %d", -pgid);
9362 g_warning("Temporary file: %s",
9363 compose->exteditor_file);
9365 compose_set_ext_editor_sensitive(compose, TRUE);
9367 g_free(compose->exteditor_file);
9368 compose->exteditor_file = NULL;
9369 compose->exteditor_pid = -1;
9370 compose->exteditor_ch = NULL;
9371 compose->exteditor_tag = -1;
9379 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9383 Compose *compose = (Compose *)data;
9386 debug_print("Compose: input from monitoring process\n");
9388 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9390 g_io_channel_shutdown(source, FALSE, NULL);
9391 g_io_channel_unref(source);
9393 waitpid(compose->exteditor_pid, NULL, 0);
9395 if (buf[0] == '0') { /* success */
9396 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9397 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9398 GtkTextIter start, end;
9401 gtk_text_buffer_set_text(buffer, "", -1);
9402 compose_insert_file(compose, compose->exteditor_file);
9403 compose_changed_cb(NULL, compose);
9404 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9406 if (claws_unlink(compose->exteditor_file) < 0)
9407 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9409 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9410 gtk_text_buffer_get_start_iter(buffer, &start);
9411 gtk_text_buffer_get_end_iter(buffer, &end);
9412 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9413 if (chars && strlen(chars) > 0)
9414 compose->modified = TRUE;
9416 } else if (buf[0] == '1') { /* failed */
9417 g_warning("Couldn't exec external editor\n");
9418 if (claws_unlink(compose->exteditor_file) < 0)
9419 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9420 } else if (buf[0] == '2') {
9421 g_warning("Couldn't write to file\n");
9422 } else if (buf[0] == '3') {
9423 g_warning("Pipe read failed\n");
9426 compose_set_ext_editor_sensitive(compose, TRUE);
9428 g_free(compose->exteditor_file);
9429 compose->exteditor_file = NULL;
9430 compose->exteditor_pid = -1;
9431 compose->exteditor_ch = NULL;
9432 compose->exteditor_tag = -1;
9437 static void compose_set_ext_editor_sensitive(Compose *compose,
9440 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9441 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9442 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9443 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9444 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9445 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9446 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9447 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9449 gtk_widget_set_sensitive(compose->text, sensitive);
9450 if (compose->toolbar->send_btn)
9451 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9452 if (compose->toolbar->sendl_btn)
9453 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9454 if (compose->toolbar->draft_btn)
9455 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9456 if (compose->toolbar->insert_btn)
9457 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9458 if (compose->toolbar->sig_btn)
9459 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9460 if (compose->toolbar->exteditor_btn)
9461 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9462 if (compose->toolbar->linewrap_current_btn)
9463 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9464 if (compose->toolbar->linewrap_all_btn)
9465 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9467 #endif /* G_OS_UNIX */
9470 * compose_undo_state_changed:
9472 * Change the sensivity of the menuentries undo and redo
9474 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9475 gint redo_state, gpointer data)
9477 Compose *compose = (Compose *)data;
9479 switch (undo_state) {
9480 case UNDO_STATE_TRUE:
9481 if (!undostruct->undo_state) {
9482 undostruct->undo_state = TRUE;
9483 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9486 case UNDO_STATE_FALSE:
9487 if (undostruct->undo_state) {
9488 undostruct->undo_state = FALSE;
9489 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9492 case UNDO_STATE_UNCHANGED:
9494 case UNDO_STATE_REFRESH:
9495 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9498 g_warning("Undo state not recognized");
9502 switch (redo_state) {
9503 case UNDO_STATE_TRUE:
9504 if (!undostruct->redo_state) {
9505 undostruct->redo_state = TRUE;
9506 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9509 case UNDO_STATE_FALSE:
9510 if (undostruct->redo_state) {
9511 undostruct->redo_state = FALSE;
9512 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9515 case UNDO_STATE_UNCHANGED:
9517 case UNDO_STATE_REFRESH:
9518 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9521 g_warning("Redo state not recognized");
9526 /* callback functions */
9528 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9529 GtkAllocation *allocation,
9532 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9535 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9536 * includes "non-client" (windows-izm) in calculation, so this calculation
9537 * may not be accurate.
9539 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9540 GtkAllocation *allocation,
9541 GtkSHRuler *shruler)
9543 if (prefs_common.show_ruler) {
9544 gint char_width = 0, char_height = 0;
9545 gint line_width_in_chars;
9547 gtkut_get_font_size(GTK_WIDGET(widget),
9548 &char_width, &char_height);
9549 line_width_in_chars =
9550 (allocation->width - allocation->x) / char_width;
9552 /* got the maximum */
9553 gtk_shruler_set_range(GTK_SHRULER(shruler),
9554 0.0, line_width_in_chars, 0);
9563 ComposePrefType type;
9564 gboolean entry_marked;
9567 static void account_activated(GtkComboBox *optmenu, gpointer data)
9569 Compose *compose = (Compose *)data;
9572 gchar *folderidentifier;
9573 gint account_id = 0;
9576 GSList *list, *saved_list = NULL;
9577 HeaderEntryState *state;
9578 GtkRcStyle *style = NULL;
9579 #if !GTK_CHECK_VERSION(3, 0, 0)
9580 static GdkColor yellow;
9581 static gboolean color_set = FALSE;
9583 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9586 /* Get ID of active account in the combo box */
9587 menu = gtk_combo_box_get_model(optmenu);
9588 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9589 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9591 ac = account_find_from_id(account_id);
9592 cm_return_if_fail(ac != NULL);
9594 if (ac != compose->account) {
9595 compose_select_account(compose, ac, FALSE);
9597 for (list = compose->header_list; list; list = list->next) {
9598 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9600 if (hentry->type == PREF_ACCOUNT || !list->next) {
9601 compose_destroy_headerentry(compose, hentry);
9605 state = g_malloc0(sizeof(HeaderEntryState));
9606 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9607 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9608 state->entry = gtk_editable_get_chars(
9609 GTK_EDITABLE(hentry->entry), 0, -1);
9610 state->type = hentry->type;
9612 #if !GTK_CHECK_VERSION(3, 0, 0)
9614 gdk_color_parse("#f5f6be", &yellow);
9615 color_set = gdk_colormap_alloc_color(
9616 gdk_colormap_get_system(),
9617 &yellow, FALSE, TRUE);
9621 style = gtk_widget_get_modifier_style(hentry->entry);
9622 state->entry_marked = gdk_color_equal(&yellow,
9623 &style->base[GTK_STATE_NORMAL]);
9625 saved_list = g_slist_append(saved_list, state);
9626 compose_destroy_headerentry(compose, hentry);
9629 compose->header_last = NULL;
9630 g_slist_free(compose->header_list);
9631 compose->header_list = NULL;
9632 compose->header_nextrow = 1;
9633 compose_create_header_entry(compose);
9635 if (ac->set_autocc && ac->auto_cc)
9636 compose_entry_append(compose, ac->auto_cc,
9637 COMPOSE_CC, PREF_ACCOUNT);
9639 if (ac->set_autobcc && ac->auto_bcc)
9640 compose_entry_append(compose, ac->auto_bcc,
9641 COMPOSE_BCC, PREF_ACCOUNT);
9643 if (ac->set_autoreplyto && ac->auto_replyto)
9644 compose_entry_append(compose, ac->auto_replyto,
9645 COMPOSE_REPLYTO, PREF_ACCOUNT);
9647 for (list = saved_list; list; list = list->next) {
9648 state = (HeaderEntryState *) list->data;
9650 compose_add_header_entry(compose, state->header,
9651 state->entry, state->type);
9652 if (state->entry_marked)
9653 compose_entry_mark_default_to(compose, state->entry);
9655 g_free(state->header);
9656 g_free(state->entry);
9659 g_slist_free(saved_list);
9661 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9662 (ac->protocol == A_NNTP) ?
9663 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9666 /* Set message save folder */
9667 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9668 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9670 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9671 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9673 compose_set_save_to(compose, NULL);
9674 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9675 folderidentifier = folder_item_get_identifier(account_get_special_folder
9676 (compose->account, F_OUTBOX));
9677 compose_set_save_to(compose, folderidentifier);
9678 g_free(folderidentifier);
9682 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9683 GtkTreeViewColumn *column, Compose *compose)
9685 compose_attach_property(NULL, compose);
9688 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9691 Compose *compose = (Compose *)data;
9692 GtkTreeSelection *attach_selection;
9693 gint attach_nr_selected;
9695 if (!event) return FALSE;
9697 if (event->button == 3) {
9698 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9699 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9701 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9702 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9704 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9705 NULL, NULL, event->button, event->time);
9712 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9715 Compose *compose = (Compose *)data;
9717 if (!event) return FALSE;
9719 switch (event->keyval) {
9720 case GDK_KEY_Delete:
9721 compose_attach_remove_selected(NULL, compose);
9727 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9729 toolbar_comp_set_sensitive(compose, allow);
9730 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9731 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9733 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9735 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9736 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9737 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9739 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9743 static void compose_send_cb(GtkAction *action, gpointer data)
9745 Compose *compose = (Compose *)data;
9747 if (prefs_common.work_offline &&
9748 !inc_offline_should_override(TRUE,
9749 _("Claws Mail needs network access in order "
9750 "to send this email.")))
9753 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9754 g_source_remove(compose->draft_timeout_tag);
9755 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9758 compose_send(compose);
9761 static void compose_send_later_cb(GtkAction *action, gpointer data)
9763 Compose *compose = (Compose *)data;
9767 compose_allow_user_actions(compose, FALSE);
9768 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9769 compose_allow_user_actions(compose, TRUE);
9773 compose_close(compose);
9774 } else if (val == -1) {
9775 alertpanel_error(_("Could not queue message."));
9776 } else if (val == -2) {
9777 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9778 } else if (val == -3) {
9779 if (privacy_peek_error())
9780 alertpanel_error(_("Could not queue message for sending:\n\n"
9781 "Signature failed: %s"), privacy_get_error());
9782 } else if (val == -4) {
9783 alertpanel_error(_("Could not queue message for sending:\n\n"
9784 "Charset conversion failed."));
9785 } else if (val == -5) {
9786 alertpanel_error(_("Could not queue message for sending:\n\n"
9787 "Couldn't get recipient encryption key."));
9788 } else if (val == -6) {
9791 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9794 #define DRAFTED_AT_EXIT "drafted_at_exit"
9795 static void compose_register_draft(MsgInfo *info)
9797 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9798 DRAFTED_AT_EXIT, NULL);
9799 FILE *fp = g_fopen(filepath, "ab");
9802 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9810 gboolean compose_draft (gpointer data, guint action)
9812 Compose *compose = (Compose *)data;
9817 MsgFlags flag = {0, 0};
9818 static gboolean lock = FALSE;
9819 MsgInfo *newmsginfo;
9821 gboolean target_locked = FALSE;
9822 gboolean err = FALSE;
9824 if (lock) return FALSE;
9826 if (compose->sending)
9829 draft = account_get_special_folder(compose->account, F_DRAFT);
9830 cm_return_val_if_fail(draft != NULL, FALSE);
9832 if (!g_mutex_trylock(compose->mutex)) {
9833 /* we don't want to lock the mutex once it's available,
9834 * because as the only other part of compose.c locking
9835 * it is compose_close - which means once unlocked,
9836 * the compose struct will be freed */
9837 debug_print("couldn't lock mutex, probably sending\n");
9843 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9844 G_DIR_SEPARATOR, compose);
9845 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9846 FILE_OP_ERROR(tmp, "fopen");
9850 /* chmod for security */
9851 if (change_file_mode_rw(fp, tmp) < 0) {
9852 FILE_OP_ERROR(tmp, "chmod");
9853 g_warning("can't change file mode\n");
9856 /* Save draft infos */
9857 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9858 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9860 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9861 gchar *savefolderid;
9863 savefolderid = compose_get_save_to(compose);
9864 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9865 g_free(savefolderid);
9867 if (compose->return_receipt) {
9868 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9870 if (compose->privacy_system) {
9871 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9872 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9873 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9876 /* Message-ID of message replying to */
9877 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9878 gchar *folderid = NULL;
9880 if (compose->replyinfo->folder)
9881 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9882 if (folderid == NULL)
9883 folderid = g_strdup("NULL");
9885 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9888 /* Message-ID of message forwarding to */
9889 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9890 gchar *folderid = NULL;
9892 if (compose->fwdinfo->folder)
9893 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9894 if (folderid == NULL)
9895 folderid = g_strdup("NULL");
9897 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9901 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9902 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9904 sheaders = compose_get_manual_headers_info(compose);
9905 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9908 /* end of headers */
9909 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9916 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9920 if (fclose(fp) == EOF) {
9924 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9925 if (compose->targetinfo) {
9926 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9928 flag.perm_flags |= MSG_LOCKED;
9930 flag.tmp_flags = MSG_DRAFT;
9932 folder_item_scan(draft);
9933 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9934 MsgInfo *tmpinfo = NULL;
9935 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9936 if (compose->msgid) {
9937 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9940 msgnum = tmpinfo->msgnum;
9941 procmsg_msginfo_free(tmpinfo);
9942 debug_print("got draft msgnum %d from scanning\n", msgnum);
9944 debug_print("didn't get draft msgnum after scanning\n");
9947 debug_print("got draft msgnum %d from adding\n", msgnum);
9953 if (action != COMPOSE_AUTO_SAVE) {
9954 if (action != COMPOSE_DRAFT_FOR_EXIT)
9955 alertpanel_error(_("Could not save draft."));
9958 gtkut_window_popup(compose->window);
9959 val = alertpanel_full(_("Could not save draft"),
9960 _("Could not save draft.\n"
9961 "Do you want to cancel exit or discard this email?"),
9962 _("_Cancel exit"), _("_Discard email"), NULL,
9963 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9964 if (val == G_ALERTALTERNATE) {
9966 g_mutex_unlock(compose->mutex); /* must be done before closing */
9967 compose_close(compose);
9971 g_mutex_unlock(compose->mutex); /* must be done before closing */
9980 if (compose->mode == COMPOSE_REEDIT) {
9981 compose_remove_reedit_target(compose, TRUE);
9984 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9987 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9989 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9991 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9992 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9993 procmsg_msginfo_set_flags(newmsginfo, 0,
9994 MSG_HAS_ATTACHMENT);
9996 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9997 compose_register_draft(newmsginfo);
9999 procmsg_msginfo_free(newmsginfo);
10002 folder_item_scan(draft);
10004 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10006 g_mutex_unlock(compose->mutex); /* must be done before closing */
10007 compose_close(compose);
10013 path = folder_item_fetch_msg(draft, msgnum);
10014 if (path == NULL) {
10015 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10018 if (g_stat(path, &s) < 0) {
10019 FILE_OP_ERROR(path, "stat");
10025 procmsg_msginfo_free(compose->targetinfo);
10026 compose->targetinfo = procmsg_msginfo_new();
10027 compose->targetinfo->msgnum = msgnum;
10028 compose->targetinfo->size = (goffset)s.st_size;
10029 compose->targetinfo->mtime = s.st_mtime;
10030 compose->targetinfo->folder = draft;
10032 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10033 compose->mode = COMPOSE_REEDIT;
10035 if (action == COMPOSE_AUTO_SAVE) {
10036 compose->autosaved_draft = compose->targetinfo;
10038 compose->modified = FALSE;
10039 compose_set_title(compose);
10043 g_mutex_unlock(compose->mutex);
10047 void compose_clear_exit_drafts(void)
10049 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10050 DRAFTED_AT_EXIT, NULL);
10051 if (is_file_exist(filepath))
10052 claws_unlink(filepath);
10057 void compose_reopen_exit_drafts(void)
10059 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10060 DRAFTED_AT_EXIT, NULL);
10061 FILE *fp = g_fopen(filepath, "rb");
10065 while (fgets(buf, sizeof(buf), fp)) {
10066 gchar **parts = g_strsplit(buf, "\t", 2);
10067 const gchar *folder = parts[0];
10068 int msgnum = parts[1] ? atoi(parts[1]):-1;
10070 if (folder && *folder && msgnum > -1) {
10071 FolderItem *item = folder_find_item_from_identifier(folder);
10072 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10074 compose_reedit(info, FALSE);
10081 compose_clear_exit_drafts();
10084 static void compose_save_cb(GtkAction *action, gpointer data)
10086 Compose *compose = (Compose *)data;
10087 compose_draft(compose, COMPOSE_KEEP_EDITING);
10088 compose->rmode = COMPOSE_REEDIT;
10091 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10093 if (compose && file_list) {
10096 for ( tmp = file_list; tmp; tmp = tmp->next) {
10097 gchar *file = (gchar *) tmp->data;
10098 gchar *utf8_filename = conv_filename_to_utf8(file);
10099 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10100 compose_changed_cb(NULL, compose);
10105 g_free(utf8_filename);
10110 static void compose_attach_cb(GtkAction *action, gpointer data)
10112 Compose *compose = (Compose *)data;
10115 if (compose->redirect_filename != NULL)
10118 /* Set focus_window properly, in case we were called via popup menu,
10119 * which unsets it (via focus_out_event callback on compose window). */
10120 manage_window_focus_in(compose->window, NULL, NULL);
10122 file_list = filesel_select_multiple_files_open(_("Select file"));
10125 compose_attach_from_list(compose, file_list, TRUE);
10126 g_list_free(file_list);
10130 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10132 Compose *compose = (Compose *)data;
10134 gint files_inserted = 0;
10136 file_list = filesel_select_multiple_files_open(_("Select file"));
10141 for ( tmp = file_list; tmp; tmp = tmp->next) {
10142 gchar *file = (gchar *) tmp->data;
10143 gchar *filedup = g_strdup(file);
10144 gchar *shortfile = g_path_get_basename(filedup);
10145 ComposeInsertResult res;
10146 /* insert the file if the file is short or if the user confirmed that
10147 he/she wants to insert the large file */
10148 res = compose_insert_file(compose, file);
10149 if (res == COMPOSE_INSERT_READ_ERROR) {
10150 alertpanel_error(_("File '%s' could not be read."), shortfile);
10151 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10152 alertpanel_error(_("File '%s' contained invalid characters\n"
10153 "for the current encoding, insertion may be incorrect."),
10155 } else if (res == COMPOSE_INSERT_SUCCESS)
10162 g_list_free(file_list);
10166 if (files_inserted > 0 && compose->gtkaspell &&
10167 compose->gtkaspell->check_while_typing)
10168 gtkaspell_highlight_all(compose->gtkaspell);
10172 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10174 Compose *compose = (Compose *)data;
10176 compose_insert_sig(compose, FALSE);
10179 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10181 Compose *compose = (Compose *)data;
10183 compose_insert_sig(compose, TRUE);
10186 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10190 Compose *compose = (Compose *)data;
10192 gtkut_widget_get_uposition(widget, &x, &y);
10193 if (!compose->batch) {
10194 prefs_common.compose_x = x;
10195 prefs_common.compose_y = y;
10197 if (compose->sending || compose->updating)
10199 compose_close_cb(NULL, compose);
10203 void compose_close_toolbar(Compose *compose)
10205 compose_close_cb(NULL, compose);
10208 static gboolean compose_can_autosave(Compose *compose)
10210 if (compose->privacy_system && compose->use_encryption)
10211 return prefs_common.autosave && prefs_common.autosave_encrypted;
10213 return prefs_common.autosave;
10216 static void compose_close_cb(GtkAction *action, gpointer data)
10218 Compose *compose = (Compose *)data;
10222 if (compose->exteditor_tag != -1) {
10223 if (!compose_ext_editor_kill(compose))
10228 if (compose->modified) {
10229 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10230 if (!g_mutex_trylock(compose->mutex)) {
10231 /* we don't want to lock the mutex once it's available,
10232 * because as the only other part of compose.c locking
10233 * it is compose_close - which means once unlocked,
10234 * the compose struct will be freed */
10235 debug_print("couldn't lock mutex, probably sending\n");
10239 val = alertpanel(_("Discard message"),
10240 _("This message has been modified. Discard it?"),
10241 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10243 val = alertpanel(_("Save changes"),
10244 _("This message has been modified. Save the latest changes?"),
10245 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10247 g_mutex_unlock(compose->mutex);
10249 case G_ALERTDEFAULT:
10250 if (compose_can_autosave(compose) && !reedit)
10251 compose_remove_draft(compose);
10253 case G_ALERTALTERNATE:
10254 compose_draft(data, COMPOSE_QUIT_EDITING);
10261 compose_close(compose);
10264 static void compose_print_cb(GtkAction *action, gpointer data)
10266 Compose *compose = (Compose *) data;
10268 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10269 if (compose->targetinfo)
10270 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10273 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10275 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10276 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10277 Compose *compose = (Compose *) data;
10280 compose->out_encoding = (CharSet)value;
10283 static void compose_address_cb(GtkAction *action, gpointer data)
10285 Compose *compose = (Compose *)data;
10287 #ifndef USE_NEW_ADDRBOOK
10288 addressbook_open(compose);
10290 GError* error = NULL;
10291 addressbook_connect_signals(compose);
10292 addressbook_dbus_open(TRUE, &error);
10294 g_warning("%s", error->message);
10295 g_error_free(error);
10300 static void about_show_cb(GtkAction *action, gpointer data)
10305 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10307 Compose *compose = (Compose *)data;
10312 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10313 cm_return_if_fail(tmpl != NULL);
10315 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10317 val = alertpanel(_("Apply template"), msg,
10318 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10321 if (val == G_ALERTDEFAULT)
10322 compose_template_apply(compose, tmpl, TRUE);
10323 else if (val == G_ALERTALTERNATE)
10324 compose_template_apply(compose, tmpl, FALSE);
10327 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10329 Compose *compose = (Compose *)data;
10331 compose_exec_ext_editor(compose);
10334 static void compose_undo_cb(GtkAction *action, gpointer data)
10336 Compose *compose = (Compose *)data;
10337 gboolean prev_autowrap = compose->autowrap;
10339 compose->autowrap = FALSE;
10340 undo_undo(compose->undostruct);
10341 compose->autowrap = prev_autowrap;
10344 static void compose_redo_cb(GtkAction *action, gpointer data)
10346 Compose *compose = (Compose *)data;
10347 gboolean prev_autowrap = compose->autowrap;
10349 compose->autowrap = FALSE;
10350 undo_redo(compose->undostruct);
10351 compose->autowrap = prev_autowrap;
10354 static void entry_cut_clipboard(GtkWidget *entry)
10356 if (GTK_IS_EDITABLE(entry))
10357 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10358 else if (GTK_IS_TEXT_VIEW(entry))
10359 gtk_text_buffer_cut_clipboard(
10360 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10361 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10365 static void entry_copy_clipboard(GtkWidget *entry)
10367 if (GTK_IS_EDITABLE(entry))
10368 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10369 else if (GTK_IS_TEXT_VIEW(entry))
10370 gtk_text_buffer_copy_clipboard(
10371 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10372 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10375 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10376 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10378 if (GTK_IS_TEXT_VIEW(entry)) {
10379 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10380 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10381 GtkTextIter start_iter, end_iter;
10383 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10385 if (contents == NULL)
10388 /* we shouldn't delete the selection when middle-click-pasting, or we
10389 * can't mid-click-paste our own selection */
10390 if (clip != GDK_SELECTION_PRIMARY) {
10391 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10392 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10395 if (insert_place == NULL) {
10396 /* if insert_place isn't specified, insert at the cursor.
10397 * used for Ctrl-V pasting */
10398 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10399 start = gtk_text_iter_get_offset(&start_iter);
10400 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10402 /* if insert_place is specified, paste here.
10403 * used for mid-click-pasting */
10404 start = gtk_text_iter_get_offset(insert_place);
10405 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10406 if (prefs_common.primary_paste_unselects)
10407 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10411 /* paste unwrapped: mark the paste so it's not wrapped later */
10412 end = start + strlen(contents);
10413 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10414 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10415 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10416 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10417 /* rewrap paragraph now (after a mid-click-paste) */
10418 mark_start = gtk_text_buffer_get_insert(buffer);
10419 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10420 gtk_text_iter_backward_char(&start_iter);
10421 compose_beautify_paragraph(compose, &start_iter, TRUE);
10423 } else if (GTK_IS_EDITABLE(entry))
10424 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10426 compose->modified = TRUE;
10429 static void entry_allsel(GtkWidget *entry)
10431 if (GTK_IS_EDITABLE(entry))
10432 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10433 else if (GTK_IS_TEXT_VIEW(entry)) {
10434 GtkTextIter startiter, enditer;
10435 GtkTextBuffer *textbuf;
10437 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10438 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10439 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10441 gtk_text_buffer_move_mark_by_name(textbuf,
10442 "selection_bound", &startiter);
10443 gtk_text_buffer_move_mark_by_name(textbuf,
10444 "insert", &enditer);
10448 static void compose_cut_cb(GtkAction *action, gpointer data)
10450 Compose *compose = (Compose *)data;
10451 if (compose->focused_editable
10452 #ifndef GENERIC_UMPC
10453 && gtk_widget_has_focus(compose->focused_editable)
10456 entry_cut_clipboard(compose->focused_editable);
10459 static void compose_copy_cb(GtkAction *action, gpointer data)
10461 Compose *compose = (Compose *)data;
10462 if (compose->focused_editable
10463 #ifndef GENERIC_UMPC
10464 && gtk_widget_has_focus(compose->focused_editable)
10467 entry_copy_clipboard(compose->focused_editable);
10470 static void compose_paste_cb(GtkAction *action, gpointer data)
10472 Compose *compose = (Compose *)data;
10473 gint prev_autowrap;
10474 GtkTextBuffer *buffer;
10476 if (compose->focused_editable &&
10477 #ifndef GENERIC_UMPC
10478 gtk_widget_has_focus(compose->focused_editable)
10481 entry_paste_clipboard(compose, compose->focused_editable,
10482 prefs_common.linewrap_pastes,
10483 GDK_SELECTION_CLIPBOARD, NULL);
10488 #ifndef GENERIC_UMPC
10489 gtk_widget_has_focus(compose->text) &&
10491 compose->gtkaspell &&
10492 compose->gtkaspell->check_while_typing)
10493 gtkaspell_highlight_all(compose->gtkaspell);
10497 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10499 Compose *compose = (Compose *)data;
10500 gint wrap_quote = prefs_common.linewrap_quote;
10501 if (compose->focused_editable
10502 #ifndef GENERIC_UMPC
10503 && gtk_widget_has_focus(compose->focused_editable)
10506 /* let text_insert() (called directly or at a later time
10507 * after the gtk_editable_paste_clipboard) know that
10508 * text is to be inserted as a quotation. implemented
10509 * by using a simple refcount... */
10510 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10511 G_OBJECT(compose->focused_editable),
10512 "paste_as_quotation"));
10513 g_object_set_data(G_OBJECT(compose->focused_editable),
10514 "paste_as_quotation",
10515 GINT_TO_POINTER(paste_as_quotation + 1));
10516 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10517 entry_paste_clipboard(compose, compose->focused_editable,
10518 prefs_common.linewrap_pastes,
10519 GDK_SELECTION_CLIPBOARD, NULL);
10520 prefs_common.linewrap_quote = wrap_quote;
10524 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10526 Compose *compose = (Compose *)data;
10527 gint prev_autowrap;
10528 GtkTextBuffer *buffer;
10530 if (compose->focused_editable
10531 #ifndef GENERIC_UMPC
10532 && gtk_widget_has_focus(compose->focused_editable)
10535 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10536 GDK_SELECTION_CLIPBOARD, NULL);
10541 #ifndef GENERIC_UMPC
10542 gtk_widget_has_focus(compose->text) &&
10544 compose->gtkaspell &&
10545 compose->gtkaspell->check_while_typing)
10546 gtkaspell_highlight_all(compose->gtkaspell);
10550 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10552 Compose *compose = (Compose *)data;
10553 gint prev_autowrap;
10554 GtkTextBuffer *buffer;
10556 if (compose->focused_editable
10557 #ifndef GENERIC_UMPC
10558 && gtk_widget_has_focus(compose->focused_editable)
10561 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10562 GDK_SELECTION_CLIPBOARD, NULL);
10567 #ifndef GENERIC_UMPC
10568 gtk_widget_has_focus(compose->text) &&
10570 compose->gtkaspell &&
10571 compose->gtkaspell->check_while_typing)
10572 gtkaspell_highlight_all(compose->gtkaspell);
10576 static void compose_allsel_cb(GtkAction *action, gpointer data)
10578 Compose *compose = (Compose *)data;
10579 if (compose->focused_editable
10580 #ifndef GENERIC_UMPC
10581 && gtk_widget_has_focus(compose->focused_editable)
10584 entry_allsel(compose->focused_editable);
10587 static void textview_move_beginning_of_line (GtkTextView *text)
10589 GtkTextBuffer *buffer;
10593 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10595 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10596 mark = gtk_text_buffer_get_insert(buffer);
10597 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10598 gtk_text_iter_set_line_offset(&ins, 0);
10599 gtk_text_buffer_place_cursor(buffer, &ins);
10602 static void textview_move_forward_character (GtkTextView *text)
10604 GtkTextBuffer *buffer;
10608 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10610 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10611 mark = gtk_text_buffer_get_insert(buffer);
10612 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10613 if (gtk_text_iter_forward_cursor_position(&ins))
10614 gtk_text_buffer_place_cursor(buffer, &ins);
10617 static void textview_move_backward_character (GtkTextView *text)
10619 GtkTextBuffer *buffer;
10623 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10625 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10626 mark = gtk_text_buffer_get_insert(buffer);
10627 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10628 if (gtk_text_iter_backward_cursor_position(&ins))
10629 gtk_text_buffer_place_cursor(buffer, &ins);
10632 static void textview_move_forward_word (GtkTextView *text)
10634 GtkTextBuffer *buffer;
10639 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10641 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10642 mark = gtk_text_buffer_get_insert(buffer);
10643 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10644 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10645 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10646 gtk_text_iter_backward_word_start(&ins);
10647 gtk_text_buffer_place_cursor(buffer, &ins);
10651 static void textview_move_backward_word (GtkTextView *text)
10653 GtkTextBuffer *buffer;
10657 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10659 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10660 mark = gtk_text_buffer_get_insert(buffer);
10661 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10662 if (gtk_text_iter_backward_word_starts(&ins, 1))
10663 gtk_text_buffer_place_cursor(buffer, &ins);
10666 static void textview_move_end_of_line (GtkTextView *text)
10668 GtkTextBuffer *buffer;
10672 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10674 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10675 mark = gtk_text_buffer_get_insert(buffer);
10676 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10677 if (gtk_text_iter_forward_to_line_end(&ins))
10678 gtk_text_buffer_place_cursor(buffer, &ins);
10681 static void textview_move_next_line (GtkTextView *text)
10683 GtkTextBuffer *buffer;
10688 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10690 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10691 mark = gtk_text_buffer_get_insert(buffer);
10692 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10693 offset = gtk_text_iter_get_line_offset(&ins);
10694 if (gtk_text_iter_forward_line(&ins)) {
10695 gtk_text_iter_set_line_offset(&ins, offset);
10696 gtk_text_buffer_place_cursor(buffer, &ins);
10700 static void textview_move_previous_line (GtkTextView *text)
10702 GtkTextBuffer *buffer;
10707 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10709 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10710 mark = gtk_text_buffer_get_insert(buffer);
10711 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10712 offset = gtk_text_iter_get_line_offset(&ins);
10713 if (gtk_text_iter_backward_line(&ins)) {
10714 gtk_text_iter_set_line_offset(&ins, offset);
10715 gtk_text_buffer_place_cursor(buffer, &ins);
10719 static void textview_delete_forward_character (GtkTextView *text)
10721 GtkTextBuffer *buffer;
10723 GtkTextIter ins, end_iter;
10725 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10727 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10728 mark = gtk_text_buffer_get_insert(buffer);
10729 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10731 if (gtk_text_iter_forward_char(&end_iter)) {
10732 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10736 static void textview_delete_backward_character (GtkTextView *text)
10738 GtkTextBuffer *buffer;
10740 GtkTextIter ins, end_iter;
10742 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10744 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10745 mark = gtk_text_buffer_get_insert(buffer);
10746 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10748 if (gtk_text_iter_backward_char(&end_iter)) {
10749 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10753 static void textview_delete_forward_word (GtkTextView *text)
10755 GtkTextBuffer *buffer;
10757 GtkTextIter ins, end_iter;
10759 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10761 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10762 mark = gtk_text_buffer_get_insert(buffer);
10763 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10765 if (gtk_text_iter_forward_word_end(&end_iter)) {
10766 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10770 static void textview_delete_backward_word (GtkTextView *text)
10772 GtkTextBuffer *buffer;
10774 GtkTextIter ins, end_iter;
10776 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10778 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10779 mark = gtk_text_buffer_get_insert(buffer);
10780 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10782 if (gtk_text_iter_backward_word_start(&end_iter)) {
10783 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10787 static void textview_delete_line (GtkTextView *text)
10789 GtkTextBuffer *buffer;
10791 GtkTextIter ins, start_iter, end_iter;
10793 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10795 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10796 mark = gtk_text_buffer_get_insert(buffer);
10797 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10800 gtk_text_iter_set_line_offset(&start_iter, 0);
10803 if (gtk_text_iter_ends_line(&end_iter)){
10804 if (!gtk_text_iter_forward_char(&end_iter))
10805 gtk_text_iter_backward_char(&start_iter);
10808 gtk_text_iter_forward_to_line_end(&end_iter);
10809 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10812 static void textview_delete_to_line_end (GtkTextView *text)
10814 GtkTextBuffer *buffer;
10816 GtkTextIter ins, end_iter;
10818 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10820 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10821 mark = gtk_text_buffer_get_insert(buffer);
10822 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10824 if (gtk_text_iter_ends_line(&end_iter))
10825 gtk_text_iter_forward_char(&end_iter);
10827 gtk_text_iter_forward_to_line_end(&end_iter);
10828 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10831 #define DO_ACTION(name, act) { \
10832 if(!strcmp(name, a_name)) { \
10836 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10838 const gchar *a_name = gtk_action_get_name(action);
10839 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10840 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10841 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10842 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10843 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10844 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10845 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10846 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10847 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10848 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10849 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10850 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10851 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10852 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10856 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10858 Compose *compose = (Compose *)data;
10859 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10860 ComposeCallAdvancedAction action = -1;
10862 action = compose_call_advanced_action_from_path(gaction);
10865 void (*do_action) (GtkTextView *text);
10866 } action_table[] = {
10867 {textview_move_beginning_of_line},
10868 {textview_move_forward_character},
10869 {textview_move_backward_character},
10870 {textview_move_forward_word},
10871 {textview_move_backward_word},
10872 {textview_move_end_of_line},
10873 {textview_move_next_line},
10874 {textview_move_previous_line},
10875 {textview_delete_forward_character},
10876 {textview_delete_backward_character},
10877 {textview_delete_forward_word},
10878 {textview_delete_backward_word},
10879 {textview_delete_line},
10880 {textview_delete_to_line_end}
10883 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10885 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10886 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10887 if (action_table[action].do_action)
10888 action_table[action].do_action(text);
10890 g_warning("Not implemented yet.");
10894 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10896 GtkAllocation allocation;
10900 if (GTK_IS_EDITABLE(widget)) {
10901 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10902 gtk_editable_set_position(GTK_EDITABLE(widget),
10905 if ((parent = gtk_widget_get_parent(widget))
10906 && (parent = gtk_widget_get_parent(parent))
10907 && (parent = gtk_widget_get_parent(parent))) {
10908 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10909 gtk_widget_get_allocation(widget, &allocation);
10910 gint y = allocation.y;
10911 gint height = allocation.height;
10912 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10913 (GTK_SCROLLED_WINDOW(parent));
10915 gfloat value = gtk_adjustment_get_value(shown);
10916 gfloat upper = gtk_adjustment_get_upper(shown);
10917 gfloat page_size = gtk_adjustment_get_page_size(shown);
10918 if (y < (int)value) {
10919 gtk_adjustment_set_value(shown, y - 1);
10921 if ((y + height) > ((int)value + (int)page_size)) {
10922 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10923 gtk_adjustment_set_value(shown,
10924 y + height - (int)page_size - 1);
10926 gtk_adjustment_set_value(shown,
10927 (int)upper - (int)page_size - 1);
10934 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10935 compose->focused_editable = widget;
10937 #ifdef GENERIC_UMPC
10938 if (GTK_IS_TEXT_VIEW(widget)
10939 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10940 g_object_ref(compose->notebook);
10941 g_object_ref(compose->edit_vbox);
10942 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10943 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10944 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10945 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10946 g_object_unref(compose->notebook);
10947 g_object_unref(compose->edit_vbox);
10948 g_signal_handlers_block_by_func(G_OBJECT(widget),
10949 G_CALLBACK(compose_grab_focus_cb),
10951 gtk_widget_grab_focus(widget);
10952 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10953 G_CALLBACK(compose_grab_focus_cb),
10955 } else if (!GTK_IS_TEXT_VIEW(widget)
10956 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10957 g_object_ref(compose->notebook);
10958 g_object_ref(compose->edit_vbox);
10959 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10960 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10961 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10962 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10963 g_object_unref(compose->notebook);
10964 g_object_unref(compose->edit_vbox);
10965 g_signal_handlers_block_by_func(G_OBJECT(widget),
10966 G_CALLBACK(compose_grab_focus_cb),
10968 gtk_widget_grab_focus(widget);
10969 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10970 G_CALLBACK(compose_grab_focus_cb),
10976 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10978 compose->modified = TRUE;
10979 // compose_beautify_paragraph(compose, NULL, TRUE);
10980 #ifndef GENERIC_UMPC
10981 compose_set_title(compose);
10985 static void compose_wrap_cb(GtkAction *action, gpointer data)
10987 Compose *compose = (Compose *)data;
10988 compose_beautify_paragraph(compose, NULL, TRUE);
10991 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10993 Compose *compose = (Compose *)data;
10994 compose_wrap_all_full(compose, TRUE);
10997 static void compose_find_cb(GtkAction *action, gpointer data)
10999 Compose *compose = (Compose *)data;
11001 message_search_compose(compose);
11004 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11007 Compose *compose = (Compose *)data;
11008 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11009 if (compose->autowrap)
11010 compose_wrap_all_full(compose, TRUE);
11011 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11014 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11017 Compose *compose = (Compose *)data;
11018 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11021 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11023 Compose *compose = (Compose *)data;
11025 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11028 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11030 Compose *compose = (Compose *)data;
11032 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11035 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11037 g_free(compose->privacy_system);
11039 compose->privacy_system = g_strdup(account->default_privacy_system);
11040 compose_update_privacy_system_menu_item(compose, warn);
11043 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11045 Compose *compose = (Compose *)data;
11047 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11048 gtk_widget_show(compose->ruler_hbox);
11049 prefs_common.show_ruler = TRUE;
11051 gtk_widget_hide(compose->ruler_hbox);
11052 gtk_widget_queue_resize(compose->edit_vbox);
11053 prefs_common.show_ruler = FALSE;
11057 static void compose_attach_drag_received_cb (GtkWidget *widget,
11058 GdkDragContext *context,
11061 GtkSelectionData *data,
11064 gpointer user_data)
11066 Compose *compose = (Compose *)user_data;
11070 type = gtk_selection_data_get_data_type(data);
11071 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11073 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11075 ) && gtk_drag_get_source_widget(context) !=
11076 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11077 list = uri_list_extract_filenames(
11078 (const gchar *)gtk_selection_data_get_data(data));
11079 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11080 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11081 compose_attach_append
11082 (compose, (const gchar *)tmp->data,
11083 utf8_filename, NULL, NULL);
11084 g_free(utf8_filename);
11086 if (list) compose_changed_cb(NULL, compose);
11087 list_free_strings(list);
11089 } else if (gtk_drag_get_source_widget(context)
11090 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11091 /* comes from our summaryview */
11092 SummaryView * summaryview = NULL;
11093 GSList * list = NULL, *cur = NULL;
11095 if (mainwindow_get_mainwindow())
11096 summaryview = mainwindow_get_mainwindow()->summaryview;
11099 list = summary_get_selected_msg_list(summaryview);
11101 for (cur = list; cur; cur = cur->next) {
11102 MsgInfo *msginfo = (MsgInfo *)cur->data;
11103 gchar *file = NULL;
11105 file = procmsg_get_message_file_full(msginfo,
11108 compose_attach_append(compose, (const gchar *)file,
11109 (const gchar *)file, "message/rfc822", NULL);
11113 g_slist_free(list);
11117 static gboolean compose_drag_drop(GtkWidget *widget,
11118 GdkDragContext *drag_context,
11120 guint time, gpointer user_data)
11122 /* not handling this signal makes compose_insert_drag_received_cb
11127 static gboolean completion_set_focus_to_subject
11128 (GtkWidget *widget,
11129 GdkEventKey *event,
11132 cm_return_val_if_fail(compose != NULL, FALSE);
11134 /* make backtab move to subject field */
11135 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11136 gtk_widget_grab_focus(compose->subject_entry);
11142 static void compose_insert_drag_received_cb (GtkWidget *widget,
11143 GdkDragContext *drag_context,
11146 GtkSelectionData *data,
11149 gpointer user_data)
11151 Compose *compose = (Compose *)user_data;
11155 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11157 type = gtk_selection_data_get_data_type(data);
11159 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11161 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11163 AlertValue val = G_ALERTDEFAULT;
11164 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11166 list = uri_list_extract_filenames(ddata);
11167 if (list == NULL && strstr(ddata, "://")) {
11168 /* Assume a list of no files, and data has ://, is a remote link */
11169 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11170 gchar *tmpfile = get_tmp_file();
11171 str_write_to_file(tmpdata, tmpfile);
11173 compose_insert_file(compose, tmpfile);
11174 claws_unlink(tmpfile);
11176 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11177 compose_beautify_paragraph(compose, NULL, TRUE);
11180 switch (prefs_common.compose_dnd_mode) {
11181 case COMPOSE_DND_ASK:
11182 val = alertpanel_full(_("Insert or attach?"),
11183 _("Do you want to insert the contents of the file(s) "
11184 "into the message body, or attach it to the email?"),
11185 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11186 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11188 case COMPOSE_DND_INSERT:
11189 val = G_ALERTALTERNATE;
11191 case COMPOSE_DND_ATTACH:
11192 val = G_ALERTOTHER;
11195 /* unexpected case */
11196 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11199 if (val & G_ALERTDISABLE) {
11200 val &= ~G_ALERTDISABLE;
11201 /* remember what action to perform by default, only if we don't click Cancel */
11202 if (val == G_ALERTALTERNATE)
11203 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11204 else if (val == G_ALERTOTHER)
11205 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11208 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11209 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11210 list_free_strings(list);
11213 } else if (val == G_ALERTOTHER) {
11214 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11215 list_free_strings(list);
11220 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11221 compose_insert_file(compose, (const gchar *)tmp->data);
11223 list_free_strings(list);
11225 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11230 static void compose_header_drag_received_cb (GtkWidget *widget,
11231 GdkDragContext *drag_context,
11234 GtkSelectionData *data,
11237 gpointer user_data)
11239 GtkEditable *entry = (GtkEditable *)user_data;
11240 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11242 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11245 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11246 gchar *decoded=g_new(gchar, strlen(email));
11249 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11250 gtk_editable_delete_text(entry, 0, -1);
11251 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11252 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11256 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11259 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11261 Compose *compose = (Compose *)data;
11263 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11264 compose->return_receipt = TRUE;
11266 compose->return_receipt = FALSE;
11269 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11271 Compose *compose = (Compose *)data;
11273 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11274 compose->remove_references = TRUE;
11276 compose->remove_references = FALSE;
11279 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11280 ComposeHeaderEntry *headerentry)
11282 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11286 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11287 GdkEventKey *event,
11288 ComposeHeaderEntry *headerentry)
11290 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11291 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11292 !(event->state & GDK_MODIFIER_MASK) &&
11293 (event->keyval == GDK_KEY_BackSpace) &&
11294 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11295 gtk_container_remove
11296 (GTK_CONTAINER(headerentry->compose->header_table),
11297 headerentry->combo);
11298 gtk_container_remove
11299 (GTK_CONTAINER(headerentry->compose->header_table),
11300 headerentry->entry);
11301 headerentry->compose->header_list =
11302 g_slist_remove(headerentry->compose->header_list,
11304 g_free(headerentry);
11305 } else if (event->keyval == GDK_KEY_Tab) {
11306 if (headerentry->compose->header_last == headerentry) {
11307 /* Override default next focus, and give it to subject_entry
11308 * instead of notebook tabs
11310 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11311 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11318 static gboolean scroll_postpone(gpointer data)
11320 Compose *compose = (Compose *)data;
11322 if (compose->batch)
11325 GTK_EVENTS_FLUSH();
11326 compose_show_first_last_header(compose, FALSE);
11330 static void compose_headerentry_changed_cb(GtkWidget *entry,
11331 ComposeHeaderEntry *headerentry)
11333 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11334 compose_create_header_entry(headerentry->compose);
11335 g_signal_handlers_disconnect_matched
11336 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11337 0, 0, NULL, NULL, headerentry);
11339 if (!headerentry->compose->batch)
11340 g_timeout_add(0, scroll_postpone, headerentry->compose);
11344 static gboolean compose_defer_auto_save_draft(Compose *compose)
11346 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11347 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11351 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11353 GtkAdjustment *vadj;
11355 cm_return_if_fail(compose);
11360 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11361 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11362 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11363 gtk_widget_get_parent(compose->header_table)));
11364 gtk_adjustment_set_value(vadj, (show_first ?
11365 gtk_adjustment_get_lower(vadj) :
11366 (gtk_adjustment_get_upper(vadj) -
11367 gtk_adjustment_get_page_size(vadj))));
11368 gtk_adjustment_changed(vadj);
11371 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11372 const gchar *text, gint len, Compose *compose)
11374 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11375 (G_OBJECT(compose->text), "paste_as_quotation"));
11378 cm_return_if_fail(text != NULL);
11380 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11381 G_CALLBACK(text_inserted),
11383 if (paste_as_quotation) {
11385 const gchar *qmark;
11387 GtkTextIter start_iter;
11390 len = strlen(text);
11392 new_text = g_strndup(text, len);
11394 qmark = compose_quote_char_from_context(compose);
11396 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11397 gtk_text_buffer_place_cursor(buffer, iter);
11399 pos = gtk_text_iter_get_offset(iter);
11401 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11402 _("Quote format error at line %d."));
11403 quote_fmt_reset_vartable();
11405 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11406 GINT_TO_POINTER(paste_as_quotation - 1));
11408 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11409 gtk_text_buffer_place_cursor(buffer, iter);
11410 gtk_text_buffer_delete_mark(buffer, mark);
11412 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11413 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11414 compose_beautify_paragraph(compose, &start_iter, FALSE);
11415 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11416 gtk_text_buffer_delete_mark(buffer, mark);
11418 if (strcmp(text, "\n") || compose->automatic_break
11419 || gtk_text_iter_starts_line(iter)) {
11420 GtkTextIter before_ins;
11421 gtk_text_buffer_insert(buffer, iter, text, len);
11422 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11423 before_ins = *iter;
11424 gtk_text_iter_backward_chars(&before_ins, len);
11425 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11428 /* check if the preceding is just whitespace or quote */
11429 GtkTextIter start_line;
11430 gchar *tmp = NULL, *quote = NULL;
11431 gint quote_len = 0, is_normal = 0;
11432 start_line = *iter;
11433 gtk_text_iter_set_line_offset(&start_line, 0);
11434 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11437 if (*tmp == '\0') {
11440 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11448 gtk_text_buffer_insert(buffer, iter, text, len);
11450 gtk_text_buffer_insert_with_tags_by_name(buffer,
11451 iter, text, len, "no_join", NULL);
11456 if (!paste_as_quotation) {
11457 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11458 compose_beautify_paragraph(compose, iter, FALSE);
11459 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11460 gtk_text_buffer_delete_mark(buffer, mark);
11463 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11464 G_CALLBACK(text_inserted),
11466 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11468 if (compose_can_autosave(compose) &&
11469 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11470 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11471 compose->draft_timeout_tag = g_timeout_add
11472 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11476 static void compose_check_all(GtkAction *action, gpointer data)
11478 Compose *compose = (Compose *)data;
11479 if (!compose->gtkaspell)
11482 if (gtk_widget_has_focus(compose->subject_entry))
11483 claws_spell_entry_check_all(
11484 CLAWS_SPELL_ENTRY(compose->subject_entry));
11486 gtkaspell_check_all(compose->gtkaspell);
11489 static void compose_highlight_all(GtkAction *action, gpointer data)
11491 Compose *compose = (Compose *)data;
11492 if (compose->gtkaspell) {
11493 claws_spell_entry_recheck_all(
11494 CLAWS_SPELL_ENTRY(compose->subject_entry));
11495 gtkaspell_highlight_all(compose->gtkaspell);
11499 static void compose_check_backwards(GtkAction *action, gpointer data)
11501 Compose *compose = (Compose *)data;
11502 if (!compose->gtkaspell) {
11503 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11507 if (gtk_widget_has_focus(compose->subject_entry))
11508 claws_spell_entry_check_backwards(
11509 CLAWS_SPELL_ENTRY(compose->subject_entry));
11511 gtkaspell_check_backwards(compose->gtkaspell);
11514 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11516 Compose *compose = (Compose *)data;
11517 if (!compose->gtkaspell) {
11518 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11522 if (gtk_widget_has_focus(compose->subject_entry))
11523 claws_spell_entry_check_forwards_go(
11524 CLAWS_SPELL_ENTRY(compose->subject_entry));
11526 gtkaspell_check_forwards_go(compose->gtkaspell);
11531 *\brief Guess originating forward account from MsgInfo and several
11532 * "common preference" settings. Return NULL if no guess.
11534 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11536 PrefsAccount *account = NULL;
11538 cm_return_val_if_fail(msginfo, NULL);
11539 cm_return_val_if_fail(msginfo->folder, NULL);
11540 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11542 if (msginfo->folder->prefs->enable_default_account)
11543 account = account_find_from_id(msginfo->folder->prefs->default_account);
11546 account = msginfo->folder->folder->account;
11548 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11550 Xstrdup_a(to, msginfo->to, return NULL);
11551 extract_address(to);
11552 account = account_find_from_address(to, FALSE);
11555 if (!account && prefs_common.forward_account_autosel) {
11556 gchar cc[BUFFSIZE];
11557 if (!procheader_get_header_from_msginfo
11558 (msginfo, cc,sizeof cc , "Cc:")) {
11559 gchar *buf = cc + strlen("Cc:");
11560 extract_address(buf);
11561 account = account_find_from_address(buf, FALSE);
11565 if (!account && prefs_common.forward_account_autosel) {
11566 gchar deliveredto[BUFFSIZE];
11567 if (!procheader_get_header_from_msginfo
11568 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11569 gchar *buf = deliveredto + strlen("Delivered-To:");
11570 extract_address(buf);
11571 account = account_find_from_address(buf, FALSE);
11578 gboolean compose_close(Compose *compose)
11582 cm_return_val_if_fail(compose, FALSE);
11584 if (!g_mutex_trylock(compose->mutex)) {
11585 /* we have to wait for the (possibly deferred by auto-save)
11586 * drafting to be done, before destroying the compose under
11588 debug_print("waiting for drafting to finish...\n");
11589 compose_allow_user_actions(compose, FALSE);
11590 if (compose->close_timeout_tag == 0) {
11591 compose->close_timeout_tag =
11592 g_timeout_add (500, (GSourceFunc) compose_close,
11598 if (compose->draft_timeout_tag >= 0) {
11599 g_source_remove(compose->draft_timeout_tag);
11600 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11603 gtkut_widget_get_uposition(compose->window, &x, &y);
11604 if (!compose->batch) {
11605 prefs_common.compose_x = x;
11606 prefs_common.compose_y = y;
11608 g_mutex_unlock(compose->mutex);
11609 compose_destroy(compose);
11614 * Add entry field for each address in list.
11615 * \param compose E-Mail composition object.
11616 * \param listAddress List of (formatted) E-Mail addresses.
11618 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11621 node = listAddress;
11623 addr = ( gchar * ) node->data;
11624 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11625 node = g_list_next( node );
11629 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11630 guint action, gboolean opening_multiple)
11632 gchar *body = NULL;
11633 GSList *new_msglist = NULL;
11634 MsgInfo *tmp_msginfo = NULL;
11635 gboolean originally_enc = FALSE;
11636 gboolean originally_sig = FALSE;
11637 Compose *compose = NULL;
11638 gchar *s_system = NULL;
11640 cm_return_if_fail(msgview != NULL);
11642 cm_return_if_fail(msginfo_list != NULL);
11644 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11645 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11646 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11648 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11649 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11650 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11651 orig_msginfo, mimeinfo);
11652 if (tmp_msginfo != NULL) {
11653 new_msglist = g_slist_append(NULL, tmp_msginfo);
11655 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11656 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11657 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11659 tmp_msginfo->folder = orig_msginfo->folder;
11660 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11661 if (orig_msginfo->tags) {
11662 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11663 tmp_msginfo->folder->tags_dirty = TRUE;
11669 if (!opening_multiple)
11670 body = messageview_get_selection(msgview);
11673 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11674 procmsg_msginfo_free(tmp_msginfo);
11675 g_slist_free(new_msglist);
11677 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11679 if (compose && originally_enc) {
11680 compose_force_encryption(compose, compose->account, FALSE, s_system);
11683 if (compose && originally_sig && compose->account->default_sign_reply) {
11684 compose_force_signing(compose, compose->account, s_system);
11688 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11691 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11694 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11695 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11696 GSList *cur = msginfo_list;
11697 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11698 "messages. Opening the windows "
11699 "could take some time. Do you "
11700 "want to continue?"),
11701 g_slist_length(msginfo_list));
11702 if (g_slist_length(msginfo_list) > 9
11703 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11704 != G_ALERTALTERNATE) {
11709 /* We'll open multiple compose windows */
11710 /* let the WM place the next windows */
11711 compose_force_window_origin = FALSE;
11712 for (; cur; cur = cur->next) {
11714 tmplist.data = cur->data;
11715 tmplist.next = NULL;
11716 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11718 compose_force_window_origin = TRUE;
11720 /* forwarding multiple mails as attachments is done via a
11721 * single compose window */
11722 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11726 void compose_check_for_email_account(Compose *compose)
11728 PrefsAccount *ac = NULL, *curr = NULL;
11734 if (compose->account && compose->account->protocol == A_NNTP) {
11735 ac = account_get_cur_account();
11736 if (ac->protocol == A_NNTP) {
11737 list = account_get_list();
11739 for( ; list != NULL ; list = g_list_next(list)) {
11740 curr = (PrefsAccount *) list->data;
11741 if (curr->protocol != A_NNTP) {
11747 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11752 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11753 const gchar *address)
11755 GSList *msginfo_list = NULL;
11756 gchar *body = messageview_get_selection(msgview);
11759 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11761 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11762 compose_check_for_email_account(compose);
11763 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11764 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11765 compose_reply_set_subject(compose, msginfo);
11768 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11771 void compose_set_position(Compose *compose, gint pos)
11773 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11775 gtkut_text_view_set_position(text, pos);
11778 gboolean compose_search_string(Compose *compose,
11779 const gchar *str, gboolean case_sens)
11781 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11783 return gtkut_text_view_search_string(text, str, case_sens);
11786 gboolean compose_search_string_backward(Compose *compose,
11787 const gchar *str, gboolean case_sens)
11789 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11791 return gtkut_text_view_search_string_backward(text, str, case_sens);
11794 /* allocate a msginfo structure and populate its data from a compose data structure */
11795 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11797 MsgInfo *newmsginfo;
11799 gchar buf[BUFFSIZE];
11801 cm_return_val_if_fail( compose != NULL, NULL );
11803 newmsginfo = procmsg_msginfo_new();
11806 get_rfc822_date(buf, sizeof(buf));
11807 newmsginfo->date = g_strdup(buf);
11810 if (compose->from_name) {
11811 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11812 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11816 if (compose->subject_entry)
11817 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11819 /* to, cc, reply-to, newsgroups */
11820 for (list = compose->header_list; list; list = list->next) {
11821 gchar *header = gtk_editable_get_chars(
11823 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11824 gchar *entry = gtk_editable_get_chars(
11825 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11827 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11828 if ( newmsginfo->to == NULL ) {
11829 newmsginfo->to = g_strdup(entry);
11830 } else if (entry && *entry) {
11831 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11832 g_free(newmsginfo->to);
11833 newmsginfo->to = tmp;
11836 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11837 if ( newmsginfo->cc == NULL ) {
11838 newmsginfo->cc = g_strdup(entry);
11839 } else if (entry && *entry) {
11840 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11841 g_free(newmsginfo->cc);
11842 newmsginfo->cc = tmp;
11845 if ( strcasecmp(header,
11846 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11847 if ( newmsginfo->newsgroups == NULL ) {
11848 newmsginfo->newsgroups = g_strdup(entry);
11849 } else if (entry && *entry) {
11850 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11851 g_free(newmsginfo->newsgroups);
11852 newmsginfo->newsgroups = tmp;
11860 /* other data is unset */
11866 /* update compose's dictionaries from folder dict settings */
11867 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11868 FolderItem *folder_item)
11870 cm_return_if_fail(compose != NULL);
11872 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11873 FolderItemPrefs *prefs = folder_item->prefs;
11875 if (prefs->enable_default_dictionary)
11876 gtkaspell_change_dict(compose->gtkaspell,
11877 prefs->default_dictionary, FALSE);
11878 if (folder_item->prefs->enable_default_alt_dictionary)
11879 gtkaspell_change_alt_dict(compose->gtkaspell,
11880 prefs->default_alt_dictionary);
11881 if (prefs->enable_default_dictionary
11882 || prefs->enable_default_alt_dictionary)
11883 compose_spell_menu_changed(compose);
11888 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11890 Compose *compose = (Compose *)data;
11892 cm_return_if_fail(compose != NULL);
11894 gtk_widget_grab_focus(compose->text);
11897 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
11899 gtk_combo_box_popup(GTK_COMBO_BOX(data));