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_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
757 static GtkTargetEntry compose_mime_types[] =
759 {"text/uri-list", 0, 0},
760 {"UTF8_STRING", 0, 0},
764 static gboolean compose_put_existing_to_front(MsgInfo *info)
766 const GList *compose_list = compose_get_compose_list();
767 const GList *elem = NULL;
770 for (elem = compose_list; elem != NULL && elem->data != NULL;
772 Compose *c = (Compose*)elem->data;
774 if (!c->targetinfo || !c->targetinfo->msgid ||
778 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
779 gtkut_window_popup(c->window);
787 static GdkColor quote_color1 =
788 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
789 static GdkColor quote_color2 =
790 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_color3 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
794 static GdkColor quote_bgcolor1 =
795 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
796 static GdkColor quote_bgcolor2 =
797 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor quote_bgcolor3 =
799 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
801 static GdkColor signature_color = {
808 static GdkColor uri_color = {
815 static void compose_create_tags(GtkTextView *text, Compose *compose)
817 GtkTextBuffer *buffer;
818 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 #if !GTK_CHECK_VERSION(2, 24, 0)
826 buffer = gtk_text_view_get_buffer(text);
828 if (prefs_common.enable_color) {
829 /* grab the quote colors, converting from an int to a GdkColor */
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
836 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
838 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
840 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
842 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
844 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
847 signature_color = quote_color1 = quote_color2 = quote_color3 =
848 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
851 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
852 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
853 "foreground-gdk", "e_color1,
854 "paragraph-background-gdk", "e_bgcolor1,
856 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
857 "foreground-gdk", "e_color2,
858 "paragraph-background-gdk", "e_bgcolor2,
860 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
861 "foreground-gdk", "e_color3,
862 "paragraph-background-gdk", "e_bgcolor3,
865 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
866 "foreground-gdk", "e_color1,
868 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
869 "foreground-gdk", "e_color2,
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-gdk", "e_color3,
876 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
877 "foreground-gdk", &signature_color,
880 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
881 "foreground-gdk", &uri_color,
883 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
884 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
886 #if !GTK_CHECK_VERSION(2, 24, 0)
887 color[0] = quote_color1;
888 color[1] = quote_color2;
889 color[2] = quote_color3;
890 color[3] = quote_bgcolor1;
891 color[4] = quote_bgcolor2;
892 color[5] = quote_bgcolor3;
893 color[6] = signature_color;
894 color[7] = uri_color;
896 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
897 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
899 for (i = 0; i < 8; i++) {
900 if (success[i] == FALSE) {
901 g_warning("Compose: color allocation failed.\n");
902 quote_color1 = quote_color2 = quote_color3 =
903 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
904 signature_color = uri_color = black;
910 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
913 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
916 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
918 return compose_generic_new(account, mailto, item, NULL, NULL);
921 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
923 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
926 #define SCROLL_TO_CURSOR(compose) { \
927 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
928 gtk_text_view_get_buffer( \
929 GTK_TEXT_VIEW(compose->text))); \
930 gtk_text_view_scroll_mark_onscreen( \
931 GTK_TEXT_VIEW(compose->text), \
935 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
938 if (folderidentifier) {
939 #if !GTK_CHECK_VERSION(2, 24, 0)
940 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
942 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
944 prefs_common.compose_save_to_history = add_history(
945 prefs_common.compose_save_to_history, folderidentifier);
946 #if !GTK_CHECK_VERSION(2, 24, 0)
947 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
948 prefs_common.compose_save_to_history);
950 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
951 prefs_common.compose_save_to_history);
955 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
956 if (folderidentifier)
957 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
959 gtk_entry_set_text(GTK_ENTRY(entry), "");
962 static gchar *compose_get_save_to(Compose *compose)
965 gchar *result = NULL;
966 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
967 result = gtk_editable_get_chars(entry, 0, -1);
970 #if !GTK_CHECK_VERSION(2, 24, 0)
971 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
973 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
975 prefs_common.compose_save_to_history = add_history(
976 prefs_common.compose_save_to_history, result);
977 #if !GTK_CHECK_VERSION(2, 24, 0)
978 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
979 prefs_common.compose_save_to_history);
981 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
982 prefs_common.compose_save_to_history);
988 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
989 GList *attach_files, GList *listAddress )
992 GtkTextView *textview;
993 GtkTextBuffer *textbuf;
995 const gchar *subject_format = NULL;
996 const gchar *body_format = NULL;
997 gchar *mailto_from = NULL;
998 PrefsAccount *mailto_account = NULL;
999 MsgInfo* dummyinfo = NULL;
1000 gint cursor_pos = -1;
1001 MailField mfield = NO_FIELD_PRESENT;
1005 /* check if mailto defines a from */
1006 if (mailto && *mailto != '\0') {
1007 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1008 /* mailto defines a from, check if we can get account prefs from it,
1009 if not, the account prefs will be guessed using other ways, but we'll keep
1012 mailto_account = account_find_from_address(mailto_from, TRUE);
1013 if (mailto_account == NULL) {
1015 Xstrdup_a(tmp_from, mailto_from, return NULL);
1016 extract_address(tmp_from);
1017 mailto_account = account_find_from_address(tmp_from, TRUE);
1021 account = mailto_account;
1024 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1025 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1026 account = account_find_from_id(item->prefs->default_account);
1028 /* if no account prefs set, fallback to the current one */
1029 if (!account) account = cur_account;
1030 cm_return_val_if_fail(account != NULL, NULL);
1032 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1034 /* override from name if mailto asked for it */
1036 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1037 g_free(mailto_from);
1039 /* override from name according to folder properties */
1040 if (item && item->prefs &&
1041 item->prefs->compose_with_format &&
1042 item->prefs->compose_override_from_format &&
1043 *item->prefs->compose_override_from_format != '\0') {
1048 dummyinfo = compose_msginfo_new_from_compose(compose);
1050 /* decode \-escape sequences in the internal representation of the quote format */
1051 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1052 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1055 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1056 compose->gtkaspell);
1058 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1060 quote_fmt_scan_string(tmp);
1063 buf = quote_fmt_get_buffer();
1065 alertpanel_error(_("New message From format error."));
1067 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1068 quote_fmt_reset_vartable();
1073 compose->replyinfo = NULL;
1074 compose->fwdinfo = NULL;
1076 textview = GTK_TEXT_VIEW(compose->text);
1077 textbuf = gtk_text_view_get_buffer(textview);
1078 compose_create_tags(textview, compose);
1080 undo_block(compose->undostruct);
1082 compose_set_dictionaries_from_folder_prefs(compose, item);
1085 if (account->auto_sig)
1086 compose_insert_sig(compose, FALSE);
1087 gtk_text_buffer_get_start_iter(textbuf, &iter);
1088 gtk_text_buffer_place_cursor(textbuf, &iter);
1090 if (account->protocol != A_NNTP) {
1091 if (mailto && *mailto != '\0') {
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1095 compose_set_folder_prefs(compose, item, TRUE);
1097 if (item && item->ret_rcpt) {
1098 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1101 if (mailto && *mailto != '\0') {
1102 if (!strchr(mailto, '@'))
1103 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1105 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1106 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1107 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1108 mfield = TO_FIELD_PRESENT;
1111 * CLAWS: just don't allow return receipt request, even if the user
1112 * may want to send an email. simple but foolproof.
1114 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1116 compose_add_field_list( compose, listAddress );
1118 if (item && item->prefs && item->prefs->compose_with_format) {
1119 subject_format = item->prefs->compose_subject_format;
1120 body_format = item->prefs->compose_body_format;
1121 } else if (account->compose_with_format) {
1122 subject_format = account->compose_subject_format;
1123 body_format = account->compose_body_format;
1124 } else if (prefs_common.compose_with_format) {
1125 subject_format = prefs_common.compose_subject_format;
1126 body_format = prefs_common.compose_body_format;
1129 if (subject_format || body_format) {
1132 && *subject_format != '\0' )
1134 gchar *subject = NULL;
1139 dummyinfo = compose_msginfo_new_from_compose(compose);
1141 /* decode \-escape sequences in the internal representation of the quote format */
1142 tmp = g_malloc(strlen(subject_format)+1);
1143 pref_get_unescaped_pref(tmp, subject_format);
1145 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1148 compose->gtkaspell);
1150 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1152 quote_fmt_scan_string(tmp);
1155 buf = quote_fmt_get_buffer();
1157 alertpanel_error(_("New message subject format error."));
1159 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1160 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1161 quote_fmt_reset_vartable();
1165 mfield = SUBJECT_FIELD_PRESENT;
1169 && *body_format != '\0' )
1172 GtkTextBuffer *buffer;
1173 GtkTextIter start, end;
1177 dummyinfo = compose_msginfo_new_from_compose(compose);
1179 text = GTK_TEXT_VIEW(compose->text);
1180 buffer = gtk_text_view_get_buffer(text);
1181 gtk_text_buffer_get_start_iter(buffer, &start);
1182 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1183 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1185 compose_quote_fmt(compose, dummyinfo,
1187 NULL, tmp, FALSE, TRUE,
1188 _("The body of the \"New message\" template has an error at line %d."));
1189 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1190 quote_fmt_reset_vartable();
1194 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1195 gtkaspell_highlight_all(compose->gtkaspell);
1197 mfield = BODY_FIELD_PRESENT;
1201 procmsg_msginfo_free( dummyinfo );
1207 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1208 ainfo = (AttachInfo *) curr->data;
1209 compose_attach_append(compose, ainfo->file, ainfo->file,
1210 ainfo->content_type, ainfo->charset);
1214 compose_show_first_last_header(compose, TRUE);
1216 /* Set save folder */
1217 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1218 gchar *folderidentifier;
1220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1221 folderidentifier = folder_item_get_identifier(item);
1222 compose_set_save_to(compose, folderidentifier);
1223 g_free(folderidentifier);
1226 /* Place cursor according to provided input (mfield) */
1228 case NO_FIELD_PRESENT:
1229 if (compose->header_last)
1230 gtk_widget_grab_focus(compose->header_last->entry);
1232 case TO_FIELD_PRESENT:
1233 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1235 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1238 gtk_widget_grab_focus(compose->subject_entry);
1240 case SUBJECT_FIELD_PRESENT:
1241 textview = GTK_TEXT_VIEW(compose->text);
1244 textbuf = gtk_text_view_get_buffer(textview);
1247 mark = gtk_text_buffer_get_insert(textbuf);
1248 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1249 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1251 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1252 * only defers where it comes to the variable body
1253 * is not null. If no body is present compose->text
1254 * will be null in which case you cannot place the
1255 * cursor inside the component so. An empty component
1256 * is therefore created before placing the cursor
1258 case BODY_FIELD_PRESENT:
1259 cursor_pos = quote_fmt_get_cursor_pos();
1260 if (cursor_pos == -1)
1261 gtk_widget_grab_focus(compose->header_last->entry);
1263 gtk_widget_grab_focus(compose->text);
1267 undo_unblock(compose->undostruct);
1269 if (prefs_common.auto_exteditor)
1270 compose_exec_ext_editor(compose);
1272 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1274 SCROLL_TO_CURSOR(compose);
1276 compose->modified = FALSE;
1277 compose_set_title(compose);
1279 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1284 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1285 gboolean override_pref, const gchar *system)
1287 const gchar *privacy = NULL;
1289 cm_return_if_fail(compose != NULL);
1290 cm_return_if_fail(account != NULL);
1292 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1295 if (account->default_privacy_system && strlen(account->default_privacy_system))
1296 privacy = account->default_privacy_system;
1300 GSList *privacy_avail = privacy_get_system_ids();
1301 if (privacy_avail && g_slist_length(privacy_avail)) {
1302 privacy = (gchar *)(privacy_avail->data);
1305 if (privacy != NULL) {
1307 g_free(compose->privacy_system);
1308 compose->privacy_system = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 else if (*(compose->privacy_system) == '\0') {
1313 g_free(compose->privacy_system);
1314 compose->privacy_system = g_strdup(privacy);
1316 compose_update_privacy_system_menu_item(compose, FALSE);
1317 compose_use_encryption(compose, TRUE);
1321 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1323 const gchar *privacy = NULL;
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1341 if (compose->privacy_system == NULL)
1342 compose->privacy_system = g_strdup(privacy);
1343 compose_update_privacy_system_menu_item(compose, FALSE);
1344 compose_use_signing(compose, TRUE);
1348 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1352 Compose *compose = NULL;
1354 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1356 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1357 cm_return_val_if_fail(msginfo != NULL, NULL);
1359 list_len = g_slist_length(msginfo_list);
1363 case COMPOSE_REPLY_TO_ADDRESS:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1365 FALSE, prefs_common.default_reply_list, FALSE, body);
1367 case COMPOSE_REPLY_WITH_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1369 FALSE, prefs_common.default_reply_list, FALSE, body);
1371 case COMPOSE_REPLY_WITHOUT_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1373 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1375 case COMPOSE_REPLY_TO_SENDER:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, FALSE, TRUE, body);
1379 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1380 compose = compose_followup_and_reply_to(msginfo,
1381 COMPOSE_QUOTE_CHECK,
1382 FALSE, FALSE, body);
1384 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1386 FALSE, FALSE, TRUE, body);
1388 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1390 FALSE, FALSE, TRUE, NULL);
1392 case COMPOSE_REPLY_TO_ALL:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1394 TRUE, FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 TRUE, FALSE, FALSE, body);
1400 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 TRUE, FALSE, FALSE, NULL);
1404 case COMPOSE_REPLY_TO_LIST:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 FALSE, TRUE, FALSE, body);
1408 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 FALSE, TRUE, FALSE, body);
1412 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 FALSE, TRUE, FALSE, NULL);
1416 case COMPOSE_FORWARD:
1417 if (prefs_common.forward_as_attachment) {
1418 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1421 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1425 case COMPOSE_FORWARD_INLINE:
1426 /* check if we reply to more than one Message */
1427 if (list_len == 1) {
1428 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1431 /* more messages FALL THROUGH */
1432 case COMPOSE_FORWARD_AS_ATTACH:
1433 compose = compose_forward_multiple(NULL, msginfo_list);
1435 case COMPOSE_REDIRECT:
1436 compose = compose_redirect(NULL, msginfo, FALSE);
1439 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1442 if (compose == NULL) {
1443 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1447 compose->rmode = mode;
1448 switch (compose->rmode) {
1450 case COMPOSE_REPLY_WITH_QUOTE:
1451 case COMPOSE_REPLY_WITHOUT_QUOTE:
1452 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1453 debug_print("reply mode Normal\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1455 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1457 case COMPOSE_REPLY_TO_SENDER:
1458 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1459 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1460 debug_print("reply mode Sender\n");
1461 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1463 case COMPOSE_REPLY_TO_ALL:
1464 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1465 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1466 debug_print("reply mode All\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1469 case COMPOSE_REPLY_TO_LIST:
1470 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1472 debug_print("reply mode List\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1475 case COMPOSE_REPLY_TO_ADDRESS:
1476 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1484 static Compose *compose_reply(MsgInfo *msginfo,
1485 ComposeQuoteMode quote_mode,
1491 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1492 to_sender, FALSE, body);
1495 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1496 ComposeQuoteMode quote_mode,
1501 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1502 to_sender, TRUE, body);
1505 static void compose_extract_original_charset(Compose *compose)
1507 MsgInfo *info = NULL;
1508 if (compose->replyinfo) {
1509 info = compose->replyinfo;
1510 } else if (compose->fwdinfo) {
1511 info = compose->fwdinfo;
1512 } else if (compose->targetinfo) {
1513 info = compose->targetinfo;
1516 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1517 MimeInfo *partinfo = mimeinfo;
1518 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1519 partinfo = procmime_mimeinfo_next(partinfo);
1521 compose->orig_charset =
1522 g_strdup(procmime_mimeinfo_get_parameter(
1523 partinfo, "charset"));
1525 procmime_mimeinfo_free_all(mimeinfo);
1529 #define SIGNAL_BLOCK(buffer) { \
1530 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1533 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1538 #define SIGNAL_UNBLOCK(buffer) { \
1539 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1540 G_CALLBACK(compose_changed_cb), \
1542 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(text_inserted), \
1547 static Compose *compose_generic_reply(MsgInfo *msginfo,
1548 ComposeQuoteMode quote_mode,
1549 gboolean to_all, gboolean to_ml,
1551 gboolean followup_and_reply_to,
1555 PrefsAccount *account = NULL;
1556 GtkTextView *textview;
1557 GtkTextBuffer *textbuf;
1558 gboolean quote = FALSE;
1559 const gchar *qmark = NULL;
1560 const gchar *body_fmt = NULL;
1561 gchar *s_system = NULL;
1563 cm_return_val_if_fail(msginfo != NULL, NULL);
1564 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1566 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1568 cm_return_val_if_fail(account != NULL, NULL);
1570 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1572 compose->updating = TRUE;
1574 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1577 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1578 if (!compose->replyinfo)
1579 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1581 compose_extract_original_charset(compose);
1583 if (msginfo->folder && msginfo->folder->ret_rcpt)
1584 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1586 /* Set save folder */
1587 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1588 gchar *folderidentifier;
1590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1591 folderidentifier = folder_item_get_identifier(msginfo->folder);
1592 compose_set_save_to(compose, folderidentifier);
1593 g_free(folderidentifier);
1596 if (compose_parse_header(compose, msginfo) < 0) {
1597 compose->updating = FALSE;
1598 compose_destroy(compose);
1602 /* override from name according to folder properties */
1603 if (msginfo->folder && msginfo->folder->prefs &&
1604 msginfo->folder->prefs->reply_with_format &&
1605 msginfo->folder->prefs->reply_override_from_format &&
1606 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1611 /* decode \-escape sequences in the internal representation of the quote format */
1612 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1613 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1616 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1617 compose->gtkaspell);
1619 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1621 quote_fmt_scan_string(tmp);
1624 buf = quote_fmt_get_buffer();
1626 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1628 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1629 quote_fmt_reset_vartable();
1634 textview = (GTK_TEXT_VIEW(compose->text));
1635 textbuf = gtk_text_view_get_buffer(textview);
1636 compose_create_tags(textview, compose);
1638 undo_block(compose->undostruct);
1640 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1641 gtkaspell_block_check(compose->gtkaspell);
1644 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1645 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1646 /* use the reply format of folder (if enabled), or the account's one
1647 (if enabled) or fallback to the global reply format, which is always
1648 enabled (even if empty), and use the relevant quotemark */
1650 if (msginfo->folder && msginfo->folder->prefs &&
1651 msginfo->folder->prefs->reply_with_format) {
1652 qmark = msginfo->folder->prefs->reply_quotemark;
1653 body_fmt = msginfo->folder->prefs->reply_body_format;
1655 } else if (account->reply_with_format) {
1656 qmark = account->reply_quotemark;
1657 body_fmt = account->reply_body_format;
1660 qmark = prefs_common.quotemark;
1661 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1662 body_fmt = gettext(prefs_common.quotefmt);
1669 /* empty quotemark is not allowed */
1670 if (qmark == NULL || *qmark == '\0')
1672 compose_quote_fmt(compose, compose->replyinfo,
1673 body_fmt, qmark, body, FALSE, TRUE,
1674 _("The body of the \"Reply\" template has an error at line %d."));
1675 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1676 quote_fmt_reset_vartable();
1679 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1680 compose_force_encryption(compose, account, FALSE, s_system);
1683 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1684 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1685 compose_force_signing(compose, account, s_system);
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1748 !(account = compose_guess_forward_account_from_msginfo
1750 account = cur_account;
1752 if (!prefs_common.forward_as_attachment)
1753 mode = COMPOSE_FORWARD_INLINE;
1755 mode = COMPOSE_FORWARD;
1756 compose = compose_create(account, msginfo->folder, mode, batch);
1758 compose->updating = TRUE;
1759 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1760 if (!compose->fwdinfo)
1761 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1763 compose_extract_original_charset(compose);
1765 if (msginfo->subject && *msginfo->subject) {
1766 gchar *buf, *buf2, *p;
1768 buf = p = g_strdup(msginfo->subject);
1769 p += subject_get_prefix_length(p);
1770 memmove(buf, p, strlen(p) + 1);
1772 buf2 = g_strdup_printf("Fw: %s", buf);
1773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1779 /* override from name according to folder properties */
1780 if (msginfo->folder && msginfo->folder->prefs &&
1781 msginfo->folder->prefs->forward_with_format &&
1782 msginfo->folder->prefs->forward_override_from_format &&
1783 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1787 MsgInfo *full_msginfo = NULL;
1790 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1792 full_msginfo = procmsg_msginfo_copy(msginfo);
1794 /* decode \-escape sequences in the internal representation of the quote format */
1795 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1796 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1799 gtkaspell_block_check(compose->gtkaspell);
1800 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1801 compose->gtkaspell);
1803 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1805 quote_fmt_scan_string(tmp);
1808 buf = quote_fmt_get_buffer();
1810 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1812 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1813 quote_fmt_reset_vartable();
1816 procmsg_msginfo_free(full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file not exist\n", msgfile);
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1890 SIGNAL_UNBLOCK(textbuf);
1892 cursor_pos = quote_fmt_get_cursor_pos();
1893 if (cursor_pos == -1)
1894 gtk_widget_grab_focus(compose->header_last->entry);
1896 gtk_widget_grab_focus(compose->text);
1898 if (!no_extedit && prefs_common.auto_exteditor)
1899 compose_exec_ext_editor(compose);
1902 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1903 gchar *folderidentifier;
1905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1906 folderidentifier = folder_item_get_identifier(msginfo->folder);
1907 compose_set_save_to(compose, folderidentifier);
1908 g_free(folderidentifier);
1911 undo_unblock(compose->undostruct);
1913 compose->modified = FALSE;
1914 compose_set_title(compose);
1916 compose->updating = FALSE;
1917 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1918 SCROLL_TO_CURSOR(compose);
1920 if (compose->deferred_destroy) {
1921 compose_destroy(compose);
1925 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1930 #undef INSERT_FW_HEADER
1932 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1935 GtkTextView *textview;
1936 GtkTextBuffer *textbuf;
1940 gboolean single_mail = TRUE;
1942 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1944 if (g_slist_length(msginfo_list) > 1)
1945 single_mail = FALSE;
1947 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1948 if (((MsgInfo *)msginfo->data)->folder == NULL)
1951 /* guess account from first selected message */
1953 !(account = compose_guess_forward_account_from_msginfo
1954 (msginfo_list->data)))
1955 account = cur_account;
1957 cm_return_val_if_fail(account != NULL, NULL);
1959 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1960 if (msginfo->data) {
1961 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1962 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1966 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1967 g_warning("no msginfo_list");
1971 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1973 compose->updating = TRUE;
1975 /* override from name according to folder properties */
1976 if (msginfo_list->data) {
1977 MsgInfo *msginfo = msginfo_list->data;
1979 if (msginfo->folder && msginfo->folder->prefs &&
1980 msginfo->folder->prefs->forward_with_format &&
1981 msginfo->folder->prefs->forward_override_from_format &&
1982 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1987 /* decode \-escape sequences in the internal representation of the quote format */
1988 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1989 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1992 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1993 compose->gtkaspell);
1995 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1997 quote_fmt_scan_string(tmp);
2000 buf = quote_fmt_get_buffer();
2002 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2004 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2005 quote_fmt_reset_vartable();
2011 textview = GTK_TEXT_VIEW(compose->text);
2012 textbuf = gtk_text_view_get_buffer(textview);
2013 compose_create_tags(textview, compose);
2015 undo_block(compose->undostruct);
2016 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2017 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2019 if (!is_file_exist(msgfile))
2020 g_warning("%s: file not exist\n", msgfile);
2022 compose_attach_append(compose, msgfile, msgfile,
2023 "message/rfc822", NULL);
2028 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2029 if (info->subject && *info->subject) {
2030 gchar *buf, *buf2, *p;
2032 buf = p = g_strdup(info->subject);
2033 p += subject_get_prefix_length(p);
2034 memmove(buf, p, strlen(p) + 1);
2036 buf2 = g_strdup_printf("Fw: %s", buf);
2037 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2043 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2044 _("Fw: multiple emails"));
2047 SIGNAL_BLOCK(textbuf);
2049 if (account->auto_sig)
2050 compose_insert_sig(compose, FALSE);
2052 compose_wrap_all(compose);
2054 SIGNAL_UNBLOCK(textbuf);
2056 gtk_text_buffer_get_start_iter(textbuf, &iter);
2057 gtk_text_buffer_place_cursor(textbuf, &iter);
2059 gtk_widget_grab_focus(compose->header_last->entry);
2060 undo_unblock(compose->undostruct);
2061 compose->modified = FALSE;
2062 compose_set_title(compose);
2064 compose->updating = FALSE;
2065 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2066 SCROLL_TO_CURSOR(compose);
2068 if (compose->deferred_destroy) {
2069 compose_destroy(compose);
2073 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2078 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2080 GtkTextIter start = *iter;
2081 GtkTextIter end_iter;
2082 int start_pos = gtk_text_iter_get_offset(&start);
2084 if (!compose->account->sig_sep)
2087 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2088 start_pos+strlen(compose->account->sig_sep));
2090 /* check sig separator */
2091 str = gtk_text_iter_get_text(&start, &end_iter);
2092 if (!strcmp(str, compose->account->sig_sep)) {
2094 /* check end of line (\n) */
2095 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2096 start_pos+strlen(compose->account->sig_sep));
2097 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2098 start_pos+strlen(compose->account->sig_sep)+1);
2099 tmp = gtk_text_iter_get_text(&start, &end_iter);
2100 if (!strcmp(tmp,"\n")) {
2112 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2114 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2115 Compose *compose = (Compose *)data;
2116 FolderItem *old_item = NULL;
2117 FolderItem *new_item = NULL;
2118 gchar *old_id, *new_id;
2120 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2121 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2124 old_item = hookdata->item;
2125 new_item = hookdata->item2;
2127 old_id = folder_item_get_identifier(old_item);
2128 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2130 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2131 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->targetinfo->folder = new_item;
2135 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2136 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2137 compose->replyinfo->folder = new_item;
2140 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2141 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2142 compose->fwdinfo->folder = new_item;
2150 static void compose_colorize_signature(Compose *compose)
2152 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2154 GtkTextIter end_iter;
2155 gtk_text_buffer_get_start_iter(buffer, &iter);
2156 while (gtk_text_iter_forward_line(&iter))
2157 if (compose_is_sig_separator(compose, buffer, &iter)) {
2158 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2159 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2163 #define BLOCK_WRAP() { \
2164 prev_autowrap = compose->autowrap; \
2165 buffer = gtk_text_view_get_buffer( \
2166 GTK_TEXT_VIEW(compose->text)); \
2167 compose->autowrap = FALSE; \
2169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2170 G_CALLBACK(compose_changed_cb), \
2172 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2173 G_CALLBACK(text_inserted), \
2176 #define UNBLOCK_WRAP() { \
2177 compose->autowrap = prev_autowrap; \
2178 if (compose->autowrap) { \
2179 gint old = compose->draft_timeout_tag; \
2180 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2181 compose_wrap_all(compose); \
2182 compose->draft_timeout_tag = old; \
2185 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(compose_changed_cb), \
2188 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2189 G_CALLBACK(text_inserted), \
2193 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2195 Compose *compose = NULL;
2196 PrefsAccount *account = NULL;
2197 GtkTextView *textview;
2198 GtkTextBuffer *textbuf;
2202 gchar buf[BUFFSIZE];
2203 gboolean use_signing = FALSE;
2204 gboolean use_encryption = FALSE;
2205 gchar *privacy_system = NULL;
2206 int priority = PRIORITY_NORMAL;
2207 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2208 gboolean autowrap = prefs_common.autowrap;
2209 gboolean autoindent = prefs_common.auto_indent;
2210 HeaderEntry *manual_headers = NULL;
2212 cm_return_val_if_fail(msginfo != NULL, NULL);
2213 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2215 if (compose_put_existing_to_front(msginfo)) {
2219 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2220 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2221 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2222 gchar queueheader_buf[BUFFSIZE];
2225 /* Select Account from queue headers */
2226 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2227 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2228 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2229 account = account_find_from_id(id);
2231 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2232 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2233 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2234 account = account_find_from_id(id);
2236 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2237 sizeof(queueheader_buf), "NAID:")) {
2238 id = atoi(&queueheader_buf[strlen("NAID:")]);
2239 account = account_find_from_id(id);
2241 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "MAID:")) {
2243 id = atoi(&queueheader_buf[strlen("MAID:")]);
2244 account = account_find_from_id(id);
2246 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2247 sizeof(queueheader_buf), "S:")) {
2248 account = account_find_from_address(queueheader_buf, FALSE);
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2251 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2252 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2253 use_signing = param;
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2258 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2259 use_signing = param;
2262 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2263 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2264 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2265 use_encryption = param;
2267 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2268 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2269 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2270 use_encryption = param;
2272 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2273 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2277 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2278 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2284 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2286 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2287 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2288 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2290 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2291 sizeof(queueheader_buf), "X-Priority: ")) {
2292 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2295 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2296 sizeof(queueheader_buf), "RMID:")) {
2297 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2298 if (tokens[0] && tokens[1] && tokens[2]) {
2299 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2300 if (orig_item != NULL) {
2301 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "FMID:")) {
2308 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2309 if (tokens[0] && tokens[1] && tokens[2]) {
2310 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2311 if (orig_item != NULL) {
2312 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2317 /* Get manual headers */
2318 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2319 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2320 if (*listmh != '\0') {
2321 debug_print("Got manual headers: %s\n", listmh);
2322 manual_headers = procheader_entries_from_str(listmh);
2327 account = msginfo->folder->folder->account;
2330 if (!account && prefs_common.reedit_account_autosel) {
2331 gchar from[BUFFSIZE];
2332 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2333 extract_address(from);
2334 account = account_find_from_address(from, FALSE);
2338 account = cur_account;
2340 cm_return_val_if_fail(account != NULL, NULL);
2342 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2344 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2345 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2346 compose->autowrap = autowrap;
2347 compose->replyinfo = replyinfo;
2348 compose->fwdinfo = fwdinfo;
2350 compose->updating = TRUE;
2351 compose->priority = priority;
2353 if (privacy_system != NULL) {
2354 compose->privacy_system = privacy_system;
2355 compose_use_signing(compose, use_signing);
2356 compose_use_encryption(compose, use_encryption);
2357 compose_update_privacy_system_menu_item(compose, FALSE);
2359 activate_privacy_system(compose, account, FALSE);
2362 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2364 compose_extract_original_charset(compose);
2366 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2367 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2368 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2369 gchar queueheader_buf[BUFFSIZE];
2371 /* Set message save folder */
2372 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2373 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2374 compose_set_save_to(compose, &queueheader_buf[4]);
2376 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2377 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2384 if (compose_parse_header(compose, msginfo) < 0) {
2385 compose->updating = FALSE;
2386 compose_destroy(compose);
2389 compose_reedit_set_entry(compose, msginfo);
2391 textview = GTK_TEXT_VIEW(compose->text);
2392 textbuf = gtk_text_view_get_buffer(textview);
2393 compose_create_tags(textview, compose);
2395 mark = gtk_text_buffer_get_insert(textbuf);
2396 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2398 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2399 G_CALLBACK(compose_changed_cb),
2402 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2403 fp = procmime_get_first_encrypted_text_content(msginfo);
2405 compose_force_encryption(compose, account, TRUE, NULL);
2408 fp = procmime_get_first_text_content(msginfo);
2411 g_warning("Can't get text part\n");
2415 gboolean prev_autowrap;
2416 GtkTextBuffer *buffer;
2418 while (fgets(buf, sizeof(buf), fp) != NULL) {
2420 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2426 compose_attach_parts(compose, msginfo);
2428 compose_colorize_signature(compose);
2430 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2431 G_CALLBACK(compose_changed_cb),
2434 if (manual_headers != NULL) {
2435 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2436 procheader_entries_free(manual_headers);
2437 compose->updating = FALSE;
2438 compose_destroy(compose);
2441 procheader_entries_free(manual_headers);
2444 gtk_widget_grab_focus(compose->text);
2446 if (prefs_common.auto_exteditor) {
2447 compose_exec_ext_editor(compose);
2449 compose->modified = FALSE;
2450 compose_set_title(compose);
2452 compose->updating = FALSE;
2453 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2454 SCROLL_TO_CURSOR(compose);
2456 if (compose->deferred_destroy) {
2457 compose_destroy(compose);
2461 compose->sig_str = account_get_signature_str(compose->account);
2463 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2468 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2475 cm_return_val_if_fail(msginfo != NULL, NULL);
2478 account = account_get_reply_account(msginfo,
2479 prefs_common.reply_account_autosel);
2480 cm_return_val_if_fail(account != NULL, NULL);
2482 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2484 compose->updating = TRUE;
2486 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2487 compose->replyinfo = NULL;
2488 compose->fwdinfo = NULL;
2490 compose_show_first_last_header(compose, TRUE);
2492 gtk_widget_grab_focus(compose->header_last->entry);
2494 filename = procmsg_get_message_file(msginfo);
2496 if (filename == NULL) {
2497 compose->updating = FALSE;
2498 compose_destroy(compose);
2503 compose->redirect_filename = filename;
2505 /* Set save folder */
2506 item = msginfo->folder;
2507 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2508 gchar *folderidentifier;
2510 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2511 folderidentifier = folder_item_get_identifier(item);
2512 compose_set_save_to(compose, folderidentifier);
2513 g_free(folderidentifier);
2516 compose_attach_parts(compose, msginfo);
2518 if (msginfo->subject)
2519 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2521 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2523 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2524 _("The body of the \"Redirect\" template has an error at line %d."));
2525 quote_fmt_reset_vartable();
2526 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2528 compose_colorize_signature(compose);
2531 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2532 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2533 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2543 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2545 if (compose->toolbar->draft_btn)
2546 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2547 if (compose->toolbar->insert_btn)
2548 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2549 if (compose->toolbar->attach_btn)
2550 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2551 if (compose->toolbar->sig_btn)
2552 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2553 if (compose->toolbar->exteditor_btn)
2554 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2555 if (compose->toolbar->linewrap_current_btn)
2556 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2557 if (compose->toolbar->linewrap_all_btn)
2558 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2560 compose->modified = FALSE;
2561 compose_set_title(compose);
2562 compose->updating = FALSE;
2563 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2564 SCROLL_TO_CURSOR(compose);
2566 if (compose->deferred_destroy) {
2567 compose_destroy(compose);
2571 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2576 const GList *compose_get_compose_list(void)
2578 return compose_list;
2581 void compose_entry_append(Compose *compose, const gchar *address,
2582 ComposeEntryType type, ComposePrefType pref_type)
2584 const gchar *header;
2586 gboolean in_quote = FALSE;
2587 if (!address || *address == '\0') return;
2594 header = N_("Bcc:");
2596 case COMPOSE_REPLYTO:
2597 header = N_("Reply-To:");
2599 case COMPOSE_NEWSGROUPS:
2600 header = N_("Newsgroups:");
2602 case COMPOSE_FOLLOWUPTO:
2603 header = N_( "Followup-To:");
2605 case COMPOSE_INREPLYTO:
2606 header = N_( "In-Reply-To:");
2613 header = prefs_common_translated_header_name(header);
2615 cur = begin = (gchar *)address;
2617 /* we separate the line by commas, but not if we're inside a quoted
2619 while (*cur != '\0') {
2621 in_quote = !in_quote;
2622 if (*cur == ',' && !in_quote) {
2623 gchar *tmp = g_strdup(begin);
2625 tmp[cur-begin]='\0';
2628 while (*tmp == ' ' || *tmp == '\t')
2630 compose_add_header_entry(compose, header, tmp, pref_type);
2637 gchar *tmp = g_strdup(begin);
2639 tmp[cur-begin]='\0';
2640 while (*tmp == ' ' || *tmp == '\t')
2642 compose_add_header_entry(compose, header, tmp, pref_type);
2647 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2649 #if !GTK_CHECK_VERSION(3, 0, 0)
2650 static GdkColor yellow;
2651 static GdkColor black;
2652 static gboolean yellow_initialised = FALSE;
2654 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2655 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2660 #if !GTK_CHECK_VERSION(3, 0, 0)
2661 if (!yellow_initialised) {
2662 gdk_color_parse("#f5f6be", &yellow);
2663 gdk_color_parse("#000000", &black);
2664 yellow_initialised = gdk_colormap_alloc_color(
2665 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2666 yellow_initialised &= gdk_colormap_alloc_color(
2667 gdk_colormap_get_system(), &black, FALSE, TRUE);
2671 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2672 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2673 if (gtk_entry_get_text(entry) &&
2674 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (yellow_initialised) {
2678 gtk_widget_modify_base(
2679 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2680 GTK_STATE_NORMAL, &yellow);
2681 gtk_widget_modify_text(
2682 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2683 GTK_STATE_NORMAL, &black);
2684 #if !GTK_CHECK_VERSION(3, 0, 0)
2691 void compose_toolbar_cb(gint action, gpointer data)
2693 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2694 Compose *compose = (Compose*)toolbar_item->parent;
2696 cm_return_if_fail(compose != NULL);
2700 compose_send_cb(NULL, compose);
2703 compose_send_later_cb(NULL, compose);
2706 compose_draft(compose, COMPOSE_QUIT_EDITING);
2709 compose_insert_file_cb(NULL, compose);
2712 compose_attach_cb(NULL, compose);
2715 compose_insert_sig(compose, FALSE);
2718 compose_insert_sig(compose, TRUE);
2721 compose_ext_editor_cb(NULL, compose);
2723 case A_LINEWRAP_CURRENT:
2724 compose_beautify_paragraph(compose, NULL, TRUE);
2726 case A_LINEWRAP_ALL:
2727 compose_wrap_all_full(compose, TRUE);
2730 compose_address_cb(NULL, compose);
2733 case A_CHECK_SPELLING:
2734 compose_check_all(NULL, compose);
2742 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2747 gchar *subject = NULL;
2751 gchar **attach = NULL;
2752 gchar *inreplyto = NULL;
2753 MailField mfield = NO_FIELD_PRESENT;
2755 /* get mailto parts but skip from */
2756 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2759 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2760 mfield = TO_FIELD_PRESENT;
2763 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2765 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2767 if (!g_utf8_validate (subject, -1, NULL)) {
2768 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2774 mfield = SUBJECT_FIELD_PRESENT;
2777 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2778 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2781 gboolean prev_autowrap = compose->autowrap;
2783 compose->autowrap = FALSE;
2785 mark = gtk_text_buffer_get_insert(buffer);
2786 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2788 if (!g_utf8_validate (body, -1, NULL)) {
2789 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2790 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2793 gtk_text_buffer_insert(buffer, &iter, body, -1);
2795 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2797 compose->autowrap = prev_autowrap;
2798 if (compose->autowrap)
2799 compose_wrap_all(compose);
2800 mfield = BODY_FIELD_PRESENT;
2804 gint i = 0, att = 0;
2805 gchar *warn_files = NULL;
2806 while (attach[i] != NULL) {
2807 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2808 if (utf8_filename) {
2809 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2810 gchar *tmp = g_strdup_printf("%s%s\n",
2811 warn_files?warn_files:"",
2817 g_free(utf8_filename);
2819 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2824 alertpanel_notice(ngettext(
2825 "The following file has been attached: \n%s",
2826 "The following files have been attached: \n%s", att), warn_files);
2831 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2844 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2846 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2847 {"Cc:", NULL, TRUE},
2848 {"References:", NULL, FALSE},
2849 {"Bcc:", NULL, TRUE},
2850 {"Newsgroups:", NULL, TRUE},
2851 {"Followup-To:", NULL, TRUE},
2852 {"List-Post:", NULL, FALSE},
2853 {"X-Priority:", NULL, FALSE},
2854 {NULL, NULL, FALSE}};
2870 cm_return_val_if_fail(msginfo != NULL, -1);
2872 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2873 procheader_get_header_fields(fp, hentry);
2876 if (hentry[H_REPLY_TO].body != NULL) {
2877 if (hentry[H_REPLY_TO].body[0] != '\0') {
2879 conv_unmime_header(hentry[H_REPLY_TO].body,
2882 g_free(hentry[H_REPLY_TO].body);
2883 hentry[H_REPLY_TO].body = NULL;
2885 if (hentry[H_CC].body != NULL) {
2886 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2887 g_free(hentry[H_CC].body);
2888 hentry[H_CC].body = NULL;
2890 if (hentry[H_REFERENCES].body != NULL) {
2891 if (compose->mode == COMPOSE_REEDIT)
2892 compose->references = hentry[H_REFERENCES].body;
2894 compose->references = compose_parse_references
2895 (hentry[H_REFERENCES].body, msginfo->msgid);
2896 g_free(hentry[H_REFERENCES].body);
2898 hentry[H_REFERENCES].body = NULL;
2900 if (hentry[H_BCC].body != NULL) {
2901 if (compose->mode == COMPOSE_REEDIT)
2903 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2904 g_free(hentry[H_BCC].body);
2905 hentry[H_BCC].body = NULL;
2907 if (hentry[H_NEWSGROUPS].body != NULL) {
2908 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2909 hentry[H_NEWSGROUPS].body = NULL;
2911 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2912 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2913 compose->followup_to =
2914 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2917 g_free(hentry[H_FOLLOWUP_TO].body);
2918 hentry[H_FOLLOWUP_TO].body = NULL;
2920 if (hentry[H_LIST_POST].body != NULL) {
2921 gchar *to = NULL, *start = NULL;
2923 extract_address(hentry[H_LIST_POST].body);
2924 if (hentry[H_LIST_POST].body[0] != '\0') {
2925 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2927 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2928 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2931 g_free(compose->ml_post);
2932 compose->ml_post = to;
2935 g_free(hentry[H_LIST_POST].body);
2936 hentry[H_LIST_POST].body = NULL;
2939 /* CLAWS - X-Priority */
2940 if (compose->mode == COMPOSE_REEDIT)
2941 if (hentry[H_X_PRIORITY].body != NULL) {
2944 priority = atoi(hentry[H_X_PRIORITY].body);
2945 g_free(hentry[H_X_PRIORITY].body);
2947 hentry[H_X_PRIORITY].body = NULL;
2949 if (priority < PRIORITY_HIGHEST ||
2950 priority > PRIORITY_LOWEST)
2951 priority = PRIORITY_NORMAL;
2953 compose->priority = priority;
2956 if (compose->mode == COMPOSE_REEDIT) {
2957 if (msginfo->inreplyto && *msginfo->inreplyto)
2958 compose->inreplyto = g_strdup(msginfo->inreplyto);
2962 if (msginfo->msgid && *msginfo->msgid)
2963 compose->inreplyto = g_strdup(msginfo->msgid);
2965 if (!compose->references) {
2966 if (msginfo->msgid && *msginfo->msgid) {
2967 if (msginfo->inreplyto && *msginfo->inreplyto)
2968 compose->references =
2969 g_strdup_printf("<%s>\n\t<%s>",
2973 compose->references =
2974 g_strconcat("<", msginfo->msgid, ">",
2976 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2977 compose->references =
2978 g_strconcat("<", msginfo->inreplyto, ">",
2986 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2991 cm_return_val_if_fail(msginfo != NULL, -1);
2993 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2994 procheader_get_header_fields(fp, entries);
2998 while (he != NULL && he->name != NULL) {
3000 GtkListStore *model = NULL;
3002 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3003 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3004 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3005 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3006 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3013 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3015 GSList *ref_id_list, *cur;
3019 ref_id_list = references_list_append(NULL, ref);
3020 if (!ref_id_list) return NULL;
3021 if (msgid && *msgid)
3022 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3027 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3028 /* "<" + Message-ID + ">" + CR+LF+TAB */
3029 len += strlen((gchar *)cur->data) + 5;
3031 if (len > MAX_REFERENCES_LEN) {
3032 /* remove second message-ID */
3033 if (ref_id_list && ref_id_list->next &&
3034 ref_id_list->next->next) {
3035 g_free(ref_id_list->next->data);
3036 ref_id_list = g_slist_remove
3037 (ref_id_list, ref_id_list->next->data);
3039 slist_free_strings_full(ref_id_list);
3046 new_ref = g_string_new("");
3047 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3048 if (new_ref->len > 0)
3049 g_string_append(new_ref, "\n\t");
3050 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3053 slist_free_strings_full(ref_id_list);
3055 new_ref_str = new_ref->str;
3056 g_string_free(new_ref, FALSE);
3061 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3062 const gchar *fmt, const gchar *qmark,
3063 const gchar *body, gboolean rewrap,
3064 gboolean need_unescape,
3065 const gchar *err_msg)
3067 MsgInfo* dummyinfo = NULL;
3068 gchar *quote_str = NULL;
3070 gboolean prev_autowrap;
3071 const gchar *trimmed_body = body;
3072 gint cursor_pos = -1;
3073 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3074 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3079 SIGNAL_BLOCK(buffer);
3082 dummyinfo = compose_msginfo_new_from_compose(compose);
3083 msginfo = dummyinfo;
3086 if (qmark != NULL) {
3088 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3089 compose->gtkaspell);
3091 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3093 quote_fmt_scan_string(qmark);
3096 buf = quote_fmt_get_buffer();
3098 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3100 Xstrdup_a(quote_str, buf, goto error)
3103 if (fmt && *fmt != '\0') {
3106 while (*trimmed_body == '\n')
3110 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3111 compose->gtkaspell);
3113 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3115 if (need_unescape) {
3118 /* decode \-escape sequences in the internal representation of the quote format */
3119 tmp = g_malloc(strlen(fmt)+1);
3120 pref_get_unescaped_pref(tmp, fmt);
3121 quote_fmt_scan_string(tmp);
3125 quote_fmt_scan_string(fmt);
3129 buf = quote_fmt_get_buffer();
3131 gint line = quote_fmt_get_line();
3132 alertpanel_error(err_msg, line);
3138 prev_autowrap = compose->autowrap;
3139 compose->autowrap = FALSE;
3141 mark = gtk_text_buffer_get_insert(buffer);
3142 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3143 if (g_utf8_validate(buf, -1, NULL)) {
3144 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3146 gchar *tmpout = NULL;
3147 tmpout = conv_codeset_strdup
3148 (buf, conv_get_locale_charset_str_no_utf8(),
3150 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3152 tmpout = g_malloc(strlen(buf)*2+1);
3153 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3155 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3159 cursor_pos = quote_fmt_get_cursor_pos();
3160 if (cursor_pos == -1)
3161 cursor_pos = gtk_text_iter_get_offset(&iter);
3162 compose->set_cursor_pos = cursor_pos;
3164 gtk_text_buffer_get_start_iter(buffer, &iter);
3165 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3166 gtk_text_buffer_place_cursor(buffer, &iter);
3168 compose->autowrap = prev_autowrap;
3169 if (compose->autowrap && rewrap)
3170 compose_wrap_all(compose);
3177 SIGNAL_UNBLOCK(buffer);
3179 procmsg_msginfo_free( dummyinfo );
3184 /* if ml_post is of type addr@host and from is of type
3185 * addr-anything@host, return TRUE
3187 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3189 gchar *left_ml = NULL;
3190 gchar *right_ml = NULL;
3191 gchar *left_from = NULL;
3192 gchar *right_from = NULL;
3193 gboolean result = FALSE;
3195 if (!ml_post || !from)
3198 left_ml = g_strdup(ml_post);
3199 if (strstr(left_ml, "@")) {
3200 right_ml = strstr(left_ml, "@")+1;
3201 *(strstr(left_ml, "@")) = '\0';
3204 left_from = g_strdup(from);
3205 if (strstr(left_from, "@")) {
3206 right_from = strstr(left_from, "@")+1;
3207 *(strstr(left_from, "@")) = '\0';
3210 if (right_ml && right_from
3211 && !strncmp(left_from, left_ml, strlen(left_ml))
3212 && !strcmp(right_from, right_ml)) {
3221 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3222 gboolean respect_default_to)
3226 if (!folder || !folder->prefs)
3229 if (respect_default_to && folder->prefs->enable_default_to) {
3230 compose_entry_append(compose, folder->prefs->default_to,
3231 COMPOSE_TO, PREF_FOLDER);
3232 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3234 if (folder->prefs->enable_default_cc)
3235 compose_entry_append(compose, folder->prefs->default_cc,
3236 COMPOSE_CC, PREF_FOLDER);
3237 if (folder->prefs->enable_default_bcc)
3238 compose_entry_append(compose, folder->prefs->default_bcc,
3239 COMPOSE_BCC, PREF_FOLDER);
3240 if (folder->prefs->enable_default_replyto)
3241 compose_entry_append(compose, folder->prefs->default_replyto,
3242 COMPOSE_REPLYTO, PREF_FOLDER);
3245 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3250 if (!compose || !msginfo)
3253 if (msginfo->subject && *msginfo->subject) {
3254 buf = p = g_strdup(msginfo->subject);
3255 p += subject_get_prefix_length(p);
3256 memmove(buf, p, strlen(p) + 1);
3258 buf2 = g_strdup_printf("Re: %s", buf);
3259 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3264 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3267 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3268 gboolean to_all, gboolean to_ml,
3270 gboolean followup_and_reply_to)
3272 GSList *cc_list = NULL;
3275 gchar *replyto = NULL;
3276 gchar *ac_email = NULL;
3278 gboolean reply_to_ml = FALSE;
3279 gboolean default_reply_to = FALSE;
3281 cm_return_if_fail(compose->account != NULL);
3282 cm_return_if_fail(msginfo != NULL);
3284 reply_to_ml = to_ml && compose->ml_post;
3286 default_reply_to = msginfo->folder &&
3287 msginfo->folder->prefs->enable_default_reply_to;
3289 if (compose->account->protocol != A_NNTP) {
3290 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3292 if (reply_to_ml && !default_reply_to) {
3294 gboolean is_subscr = is_subscription(compose->ml_post,
3297 /* normal answer to ml post with a reply-to */
3298 compose_entry_append(compose,
3300 COMPOSE_TO, PREF_ML);
3301 if (compose->replyto)
3302 compose_entry_append(compose,
3304 COMPOSE_CC, PREF_ML);
3306 /* answer to subscription confirmation */
3307 if (compose->replyto)
3308 compose_entry_append(compose,
3310 COMPOSE_TO, PREF_ML);
3311 else if (msginfo->from)
3312 compose_entry_append(compose,
3314 COMPOSE_TO, PREF_ML);
3317 else if (!(to_all || to_sender) && default_reply_to) {
3318 compose_entry_append(compose,
3319 msginfo->folder->prefs->default_reply_to,
3320 COMPOSE_TO, PREF_FOLDER);
3321 compose_entry_mark_default_to(compose,
3322 msginfo->folder->prefs->default_reply_to);
3328 compose_entry_append(compose, msginfo->from,
3329 COMPOSE_TO, PREF_NONE);
3331 Xstrdup_a(tmp1, msginfo->from, return);
3332 extract_address(tmp1);
3333 compose_entry_append(compose,
3334 (!account_find_from_address(tmp1, FALSE))
3337 COMPOSE_TO, PREF_NONE);
3339 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3340 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3341 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3342 if (compose->replyto) {
3343 compose_entry_append(compose,
3345 COMPOSE_TO, PREF_NONE);
3347 compose_entry_append(compose,
3348 msginfo->from ? msginfo->from : "",
3349 COMPOSE_TO, PREF_NONE);
3352 /* replying to own mail, use original recp */
3353 compose_entry_append(compose,
3354 msginfo->to ? msginfo->to : "",
3355 COMPOSE_TO, PREF_NONE);
3356 compose_entry_append(compose,
3357 msginfo->cc ? msginfo->cc : "",
3358 COMPOSE_CC, PREF_NONE);
3363 if (to_sender || (compose->followup_to &&
3364 !strncmp(compose->followup_to, "poster", 6)))
3365 compose_entry_append
3367 (compose->replyto ? compose->replyto :
3368 msginfo->from ? msginfo->from : ""),
3369 COMPOSE_TO, PREF_NONE);
3371 else if (followup_and_reply_to || to_all) {
3372 compose_entry_append
3374 (compose->replyto ? compose->replyto :
3375 msginfo->from ? msginfo->from : ""),
3376 COMPOSE_TO, PREF_NONE);
3378 compose_entry_append
3380 compose->followup_to ? compose->followup_to :
3381 compose->newsgroups ? compose->newsgroups : "",
3382 COMPOSE_NEWSGROUPS, PREF_NONE);
3385 compose_entry_append
3387 compose->followup_to ? compose->followup_to :
3388 compose->newsgroups ? compose->newsgroups : "",
3389 COMPOSE_NEWSGROUPS, PREF_NONE);
3391 compose_reply_set_subject(compose, msginfo);
3393 if (to_ml && compose->ml_post) return;
3394 if (!to_all || compose->account->protocol == A_NNTP) return;
3396 if (compose->replyto) {
3397 Xstrdup_a(replyto, compose->replyto, return);
3398 extract_address(replyto);
3400 if (msginfo->from) {
3401 Xstrdup_a(from, msginfo->from, return);
3402 extract_address(from);
3405 if (replyto && from)
3406 cc_list = address_list_append_with_comments(cc_list, from);
3407 if (to_all && msginfo->folder &&
3408 msginfo->folder->prefs->enable_default_reply_to)
3409 cc_list = address_list_append_with_comments(cc_list,
3410 msginfo->folder->prefs->default_reply_to);
3411 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3412 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3414 ac_email = g_utf8_strdown(compose->account->address, -1);
3417 for (cur = cc_list; cur != NULL; cur = cur->next) {
3418 gchar *addr = g_utf8_strdown(cur->data, -1);
3419 extract_address(addr);
3421 if (strcmp(ac_email, addr))
3422 compose_entry_append(compose, (gchar *)cur->data,
3423 COMPOSE_CC, PREF_NONE);
3425 debug_print("Cc address same as compose account's, ignoring\n");
3430 slist_free_strings_full(cc_list);
3436 #define SET_ENTRY(entry, str) \
3439 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3442 #define SET_ADDRESS(type, str) \
3445 compose_entry_append(compose, str, type, PREF_NONE); \
3448 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3450 cm_return_if_fail(msginfo != NULL);
3452 SET_ENTRY(subject_entry, msginfo->subject);
3453 SET_ENTRY(from_name, msginfo->from);
3454 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3455 SET_ADDRESS(COMPOSE_CC, compose->cc);
3456 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3457 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3458 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3459 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3461 compose_update_priority_menu_item(compose);
3462 compose_update_privacy_system_menu_item(compose, FALSE);
3463 compose_show_first_last_header(compose, TRUE);
3469 static void compose_insert_sig(Compose *compose, gboolean replace)
3471 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3472 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3474 GtkTextIter iter, iter_end;
3475 gint cur_pos, ins_pos;
3476 gboolean prev_autowrap;
3477 gboolean found = FALSE;
3478 gboolean exists = FALSE;
3480 cm_return_if_fail(compose->account != NULL);
3484 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3485 G_CALLBACK(compose_changed_cb),
3488 mark = gtk_text_buffer_get_insert(buffer);
3489 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3490 cur_pos = gtk_text_iter_get_offset (&iter);
3493 gtk_text_buffer_get_end_iter(buffer, &iter);
3495 exists = (compose->sig_str != NULL);
3498 GtkTextIter first_iter, start_iter, end_iter;
3500 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3502 if (!exists || compose->sig_str[0] == '\0')
3505 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3506 compose->signature_tag);
3509 /* include previous \n\n */
3510 gtk_text_iter_backward_chars(&first_iter, 1);
3511 start_iter = first_iter;
3512 end_iter = first_iter;
3514 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3515 compose->signature_tag);
3516 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3517 compose->signature_tag);
3519 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3525 g_free(compose->sig_str);
3526 compose->sig_str = account_get_signature_str(compose->account);
3528 cur_pos = gtk_text_iter_get_offset(&iter);
3530 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3531 g_free(compose->sig_str);
3532 compose->sig_str = NULL;
3534 if (compose->sig_inserted == FALSE)
3535 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3536 compose->sig_inserted = TRUE;
3538 cur_pos = gtk_text_iter_get_offset(&iter);
3539 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3541 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3542 gtk_text_iter_forward_chars(&iter, 1);
3543 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3544 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3546 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3547 cur_pos = gtk_text_buffer_get_char_count (buffer);
3550 /* put the cursor where it should be
3551 * either where the quote_fmt says, either where it was */
3552 if (compose->set_cursor_pos < 0)
3553 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3555 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3556 compose->set_cursor_pos);
3558 compose->set_cursor_pos = -1;
3559 gtk_text_buffer_place_cursor(buffer, &iter);
3560 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3561 G_CALLBACK(compose_changed_cb),
3567 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3570 GtkTextBuffer *buffer;
3573 const gchar *cur_encoding;
3574 gchar buf[BUFFSIZE];
3577 gboolean prev_autowrap;
3578 struct stat file_stat;
3580 GString *file_contents = NULL;
3581 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3583 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3585 /* get the size of the file we are about to insert */
3586 ret = g_stat(file, &file_stat);
3588 gchar *shortfile = g_path_get_basename(file);
3589 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3591 return COMPOSE_INSERT_NO_FILE;
3592 } else if (prefs_common.warn_large_insert == TRUE) {
3594 /* ask user for confirmation if the file is large */
3595 if (prefs_common.warn_large_insert_size < 0 ||
3596 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3600 msg = g_strdup_printf(_("You are about to insert a file of %s "
3601 "in the message body. Are you sure you want to do that?"),
3602 to_human_readable(file_stat.st_size));
3603 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3604 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3607 /* do we ask for confirmation next time? */
3608 if (aval & G_ALERTDISABLE) {
3609 /* no confirmation next time, disable feature in preferences */
3610 aval &= ~G_ALERTDISABLE;
3611 prefs_common.warn_large_insert = FALSE;
3614 /* abort file insertion if user canceled action */
3615 if (aval != G_ALERTALTERNATE) {
3616 return COMPOSE_INSERT_NO_FILE;
3622 if ((fp = g_fopen(file, "rb")) == NULL) {
3623 FILE_OP_ERROR(file, "fopen");
3624 return COMPOSE_INSERT_READ_ERROR;
3627 prev_autowrap = compose->autowrap;
3628 compose->autowrap = FALSE;
3630 text = GTK_TEXT_VIEW(compose->text);
3631 buffer = gtk_text_view_get_buffer(text);
3632 mark = gtk_text_buffer_get_insert(buffer);
3633 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3635 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3636 G_CALLBACK(text_inserted),
3639 cur_encoding = conv_get_locale_charset_str_no_utf8();
3641 file_contents = g_string_new("");
3642 while (fgets(buf, sizeof(buf), fp) != NULL) {
3645 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3646 str = g_strdup(buf);
3648 codeconv_set_strict(TRUE);
3649 str = conv_codeset_strdup
3650 (buf, cur_encoding, CS_INTERNAL);
3651 codeconv_set_strict(FALSE);
3654 result = COMPOSE_INSERT_INVALID_CHARACTER;
3660 /* strip <CR> if DOS/Windows file,
3661 replace <CR> with <LF> if Macintosh file. */
3664 if (len > 0 && str[len - 1] != '\n') {
3666 if (str[len] == '\r') str[len] = '\n';
3669 file_contents = g_string_append(file_contents, str);
3673 if (result == COMPOSE_INSERT_SUCCESS) {
3674 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3676 compose_changed_cb(NULL, compose);
3677 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3678 G_CALLBACK(text_inserted),
3680 compose->autowrap = prev_autowrap;
3681 if (compose->autowrap)
3682 compose_wrap_all(compose);
3685 g_string_free(file_contents, TRUE);
3691 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3692 const gchar *filename,
3693 const gchar *content_type,
3694 const gchar *charset)
3702 GtkListStore *store;
3704 gboolean has_binary = FALSE;
3706 if (!is_file_exist(file)) {
3707 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3708 gboolean result = FALSE;
3709 if (file_from_uri && is_file_exist(file_from_uri)) {
3710 result = compose_attach_append(
3711 compose, file_from_uri,
3712 filename, content_type,
3715 g_free(file_from_uri);
3718 alertpanel_error("File %s doesn't exist\n", filename);
3721 if ((size = get_file_size(file)) < 0) {
3722 alertpanel_error("Can't get file size of %s\n", filename);
3726 /* In batch mode, we allow 0-length files to be attached no questions asked */
3727 if (size == 0 && !compose->batch) {
3728 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3729 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3730 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3731 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3734 if (aval != G_ALERTALTERNATE) {
3738 if ((fp = g_fopen(file, "rb")) == NULL) {
3739 alertpanel_error(_("Can't read %s."), filename);
3744 ainfo = g_new0(AttachInfo, 1);
3745 auto_ainfo = g_auto_pointer_new_with_free
3746 (ainfo, (GFreeFunc) compose_attach_info_free);
3747 ainfo->file = g_strdup(file);
3750 ainfo->content_type = g_strdup(content_type);
3751 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3753 MsgFlags flags = {0, 0};
3755 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3756 ainfo->encoding = ENC_7BIT;
3758 ainfo->encoding = ENC_8BIT;
3760 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3761 if (msginfo && msginfo->subject)
3762 name = g_strdup(msginfo->subject);
3764 name = g_path_get_basename(filename ? filename : file);
3766 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3768 procmsg_msginfo_free(msginfo);
3770 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3771 ainfo->charset = g_strdup(charset);
3772 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3774 ainfo->encoding = ENC_BASE64;
3776 name = g_path_get_basename(filename ? filename : file);
3777 ainfo->name = g_strdup(name);
3781 ainfo->content_type = procmime_get_mime_type(file);
3782 if (!ainfo->content_type) {
3783 ainfo->content_type =
3784 g_strdup("application/octet-stream");
3785 ainfo->encoding = ENC_BASE64;
3786 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3788 procmime_get_encoding_for_text_file(file, &has_binary);
3790 ainfo->encoding = ENC_BASE64;
3791 name = g_path_get_basename(filename ? filename : file);
3792 ainfo->name = g_strdup(name);
3796 if (ainfo->name != NULL
3797 && !strcmp(ainfo->name, ".")) {
3798 g_free(ainfo->name);
3802 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3803 g_free(ainfo->content_type);
3804 ainfo->content_type = g_strdup("application/octet-stream");
3805 g_free(ainfo->charset);
3806 ainfo->charset = NULL;
3809 ainfo->size = (goffset)size;
3810 size_text = to_human_readable((goffset)size);
3812 store = GTK_LIST_STORE(gtk_tree_view_get_model
3813 (GTK_TREE_VIEW(compose->attach_clist)));
3815 gtk_list_store_append(store, &iter);
3816 gtk_list_store_set(store, &iter,
3817 COL_MIMETYPE, ainfo->content_type,
3818 COL_SIZE, size_text,
3819 COL_NAME, ainfo->name,
3820 COL_CHARSET, ainfo->charset,
3822 COL_AUTODATA, auto_ainfo,
3825 g_auto_pointer_free(auto_ainfo);
3826 compose_attach_update_label(compose);
3830 static void compose_use_signing(Compose *compose, gboolean use_signing)
3832 compose->use_signing = use_signing;
3833 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3836 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3838 compose->use_encryption = use_encryption;
3839 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3842 #define NEXT_PART_NOT_CHILD(info) \
3844 node = info->node; \
3845 while (node->children) \
3846 node = g_node_last_child(node); \
3847 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3850 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3854 MimeInfo *firsttext = NULL;
3855 MimeInfo *encrypted = NULL;
3858 const gchar *partname = NULL;
3860 mimeinfo = procmime_scan_message(msginfo);
3861 if (!mimeinfo) return;
3863 if (mimeinfo->node->children == NULL) {
3864 procmime_mimeinfo_free_all(mimeinfo);
3868 /* find first content part */
3869 child = (MimeInfo *) mimeinfo->node->children->data;
3870 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3871 child = (MimeInfo *)child->node->children->data;
3874 if (child->type == MIMETYPE_TEXT) {
3876 debug_print("First text part found\n");
3877 } else if (compose->mode == COMPOSE_REEDIT &&
3878 child->type == MIMETYPE_APPLICATION &&
3879 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3880 encrypted = (MimeInfo *)child->node->parent->data;
3883 child = (MimeInfo *) mimeinfo->node->children->data;
3884 while (child != NULL) {
3887 if (child == encrypted) {
3888 /* skip this part of tree */
3889 NEXT_PART_NOT_CHILD(child);
3893 if (child->type == MIMETYPE_MULTIPART) {
3894 /* get the actual content */
3895 child = procmime_mimeinfo_next(child);
3899 if (child == firsttext) {
3900 child = procmime_mimeinfo_next(child);
3904 outfile = procmime_get_tmp_file_name(child);
3905 if ((err = procmime_get_part(outfile, child)) < 0)
3906 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3908 gchar *content_type;
3910 content_type = procmime_get_content_type_str(child->type, child->subtype);
3912 /* if we meet a pgp signature, we don't attach it, but
3913 * we force signing. */
3914 if ((strcmp(content_type, "application/pgp-signature") &&
3915 strcmp(content_type, "application/pkcs7-signature") &&
3916 strcmp(content_type, "application/x-pkcs7-signature"))
3917 || compose->mode == COMPOSE_REDIRECT) {
3918 partname = procmime_mimeinfo_get_parameter(child, "filename");
3919 if (partname == NULL)
3920 partname = procmime_mimeinfo_get_parameter(child, "name");
3921 if (partname == NULL)
3923 compose_attach_append(compose, outfile,
3924 partname, content_type,
3925 procmime_mimeinfo_get_parameter(child, "charset"));
3927 compose_force_signing(compose, compose->account, NULL);
3929 g_free(content_type);
3932 NEXT_PART_NOT_CHILD(child);
3934 procmime_mimeinfo_free_all(mimeinfo);
3937 #undef NEXT_PART_NOT_CHILD
3942 WAIT_FOR_INDENT_CHAR,
3943 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3946 /* return indent length, we allow:
3947 indent characters followed by indent characters or spaces/tabs,
3948 alphabets and numbers immediately followed by indent characters,
3949 and the repeating sequences of the above
3950 If quote ends with multiple spaces, only the first one is included. */
3951 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3952 const GtkTextIter *start, gint *len)
3954 GtkTextIter iter = *start;
3958 IndentState state = WAIT_FOR_INDENT_CHAR;
3961 gint alnum_count = 0;
3962 gint space_count = 0;
3965 if (prefs_common.quote_chars == NULL) {
3969 while (!gtk_text_iter_ends_line(&iter)) {
3970 wc = gtk_text_iter_get_char(&iter);
3971 if (g_unichar_iswide(wc))
3973 clen = g_unichar_to_utf8(wc, ch);
3977 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3978 is_space = g_unichar_isspace(wc);
3980 if (state == WAIT_FOR_INDENT_CHAR) {
3981 if (!is_indent && !g_unichar_isalnum(wc))
3984 quote_len += alnum_count + space_count + 1;
3985 alnum_count = space_count = 0;
3986 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3989 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3990 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3994 else if (is_indent) {
3995 quote_len += alnum_count + space_count + 1;
3996 alnum_count = space_count = 0;
3999 state = WAIT_FOR_INDENT_CHAR;
4003 gtk_text_iter_forward_char(&iter);
4006 if (quote_len > 0 && space_count > 0)
4012 if (quote_len > 0) {
4014 gtk_text_iter_forward_chars(&iter, quote_len);
4015 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4021 /* return >0 if the line is itemized */
4022 static int compose_itemized_length(GtkTextBuffer *buffer,
4023 const GtkTextIter *start)
4025 GtkTextIter iter = *start;
4030 if (gtk_text_iter_ends_line(&iter))
4035 wc = gtk_text_iter_get_char(&iter);
4036 if (!g_unichar_isspace(wc))
4038 gtk_text_iter_forward_char(&iter);
4039 if (gtk_text_iter_ends_line(&iter))
4043 clen = g_unichar_to_utf8(wc, ch);
4047 if (!strchr("*-+", ch[0]))
4050 gtk_text_iter_forward_char(&iter);
4051 if (gtk_text_iter_ends_line(&iter))
4053 wc = gtk_text_iter_get_char(&iter);
4054 if (g_unichar_isspace(wc)) {
4060 /* return the string at the start of the itemization */
4061 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4062 const GtkTextIter *start)
4064 GtkTextIter iter = *start;
4067 GString *item_chars = g_string_new("");
4070 if (gtk_text_iter_ends_line(&iter))
4075 wc = gtk_text_iter_get_char(&iter);
4076 if (!g_unichar_isspace(wc))
4078 gtk_text_iter_forward_char(&iter);
4079 if (gtk_text_iter_ends_line(&iter))
4081 g_string_append_unichar(item_chars, wc);
4084 str = item_chars->str;
4085 g_string_free(item_chars, FALSE);
4089 /* return the number of spaces at a line's start */
4090 static int compose_left_offset_length(GtkTextBuffer *buffer,
4091 const GtkTextIter *start)
4093 GtkTextIter iter = *start;
4096 if (gtk_text_iter_ends_line(&iter))
4100 wc = gtk_text_iter_get_char(&iter);
4101 if (!g_unichar_isspace(wc))
4104 gtk_text_iter_forward_char(&iter);
4105 if (gtk_text_iter_ends_line(&iter))
4109 gtk_text_iter_forward_char(&iter);
4110 if (gtk_text_iter_ends_line(&iter))
4115 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4116 const GtkTextIter *start,
4117 GtkTextIter *break_pos,
4121 GtkTextIter iter = *start, line_end = *start;
4122 PangoLogAttr *attrs;
4129 gboolean can_break = FALSE;
4130 gboolean do_break = FALSE;
4131 gboolean was_white = FALSE;
4132 gboolean prev_dont_break = FALSE;
4134 gtk_text_iter_forward_to_line_end(&line_end);
4135 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4136 len = g_utf8_strlen(str, -1);
4140 g_warning("compose_get_line_break_pos: len = 0!\n");
4144 /* g_print("breaking line: %d: %s (len = %d)\n",
4145 gtk_text_iter_get_line(&iter), str, len); */
4147 attrs = g_new(PangoLogAttr, len + 1);
4149 pango_default_break(str, -1, NULL, attrs, len + 1);
4153 /* skip quote and leading spaces */
4154 for (i = 0; *p != '\0' && i < len; i++) {
4157 wc = g_utf8_get_char(p);
4158 if (i >= quote_len && !g_unichar_isspace(wc))
4160 if (g_unichar_iswide(wc))
4162 else if (*p == '\t')
4166 p = g_utf8_next_char(p);
4169 for (; *p != '\0' && i < len; i++) {
4170 PangoLogAttr *attr = attrs + i;
4174 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4177 was_white = attr->is_white;
4179 /* don't wrap URI */
4180 if ((uri_len = get_uri_len(p)) > 0) {
4182 if (pos > 0 && col > max_col) {
4192 wc = g_utf8_get_char(p);
4193 if (g_unichar_iswide(wc)) {
4195 if (prev_dont_break && can_break && attr->is_line_break)
4197 } else if (*p == '\t')
4201 if (pos > 0 && col > max_col) {
4206 if (*p == '-' || *p == '/')
4207 prev_dont_break = TRUE;
4209 prev_dont_break = FALSE;
4211 p = g_utf8_next_char(p);
4215 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4220 *break_pos = *start;
4221 gtk_text_iter_set_line_offset(break_pos, pos);
4226 static gboolean compose_join_next_line(Compose *compose,
4227 GtkTextBuffer *buffer,
4229 const gchar *quote_str)
4231 GtkTextIter iter_ = *iter, cur, prev, next, end;
4232 PangoLogAttr attrs[3];
4234 gchar *next_quote_str;
4237 gboolean keep_cursor = FALSE;
4239 if (!gtk_text_iter_forward_line(&iter_) ||
4240 gtk_text_iter_ends_line(&iter_)) {
4243 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4245 if ((quote_str || next_quote_str) &&
4246 strcmp2(quote_str, next_quote_str) != 0) {
4247 g_free(next_quote_str);
4250 g_free(next_quote_str);
4253 if (quote_len > 0) {
4254 gtk_text_iter_forward_chars(&end, quote_len);
4255 if (gtk_text_iter_ends_line(&end)) {
4260 /* don't join itemized lines */
4261 if (compose_itemized_length(buffer, &end) > 0) {
4265 /* don't join signature separator */
4266 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4269 /* delete quote str */
4271 gtk_text_buffer_delete(buffer, &iter_, &end);
4273 /* don't join line breaks put by the user */
4275 gtk_text_iter_backward_char(&cur);
4276 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4277 gtk_text_iter_forward_char(&cur);
4281 gtk_text_iter_forward_char(&cur);
4282 /* delete linebreak and extra spaces */
4283 while (gtk_text_iter_backward_char(&cur)) {
4284 wc1 = gtk_text_iter_get_char(&cur);
4285 if (!g_unichar_isspace(wc1))
4290 while (!gtk_text_iter_ends_line(&cur)) {
4291 wc1 = gtk_text_iter_get_char(&cur);
4292 if (!g_unichar_isspace(wc1))
4294 gtk_text_iter_forward_char(&cur);
4297 if (!gtk_text_iter_equal(&prev, &next)) {
4300 mark = gtk_text_buffer_get_insert(buffer);
4301 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4302 if (gtk_text_iter_equal(&prev, &cur))
4304 gtk_text_buffer_delete(buffer, &prev, &next);
4308 /* insert space if required */
4309 gtk_text_iter_backward_char(&prev);
4310 wc1 = gtk_text_iter_get_char(&prev);
4311 wc2 = gtk_text_iter_get_char(&next);
4312 gtk_text_iter_forward_char(&next);
4313 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4314 pango_default_break(str, -1, NULL, attrs, 3);
4315 if (!attrs[1].is_line_break ||
4316 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4317 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4319 gtk_text_iter_backward_char(&iter_);
4320 gtk_text_buffer_place_cursor(buffer, &iter_);
4329 #define ADD_TXT_POS(bp_, ep_, pti_) \
4330 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4331 last = last->next; \
4332 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4333 last->next = NULL; \
4335 g_warning("alloc error scanning URIs\n"); \
4338 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4340 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4341 GtkTextBuffer *buffer;
4342 GtkTextIter iter, break_pos, end_of_line;
4343 gchar *quote_str = NULL;
4345 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4346 gboolean prev_autowrap = compose->autowrap;
4347 gint startq_offset = -1, noq_offset = -1;
4348 gint uri_start = -1, uri_stop = -1;
4349 gint nouri_start = -1, nouri_stop = -1;
4350 gint num_blocks = 0;
4351 gint quotelevel = -1;
4352 gboolean modified = force;
4353 gboolean removed = FALSE;
4354 gboolean modified_before_remove = FALSE;
4356 gboolean start = TRUE;
4357 gint itemized_len = 0, rem_item_len = 0;
4358 gchar *itemized_chars = NULL;
4359 gboolean item_continuation = FALSE;
4364 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4368 compose->autowrap = FALSE;
4370 buffer = gtk_text_view_get_buffer(text);
4371 undo_wrapping(compose->undostruct, TRUE);
4376 mark = gtk_text_buffer_get_insert(buffer);
4377 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4381 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4382 if (gtk_text_iter_ends_line(&iter)) {
4383 while (gtk_text_iter_ends_line(&iter) &&
4384 gtk_text_iter_forward_line(&iter))
4387 while (gtk_text_iter_backward_line(&iter)) {
4388 if (gtk_text_iter_ends_line(&iter)) {
4389 gtk_text_iter_forward_line(&iter);
4395 /* move to line start */
4396 gtk_text_iter_set_line_offset(&iter, 0);
4399 itemized_len = compose_itemized_length(buffer, &iter);
4401 if (!itemized_len) {
4402 itemized_len = compose_left_offset_length(buffer, &iter);
4403 item_continuation = TRUE;
4407 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4409 /* go until paragraph end (empty line) */
4410 while (start || !gtk_text_iter_ends_line(&iter)) {
4411 gchar *scanpos = NULL;
4412 /* parse table - in order of priority */
4414 const gchar *needle; /* token */
4416 /* token search function */
4417 gchar *(*search) (const gchar *haystack,
4418 const gchar *needle);
4419 /* part parsing function */
4420 gboolean (*parse) (const gchar *start,
4421 const gchar *scanpos,
4425 /* part to URI function */
4426 gchar *(*build_uri) (const gchar *bp,
4430 static struct table parser[] = {
4431 {"http://", strcasestr, get_uri_part, make_uri_string},
4432 {"https://", strcasestr, get_uri_part, make_uri_string},
4433 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4434 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4435 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4436 {"www.", strcasestr, get_uri_part, make_http_string},
4437 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4438 {"@", strcasestr, get_email_part, make_email_string}
4440 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4441 gint last_index = PARSE_ELEMS;
4443 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4447 if (!prev_autowrap && num_blocks == 0) {
4449 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4450 G_CALLBACK(text_inserted),
4453 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4456 uri_start = uri_stop = -1;
4458 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4461 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4462 if (startq_offset == -1)
4463 startq_offset = gtk_text_iter_get_offset(&iter);
4464 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4465 if (quotelevel > 2) {
4466 /* recycle colors */
4467 if (prefs_common.recycle_quote_colors)
4476 if (startq_offset == -1)
4477 noq_offset = gtk_text_iter_get_offset(&iter);
4481 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4484 if (gtk_text_iter_ends_line(&iter)) {
4486 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4487 prefs_common.linewrap_len,
4489 GtkTextIter prev, next, cur;
4490 if (prev_autowrap != FALSE || force) {
4491 compose->automatic_break = TRUE;
4493 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4494 compose->automatic_break = FALSE;
4495 if (itemized_len && compose->autoindent) {
4496 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4497 if (!item_continuation)
4498 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4500 } else if (quote_str && wrap_quote) {
4501 compose->automatic_break = TRUE;
4503 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4504 compose->automatic_break = FALSE;
4505 if (itemized_len && compose->autoindent) {
4506 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4507 if (!item_continuation)
4508 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4512 /* remove trailing spaces */
4514 rem_item_len = itemized_len;
4515 while (compose->autoindent && rem_item_len-- > 0)
4516 gtk_text_iter_backward_char(&cur);
4517 gtk_text_iter_backward_char(&cur);
4520 while (!gtk_text_iter_starts_line(&cur)) {
4523 gtk_text_iter_backward_char(&cur);
4524 wc = gtk_text_iter_get_char(&cur);
4525 if (!g_unichar_isspace(wc))
4529 if (!gtk_text_iter_equal(&prev, &next)) {
4530 gtk_text_buffer_delete(buffer, &prev, &next);
4532 gtk_text_iter_forward_char(&break_pos);
4536 gtk_text_buffer_insert(buffer, &break_pos,
4540 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4542 /* move iter to current line start */
4543 gtk_text_iter_set_line_offset(&iter, 0);
4550 /* move iter to next line start */
4556 if (!prev_autowrap && num_blocks > 0) {
4558 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4559 G_CALLBACK(text_inserted),
4563 while (!gtk_text_iter_ends_line(&end_of_line)) {
4564 gtk_text_iter_forward_char(&end_of_line);
4566 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4568 nouri_start = gtk_text_iter_get_offset(&iter);
4569 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4571 walk_pos = gtk_text_iter_get_offset(&iter);
4572 /* FIXME: this looks phony. scanning for anything in the parse table */
4573 for (n = 0; n < PARSE_ELEMS; n++) {
4576 tmp = parser[n].search(walk, parser[n].needle);
4578 if (scanpos == NULL || tmp < scanpos) {
4587 /* check if URI can be parsed */
4588 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4589 (const gchar **)&ep, FALSE)
4590 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4594 strlen(parser[last_index].needle);
4597 uri_start = walk_pos + (bp - o_walk);
4598 uri_stop = walk_pos + (ep - o_walk);
4602 gtk_text_iter_forward_line(&iter);
4605 if (startq_offset != -1) {
4606 GtkTextIter startquote, endquote;
4607 gtk_text_buffer_get_iter_at_offset(
4608 buffer, &startquote, startq_offset);
4611 switch (quotelevel) {
4613 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4614 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4615 gtk_text_buffer_apply_tag_by_name(
4616 buffer, "quote0", &startquote, &endquote);
4617 gtk_text_buffer_remove_tag_by_name(
4618 buffer, "quote1", &startquote, &endquote);
4619 gtk_text_buffer_remove_tag_by_name(
4620 buffer, "quote2", &startquote, &endquote);
4625 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4626 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4627 gtk_text_buffer_apply_tag_by_name(
4628 buffer, "quote1", &startquote, &endquote);
4629 gtk_text_buffer_remove_tag_by_name(
4630 buffer, "quote0", &startquote, &endquote);
4631 gtk_text_buffer_remove_tag_by_name(
4632 buffer, "quote2", &startquote, &endquote);
4637 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4638 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4639 gtk_text_buffer_apply_tag_by_name(
4640 buffer, "quote2", &startquote, &endquote);
4641 gtk_text_buffer_remove_tag_by_name(
4642 buffer, "quote0", &startquote, &endquote);
4643 gtk_text_buffer_remove_tag_by_name(
4644 buffer, "quote1", &startquote, &endquote);
4650 } else if (noq_offset != -1) {
4651 GtkTextIter startnoquote, endnoquote;
4652 gtk_text_buffer_get_iter_at_offset(
4653 buffer, &startnoquote, noq_offset);
4656 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4657 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4658 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4659 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4660 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4661 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4662 gtk_text_buffer_remove_tag_by_name(
4663 buffer, "quote0", &startnoquote, &endnoquote);
4664 gtk_text_buffer_remove_tag_by_name(
4665 buffer, "quote1", &startnoquote, &endnoquote);
4666 gtk_text_buffer_remove_tag_by_name(
4667 buffer, "quote2", &startnoquote, &endnoquote);
4673 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4674 GtkTextIter nouri_start_iter, nouri_end_iter;
4675 gtk_text_buffer_get_iter_at_offset(
4676 buffer, &nouri_start_iter, nouri_start);
4677 gtk_text_buffer_get_iter_at_offset(
4678 buffer, &nouri_end_iter, nouri_stop);
4679 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4680 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4681 gtk_text_buffer_remove_tag_by_name(
4682 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4683 modified_before_remove = modified;
4688 if (uri_start >= 0 && uri_stop > 0) {
4689 GtkTextIter uri_start_iter, uri_end_iter, back;
4690 gtk_text_buffer_get_iter_at_offset(
4691 buffer, &uri_start_iter, uri_start);
4692 gtk_text_buffer_get_iter_at_offset(
4693 buffer, &uri_end_iter, uri_stop);
4694 back = uri_end_iter;
4695 gtk_text_iter_backward_char(&back);
4696 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4697 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4698 gtk_text_buffer_apply_tag_by_name(
4699 buffer, "link", &uri_start_iter, &uri_end_iter);
4701 if (removed && !modified_before_remove) {
4707 // debug_print("not modified, out after %d lines\n", lines);
4711 // debug_print("modified, out after %d lines\n", lines);
4713 g_free(itemized_chars);
4716 undo_wrapping(compose->undostruct, FALSE);
4717 compose->autowrap = prev_autowrap;
4722 void compose_action_cb(void *data)
4724 Compose *compose = (Compose *)data;
4725 compose_wrap_all(compose);
4728 static void compose_wrap_all(Compose *compose)
4730 compose_wrap_all_full(compose, FALSE);
4733 static void compose_wrap_all_full(Compose *compose, gboolean force)
4735 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4736 GtkTextBuffer *buffer;
4738 gboolean modified = TRUE;
4740 buffer = gtk_text_view_get_buffer(text);
4742 gtk_text_buffer_get_start_iter(buffer, &iter);
4744 undo_wrapping(compose->undostruct, TRUE);
4746 while (!gtk_text_iter_is_end(&iter) && modified)
4747 modified = compose_beautify_paragraph(compose, &iter, force);
4749 undo_wrapping(compose->undostruct, FALSE);
4753 static void compose_set_title(Compose *compose)
4759 edited = compose->modified ? _(" [Edited]") : "";
4761 subject = gtk_editable_get_chars(
4762 GTK_EDITABLE(compose->subject_entry), 0, -1);
4764 #ifndef GENERIC_UMPC
4765 if (subject && strlen(subject))
4766 str = g_strdup_printf(_("%s - Compose message%s"),
4769 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4771 str = g_strdup(_("Compose message"));
4774 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4780 * compose_current_mail_account:
4782 * Find a current mail account (the currently selected account, or the
4783 * default account, if a news account is currently selected). If a
4784 * mail account cannot be found, display an error message.
4786 * Return value: Mail account, or NULL if not found.
4788 static PrefsAccount *
4789 compose_current_mail_account(void)
4793 if (cur_account && cur_account->protocol != A_NNTP)
4796 ac = account_get_default();
4797 if (!ac || ac->protocol == A_NNTP) {
4798 alertpanel_error(_("Account for sending mail is not specified.\n"
4799 "Please select a mail account before sending."));
4806 #define QUOTE_IF_REQUIRED(out, str) \
4808 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4812 len = strlen(str) + 3; \
4813 if ((__tmp = alloca(len)) == NULL) { \
4814 g_warning("can't allocate memory\n"); \
4815 g_string_free(header, TRUE); \
4818 g_snprintf(__tmp, len, "\"%s\"", str); \
4823 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4824 g_warning("can't allocate memory\n"); \
4825 g_string_free(header, TRUE); \
4828 strcpy(__tmp, str); \
4834 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4836 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4840 len = strlen(str) + 3; \
4841 if ((__tmp = alloca(len)) == NULL) { \
4842 g_warning("can't allocate memory\n"); \
4845 g_snprintf(__tmp, len, "\"%s\"", str); \
4850 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4851 g_warning("can't allocate memory\n"); \
4854 strcpy(__tmp, str); \
4860 static void compose_select_account(Compose *compose, PrefsAccount *account,
4863 gchar *from = NULL, *header = NULL;
4864 ComposeHeaderEntry *header_entry;
4865 #if GTK_CHECK_VERSION(2, 24, 0)
4869 cm_return_if_fail(account != NULL);
4871 compose->account = account;
4872 if (account->name && *account->name) {
4874 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4875 qbuf = escape_internal_quotes(buf, '"');
4876 from = g_strdup_printf("%s <%s>",
4877 qbuf, account->address);
4880 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4882 from = g_strdup_printf("<%s>",
4884 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4889 compose_set_title(compose);
4891 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4892 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4894 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4895 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4896 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4898 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4900 activate_privacy_system(compose, account, FALSE);
4902 if (!init && compose->mode != COMPOSE_REDIRECT) {
4903 undo_block(compose->undostruct);
4904 compose_insert_sig(compose, TRUE);
4905 undo_unblock(compose->undostruct);
4908 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4909 #if !GTK_CHECK_VERSION(2, 24, 0)
4910 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4912 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4913 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4914 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4917 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4918 if (account->protocol == A_NNTP) {
4919 if (!strcmp(header, _("To:")))
4920 combobox_select_by_text(
4921 GTK_COMBO_BOX(header_entry->combo),
4924 if (!strcmp(header, _("Newsgroups:")))
4925 combobox_select_by_text(
4926 GTK_COMBO_BOX(header_entry->combo),
4934 /* use account's dict info if set */
4935 if (compose->gtkaspell) {
4936 if (account->enable_default_dictionary)
4937 gtkaspell_change_dict(compose->gtkaspell,
4938 account->default_dictionary, FALSE);
4939 if (account->enable_default_alt_dictionary)
4940 gtkaspell_change_alt_dict(compose->gtkaspell,
4941 account->default_alt_dictionary);
4942 if (account->enable_default_dictionary
4943 || account->enable_default_alt_dictionary)
4944 compose_spell_menu_changed(compose);
4949 gboolean compose_check_for_valid_recipient(Compose *compose) {
4950 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4951 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4952 gboolean recipient_found = FALSE;
4956 /* free to and newsgroup list */
4957 slist_free_strings_full(compose->to_list);
4958 compose->to_list = NULL;
4960 slist_free_strings_full(compose->newsgroup_list);
4961 compose->newsgroup_list = NULL;
4963 /* search header entries for to and newsgroup entries */
4964 for (list = compose->header_list; list; list = list->next) {
4967 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4968 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4971 if (entry[0] != '\0') {
4972 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4973 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4974 compose->to_list = address_list_append(compose->to_list, entry);
4975 recipient_found = TRUE;
4978 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4979 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4980 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4981 recipient_found = TRUE;
4988 return recipient_found;
4991 static gboolean compose_check_for_set_recipients(Compose *compose)
4993 if (compose->account->set_autocc && compose->account->auto_cc) {
4994 gboolean found_other = FALSE;
4996 /* search header entries for to and newsgroup entries */
4997 for (list = compose->header_list; list; list = list->next) {
5000 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5001 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5004 if (strcmp(entry, compose->account->auto_cc)
5005 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5015 if (compose->batch) {
5016 gtk_widget_show_all(compose->window);
5018 aval = alertpanel(_("Send"),
5019 _("The only recipient is the default CC address. Send anyway?"),
5020 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5021 if (aval != G_ALERTALTERNATE)
5025 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5026 gboolean found_other = FALSE;
5028 /* search header entries for to and newsgroup entries */
5029 for (list = compose->header_list; list; list = list->next) {
5032 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5033 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5036 if (strcmp(entry, compose->account->auto_bcc)
5037 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5047 if (compose->batch) {
5048 gtk_widget_show_all(compose->window);
5050 aval = alertpanel(_("Send"),
5051 _("The only recipient is the default BCC address. Send anyway?"),
5052 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5053 if (aval != G_ALERTALTERNATE)
5060 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5064 if (compose_check_for_valid_recipient(compose) == FALSE) {
5065 if (compose->batch) {
5066 gtk_widget_show_all(compose->window);
5068 alertpanel_error(_("Recipient is not specified."));
5072 if (compose_check_for_set_recipients(compose) == FALSE) {
5076 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5077 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5078 if (*str == '\0' && check_everything == TRUE &&
5079 compose->mode != COMPOSE_REDIRECT) {
5081 gchar *button_label;
5084 if (compose->sending)
5085 button_label = _("+_Send");
5087 button_label = _("+_Queue");
5088 message = g_strdup_printf(_("Subject is empty. %s"),
5089 compose->sending?_("Send it anyway?"):
5090 _("Queue it anyway?"));
5092 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5093 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5094 ALERT_QUESTION, G_ALERTDEFAULT);
5096 if (aval & G_ALERTDISABLE) {
5097 aval &= ~G_ALERTDISABLE;
5098 prefs_common.warn_empty_subj = FALSE;
5100 if (aval != G_ALERTALTERNATE)
5105 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5111 gint compose_send(Compose *compose)
5114 FolderItem *folder = NULL;
5116 gchar *msgpath = NULL;
5117 gboolean discard_window = FALSE;
5118 gchar *errstr = NULL;
5119 gchar *tmsgid = NULL;
5120 MainWindow *mainwin = mainwindow_get_mainwindow();
5121 gboolean queued_removed = FALSE;
5123 if (prefs_common.send_dialog_invisible
5124 || compose->batch == TRUE)
5125 discard_window = TRUE;
5127 compose_allow_user_actions (compose, FALSE);
5128 compose->sending = TRUE;
5130 if (compose_check_entries(compose, TRUE) == FALSE) {
5131 if (compose->batch) {
5132 gtk_widget_show_all(compose->window);
5138 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5141 if (compose->batch) {
5142 gtk_widget_show_all(compose->window);
5145 alertpanel_error(_("Could not queue message for sending:\n\n"
5146 "Charset conversion failed."));
5147 } else if (val == -5) {
5148 alertpanel_error(_("Could not queue message for sending:\n\n"
5149 "Couldn't get recipient encryption key."));
5150 } else if (val == -6) {
5152 } else if (val == -3) {
5153 if (privacy_peek_error())
5154 alertpanel_error(_("Could not queue message for sending:\n\n"
5155 "Signature failed: %s"), privacy_get_error());
5156 } else if (val == -2 && errno != 0) {
5157 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5159 alertpanel_error(_("Could not queue message for sending."));
5164 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5165 if (discard_window) {
5166 compose->sending = FALSE;
5167 compose_close(compose);
5168 /* No more compose access in the normal codepath
5169 * after this point! */
5174 alertpanel_error(_("The message was queued but could not be "
5175 "sent.\nUse \"Send queued messages\" from "
5176 "the main window to retry."));
5177 if (!discard_window) {
5184 if (msgpath == NULL) {
5185 msgpath = folder_item_fetch_msg(folder, msgnum);
5186 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5189 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5190 claws_unlink(msgpath);
5193 if (!discard_window) {
5195 if (!queued_removed)
5196 folder_item_remove_msg(folder, msgnum);
5197 folder_item_scan(folder);
5199 /* make sure we delete that */
5200 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5202 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5203 folder_item_remove_msg(folder, tmp->msgnum);
5204 procmsg_msginfo_free(tmp);
5211 if (!queued_removed)
5212 folder_item_remove_msg(folder, msgnum);
5213 folder_item_scan(folder);
5215 /* make sure we delete that */
5216 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5218 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5219 folder_item_remove_msg(folder, tmp->msgnum);
5220 procmsg_msginfo_free(tmp);
5223 if (!discard_window) {
5224 compose->sending = FALSE;
5225 compose_allow_user_actions (compose, TRUE);
5226 compose_close(compose);
5230 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5231 "the main window to retry."), errstr);
5234 alertpanel_error_log(_("The message was queued but could not be "
5235 "sent.\nUse \"Send queued messages\" from "
5236 "the main window to retry."));
5238 if (!discard_window) {
5247 toolbar_main_set_sensitive(mainwin);
5248 main_window_set_menu_sensitive(mainwin);
5254 compose_allow_user_actions (compose, TRUE);
5255 compose->sending = FALSE;
5256 compose->modified = TRUE;
5257 toolbar_main_set_sensitive(mainwin);
5258 main_window_set_menu_sensitive(mainwin);
5263 static gboolean compose_use_attach(Compose *compose)
5265 GtkTreeModel *model = gtk_tree_view_get_model
5266 (GTK_TREE_VIEW(compose->attach_clist));
5267 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5270 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5273 gchar buf[BUFFSIZE];
5275 gboolean first_to_address;
5276 gboolean first_cc_address;
5278 ComposeHeaderEntry *headerentry;
5279 const gchar *headerentryname;
5280 const gchar *cc_hdr;
5281 const gchar *to_hdr;
5282 gboolean err = FALSE;
5284 debug_print("Writing redirect header\n");
5286 cc_hdr = prefs_common_translated_header_name("Cc:");
5287 to_hdr = prefs_common_translated_header_name("To:");
5289 first_to_address = TRUE;
5290 for (list = compose->header_list; list; list = list->next) {
5291 headerentry = ((ComposeHeaderEntry *)list->data);
5292 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5294 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5295 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5296 Xstrdup_a(str, entstr, return -1);
5298 if (str[0] != '\0') {
5299 compose_convert_header
5300 (compose, buf, sizeof(buf), str,
5301 strlen("Resent-To") + 2, TRUE);
5303 if (first_to_address) {
5304 err |= (fprintf(fp, "Resent-To: ") < 0);
5305 first_to_address = FALSE;
5307 err |= (fprintf(fp, ",") < 0);
5309 err |= (fprintf(fp, "%s", buf) < 0);
5313 if (!first_to_address) {
5314 err |= (fprintf(fp, "\n") < 0);
5317 first_cc_address = TRUE;
5318 for (list = compose->header_list; list; list = list->next) {
5319 headerentry = ((ComposeHeaderEntry *)list->data);
5320 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5322 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5323 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5324 Xstrdup_a(str, strg, return -1);
5326 if (str[0] != '\0') {
5327 compose_convert_header
5328 (compose, buf, sizeof(buf), str,
5329 strlen("Resent-Cc") + 2, TRUE);
5331 if (first_cc_address) {
5332 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5333 first_cc_address = FALSE;
5335 err |= (fprintf(fp, ",") < 0);
5337 err |= (fprintf(fp, "%s", buf) < 0);
5341 if (!first_cc_address) {
5342 err |= (fprintf(fp, "\n") < 0);
5345 return (err ? -1:0);
5348 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5350 gchar buf[BUFFSIZE];
5352 const gchar *entstr;
5353 /* struct utsname utsbuf; */
5354 gboolean err = FALSE;
5356 cm_return_val_if_fail(fp != NULL, -1);
5357 cm_return_val_if_fail(compose->account != NULL, -1);
5358 cm_return_val_if_fail(compose->account->address != NULL, -1);
5361 get_rfc822_date(buf, sizeof(buf));
5362 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5365 if (compose->account->name && *compose->account->name) {
5366 compose_convert_header
5367 (compose, buf, sizeof(buf), compose->account->name,
5368 strlen("From: "), TRUE);
5369 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5370 buf, compose->account->address) < 0);
5372 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5375 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5376 if (*entstr != '\0') {
5377 Xstrdup_a(str, entstr, return -1);
5380 compose_convert_header(compose, buf, sizeof(buf), str,
5381 strlen("Subject: "), FALSE);
5382 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5386 /* Resent-Message-ID */
5387 if (compose->account->set_domain && compose->account->domain) {
5388 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5389 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5390 g_snprintf(buf, sizeof(buf), "%s",
5391 strchr(compose->account->address, '@') ?
5392 strchr(compose->account->address, '@')+1 :
5393 compose->account->address);
5395 g_snprintf(buf, sizeof(buf), "%s", "");
5398 if (compose->account->gen_msgid) {
5400 if (compose->account->msgid_with_addr) {
5401 addr = compose->account->address;
5403 generate_msgid(buf, sizeof(buf), addr);
5404 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5406 g_free(compose->msgid);
5407 compose->msgid = g_strdup(buf);
5409 compose->msgid = NULL;
5412 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5415 /* separator between header and body */
5416 err |= (fputs("\n", fp) == EOF);
5418 return (err ? -1:0);
5421 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5425 gchar buf[BUFFSIZE];
5427 gboolean skip = FALSE;
5428 gboolean err = FALSE;
5429 gchar *not_included[]={
5430 "Return-Path:", "Delivered-To:", "Received:",
5431 "Subject:", "X-UIDL:", "AF:",
5432 "NF:", "PS:", "SRH:",
5433 "SFN:", "DSR:", "MID:",
5434 "CFG:", "PT:", "S:",
5435 "RQ:", "SSV:", "NSV:",
5436 "SSH:", "R:", "MAID:",
5437 "NAID:", "RMID:", "FMID:",
5438 "SCF:", "RRCPT:", "NG:",
5439 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5440 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5441 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5442 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5443 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5446 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5447 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5451 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5453 for (i = 0; not_included[i] != NULL; i++) {
5454 if (g_ascii_strncasecmp(buf, not_included[i],
5455 strlen(not_included[i])) == 0) {
5462 if (fputs(buf, fdest) == -1)
5465 if (!prefs_common.redirect_keep_from) {
5466 if (g_ascii_strncasecmp(buf, "From:",
5467 strlen("From:")) == 0) {
5468 err |= (fputs(" (by way of ", fdest) == EOF);
5469 if (compose->account->name
5470 && *compose->account->name) {
5471 compose_convert_header
5472 (compose, buf, sizeof(buf),
5473 compose->account->name,
5476 err |= (fprintf(fdest, "%s <%s>",
5478 compose->account->address) < 0);
5480 err |= (fprintf(fdest, "%s",
5481 compose->account->address) < 0);
5482 err |= (fputs(")", fdest) == EOF);
5486 if (fputs("\n", fdest) == -1)
5493 if (compose_redirect_write_headers(compose, fdest))
5496 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5497 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5510 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5512 GtkTextBuffer *buffer;
5513 GtkTextIter start, end;
5516 const gchar *out_codeset;
5517 EncodingType encoding = ENC_UNKNOWN;
5518 MimeInfo *mimemsg, *mimetext;
5520 const gchar *src_codeset = CS_INTERNAL;
5521 gchar *from_addr = NULL;
5522 gchar *from_name = NULL;
5524 if (action == COMPOSE_WRITE_FOR_SEND)
5525 attach_parts = TRUE;
5527 /* create message MimeInfo */
5528 mimemsg = procmime_mimeinfo_new();
5529 mimemsg->type = MIMETYPE_MESSAGE;
5530 mimemsg->subtype = g_strdup("rfc822");
5531 mimemsg->content = MIMECONTENT_MEM;
5532 mimemsg->tmp = TRUE; /* must free content later */
5533 mimemsg->data.mem = compose_get_header(compose);
5535 /* Create text part MimeInfo */
5536 /* get all composed text */
5537 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5538 gtk_text_buffer_get_start_iter(buffer, &start);
5539 gtk_text_buffer_get_end_iter(buffer, &end);
5540 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5542 out_codeset = conv_get_charset_str(compose->out_encoding);
5544 if (!out_codeset && is_ascii_str(chars)) {
5545 out_codeset = CS_US_ASCII;
5546 } else if (prefs_common.outgoing_fallback_to_ascii &&
5547 is_ascii_str(chars)) {
5548 out_codeset = CS_US_ASCII;
5549 encoding = ENC_7BIT;
5553 gchar *test_conv_global_out = NULL;
5554 gchar *test_conv_reply = NULL;
5556 /* automatic mode. be automatic. */
5557 codeconv_set_strict(TRUE);
5559 out_codeset = conv_get_outgoing_charset_str();
5561 debug_print("trying to convert to %s\n", out_codeset);
5562 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5565 if (!test_conv_global_out && compose->orig_charset
5566 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5567 out_codeset = compose->orig_charset;
5568 debug_print("failure; trying to convert to %s\n", out_codeset);
5569 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5572 if (!test_conv_global_out && !test_conv_reply) {
5574 out_codeset = CS_INTERNAL;
5575 debug_print("failure; finally using %s\n", out_codeset);
5577 g_free(test_conv_global_out);
5578 g_free(test_conv_reply);
5579 codeconv_set_strict(FALSE);
5582 if (encoding == ENC_UNKNOWN) {
5583 if (prefs_common.encoding_method == CTE_BASE64)
5584 encoding = ENC_BASE64;
5585 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5586 encoding = ENC_QUOTED_PRINTABLE;
5587 else if (prefs_common.encoding_method == CTE_8BIT)
5588 encoding = ENC_8BIT;
5590 encoding = procmime_get_encoding_for_charset(out_codeset);
5593 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5594 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5596 if (action == COMPOSE_WRITE_FOR_SEND) {
5597 codeconv_set_strict(TRUE);
5598 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5599 codeconv_set_strict(FALSE);
5605 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5606 "to the specified %s charset.\n"
5607 "Send it as %s?"), out_codeset, src_codeset);
5608 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5609 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5612 if (aval != G_ALERTALTERNATE) {
5617 out_codeset = src_codeset;
5623 out_codeset = src_codeset;
5628 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5629 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5630 strstr(buf, "\nFrom ") != NULL) {
5631 encoding = ENC_QUOTED_PRINTABLE;
5635 mimetext = procmime_mimeinfo_new();
5636 mimetext->content = MIMECONTENT_MEM;
5637 mimetext->tmp = TRUE; /* must free content later */
5638 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5639 * and free the data, which we need later. */
5640 mimetext->data.mem = g_strdup(buf);
5641 mimetext->type = MIMETYPE_TEXT;
5642 mimetext->subtype = g_strdup("plain");
5643 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5644 g_strdup(out_codeset));
5646 /* protect trailing spaces when signing message */
5647 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5648 privacy_system_can_sign(compose->privacy_system)) {
5649 encoding = ENC_QUOTED_PRINTABLE;
5652 debug_print("main text: %zd bytes encoded as %s in %d\n",
5653 strlen(buf), out_codeset, encoding);
5655 /* check for line length limit */
5656 if (action == COMPOSE_WRITE_FOR_SEND &&
5657 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5658 check_line_length(buf, 1000, &line) < 0) {
5662 msg = g_strdup_printf
5663 (_("Line %d exceeds the line length limit (998 bytes).\n"
5664 "The contents of the message might be broken on the way to the delivery.\n"
5666 "Send it anyway?"), line + 1);
5667 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5669 if (aval != G_ALERTALTERNATE) {
5675 if (encoding != ENC_UNKNOWN)
5676 procmime_encode_content(mimetext, encoding);
5678 /* append attachment parts */
5679 if (compose_use_attach(compose) && attach_parts) {
5680 MimeInfo *mimempart;
5681 gchar *boundary = NULL;
5682 mimempart = procmime_mimeinfo_new();
5683 mimempart->content = MIMECONTENT_EMPTY;
5684 mimempart->type = MIMETYPE_MULTIPART;
5685 mimempart->subtype = g_strdup("mixed");
5689 boundary = generate_mime_boundary(NULL);
5690 } while (strstr(buf, boundary) != NULL);
5692 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5695 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5697 g_node_append(mimempart->node, mimetext->node);
5698 g_node_append(mimemsg->node, mimempart->node);
5700 if (compose_add_attachments(compose, mimempart) < 0)
5703 g_node_append(mimemsg->node, mimetext->node);
5707 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5708 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5709 /* extract name and address */
5710 if (strstr(spec, " <") && strstr(spec, ">")) {
5711 from_addr = g_strdup(strrchr(spec, '<')+1);
5712 *(strrchr(from_addr, '>')) = '\0';
5713 from_name = g_strdup(spec);
5714 *(strrchr(from_name, '<')) = '\0';
5721 /* sign message if sending */
5722 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5723 privacy_system_can_sign(compose->privacy_system))
5724 if (!privacy_sign(compose->privacy_system, mimemsg,
5725 compose->account, from_addr)) {
5732 procmime_write_mimeinfo(mimemsg, fp);
5734 procmime_mimeinfo_free_all(mimemsg);
5739 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5741 GtkTextBuffer *buffer;
5742 GtkTextIter start, end;
5747 if ((fp = g_fopen(file, "wb")) == NULL) {
5748 FILE_OP_ERROR(file, "fopen");
5752 /* chmod for security */
5753 if (change_file_mode_rw(fp, file) < 0) {
5754 FILE_OP_ERROR(file, "chmod");
5755 g_warning("can't change file mode\n");
5758 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5759 gtk_text_buffer_get_start_iter(buffer, &start);
5760 gtk_text_buffer_get_end_iter(buffer, &end);
5761 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5763 chars = conv_codeset_strdup
5764 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5773 len = strlen(chars);
5774 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5775 FILE_OP_ERROR(file, "fwrite");
5784 if (fclose(fp) == EOF) {
5785 FILE_OP_ERROR(file, "fclose");
5792 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5795 MsgInfo *msginfo = compose->targetinfo;
5797 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5798 if (!msginfo) return -1;
5800 if (!force && MSG_IS_LOCKED(msginfo->flags))
5803 item = msginfo->folder;
5804 cm_return_val_if_fail(item != NULL, -1);
5806 if (procmsg_msg_exist(msginfo) &&
5807 (folder_has_parent_of_type(item, F_QUEUE) ||
5808 folder_has_parent_of_type(item, F_DRAFT)
5809 || msginfo == compose->autosaved_draft)) {
5810 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5811 g_warning("can't remove the old message\n");
5814 debug_print("removed reedit target %d\n", msginfo->msgnum);
5821 static void compose_remove_draft(Compose *compose)
5824 MsgInfo *msginfo = compose->targetinfo;
5825 drafts = account_get_special_folder(compose->account, F_DRAFT);
5827 if (procmsg_msg_exist(msginfo)) {
5828 folder_item_remove_msg(drafts, msginfo->msgnum);
5833 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5834 gboolean remove_reedit_target)
5836 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5839 static gboolean compose_warn_encryption(Compose *compose)
5841 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5842 AlertValue val = G_ALERTALTERNATE;
5844 if (warning == NULL)
5847 val = alertpanel_full(_("Encryption warning"), warning,
5848 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5849 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5850 if (val & G_ALERTDISABLE) {
5851 val &= ~G_ALERTDISABLE;
5852 if (val == G_ALERTALTERNATE)
5853 privacy_inhibit_encrypt_warning(compose->privacy_system,
5857 if (val == G_ALERTALTERNATE) {
5864 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5865 gchar **msgpath, gboolean check_subject,
5866 gboolean remove_reedit_target)
5873 PrefsAccount *mailac = NULL, *newsac = NULL;
5874 gboolean err = FALSE;
5876 debug_print("queueing message...\n");
5877 cm_return_val_if_fail(compose->account != NULL, -1);
5879 if (compose_check_entries(compose, check_subject) == FALSE) {
5880 if (compose->batch) {
5881 gtk_widget_show_all(compose->window);
5886 if (!compose->to_list && !compose->newsgroup_list) {
5887 g_warning("can't get recipient list.");
5891 if (compose->to_list) {
5892 if (compose->account->protocol != A_NNTP)
5893 mailac = compose->account;
5894 else if (cur_account && cur_account->protocol != A_NNTP)
5895 mailac = cur_account;
5896 else if (!(mailac = compose_current_mail_account())) {
5897 alertpanel_error(_("No account for sending mails available!"));
5902 if (compose->newsgroup_list) {
5903 if (compose->account->protocol == A_NNTP)
5904 newsac = compose->account;
5906 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5911 /* write queue header */
5912 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5913 G_DIR_SEPARATOR, compose, (guint) rand());
5914 debug_print("queuing to %s\n", tmp);
5915 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5916 FILE_OP_ERROR(tmp, "fopen");
5921 if (change_file_mode_rw(fp, tmp) < 0) {
5922 FILE_OP_ERROR(tmp, "chmod");
5923 g_warning("can't change file mode\n");
5926 /* queueing variables */
5927 err |= (fprintf(fp, "AF:\n") < 0);
5928 err |= (fprintf(fp, "NF:0\n") < 0);
5929 err |= (fprintf(fp, "PS:10\n") < 0);
5930 err |= (fprintf(fp, "SRH:1\n") < 0);
5931 err |= (fprintf(fp, "SFN:\n") < 0);
5932 err |= (fprintf(fp, "DSR:\n") < 0);
5934 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5936 err |= (fprintf(fp, "MID:\n") < 0);
5937 err |= (fprintf(fp, "CFG:\n") < 0);
5938 err |= (fprintf(fp, "PT:0\n") < 0);
5939 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5940 err |= (fprintf(fp, "RQ:\n") < 0);
5942 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5944 err |= (fprintf(fp, "SSV:\n") < 0);
5946 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5948 err |= (fprintf(fp, "NSV:\n") < 0);
5949 err |= (fprintf(fp, "SSH:\n") < 0);
5950 /* write recepient list */
5951 if (compose->to_list) {
5952 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5953 for (cur = compose->to_list->next; cur != NULL;
5955 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5956 err |= (fprintf(fp, "\n") < 0);
5958 /* write newsgroup list */
5959 if (compose->newsgroup_list) {
5960 err |= (fprintf(fp, "NG:") < 0);
5961 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5962 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5963 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5964 err |= (fprintf(fp, "\n") < 0);
5966 /* Sylpheed account IDs */
5968 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5970 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5973 if (compose->privacy_system != NULL) {
5974 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5975 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5976 if (compose->use_encryption) {
5978 if (!compose_warn_encryption(compose)) {
5984 if (mailac && mailac->encrypt_to_self) {
5985 GSList *tmp_list = g_slist_copy(compose->to_list);
5986 tmp_list = g_slist_append(tmp_list, compose->account->address);
5987 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5988 g_slist_free(tmp_list);
5990 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5992 if (encdata != NULL) {
5993 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5994 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5995 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5997 } /* else we finally dont want to encrypt */
5999 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6000 /* and if encdata was null, it means there's been a problem in
6003 g_warning("failed to write queue message");
6013 /* Save copy folder */
6014 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6015 gchar *savefolderid;
6017 savefolderid = compose_get_save_to(compose);
6018 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6019 g_free(savefolderid);
6021 /* Save copy folder */
6022 if (compose->return_receipt) {
6023 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6025 /* Message-ID of message replying to */
6026 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6027 gchar *folderid = NULL;
6029 if (compose->replyinfo->folder)
6030 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6031 if (folderid == NULL)
6032 folderid = g_strdup("NULL");
6034 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6037 /* Message-ID of message forwarding to */
6038 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6039 gchar *folderid = NULL;
6041 if (compose->fwdinfo->folder)
6042 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6043 if (folderid == NULL)
6044 folderid = g_strdup("NULL");
6046 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6050 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6051 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6053 /* end of headers */
6054 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6056 if (compose->redirect_filename != NULL) {
6057 if (compose_redirect_write_to_file(compose, fp) < 0) {
6065 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6069 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6073 g_warning("failed to write queue message\n");
6079 if (fclose(fp) == EOF) {
6080 FILE_OP_ERROR(tmp, "fclose");
6086 if (item && *item) {
6089 queue = account_get_special_folder(compose->account, F_QUEUE);
6092 g_warning("can't find queue folder\n");
6097 folder_item_scan(queue);
6098 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6099 g_warning("can't queue the message\n");
6105 if (msgpath == NULL) {
6111 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6112 compose_remove_reedit_target(compose, FALSE);
6115 if ((msgnum != NULL) && (item != NULL)) {
6123 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6126 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6128 struct stat statbuf;
6129 gchar *type, *subtype;
6130 GtkTreeModel *model;
6133 model = gtk_tree_view_get_model(tree_view);
6135 if (!gtk_tree_model_get_iter_first(model, &iter))
6138 gtk_tree_model_get(model, &iter,
6142 if (!is_file_exist(ainfo->file)) {
6143 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6144 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6145 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6147 if (val == G_ALERTDEFAULT) {
6152 if (g_stat(ainfo->file, &statbuf) < 0)
6155 mimepart = procmime_mimeinfo_new();
6156 mimepart->content = MIMECONTENT_FILE;
6157 mimepart->data.filename = g_strdup(ainfo->file);
6158 mimepart->tmp = FALSE; /* or we destroy our attachment */
6159 mimepart->offset = 0;
6160 mimepart->length = statbuf.st_size;
6162 type = g_strdup(ainfo->content_type);
6164 if (!strchr(type, '/')) {
6166 type = g_strdup("application/octet-stream");
6169 subtype = strchr(type, '/') + 1;
6170 *(subtype - 1) = '\0';
6171 mimepart->type = procmime_get_media_type(type);
6172 mimepart->subtype = g_strdup(subtype);
6175 if (mimepart->type == MIMETYPE_MESSAGE &&
6176 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6177 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6178 } else if (mimepart->type == MIMETYPE_TEXT) {
6179 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6180 /* Text parts with no name come from multipart/alternative
6181 * forwards. Make sure the recipient won't look at the
6182 * original HTML part by mistake. */
6183 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6184 ainfo->name = g_strdup_printf(_("Original %s part"),
6188 g_hash_table_insert(mimepart->typeparameters,
6189 g_strdup("charset"), g_strdup(ainfo->charset));
6191 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6192 if (mimepart->type == MIMETYPE_APPLICATION &&
6193 !strcmp2(mimepart->subtype, "octet-stream"))
6194 g_hash_table_insert(mimepart->typeparameters,
6195 g_strdup("name"), g_strdup(ainfo->name));
6196 g_hash_table_insert(mimepart->dispositionparameters,
6197 g_strdup("filename"), g_strdup(ainfo->name));
6198 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6201 if (mimepart->type == MIMETYPE_MESSAGE
6202 || mimepart->type == MIMETYPE_MULTIPART)
6203 ainfo->encoding = ENC_BINARY;
6204 else if (compose->use_signing) {
6205 if (ainfo->encoding == ENC_7BIT)
6206 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6207 else if (ainfo->encoding == ENC_8BIT)
6208 ainfo->encoding = ENC_BASE64;
6213 procmime_encode_content(mimepart, ainfo->encoding);
6215 g_node_append(parent->node, mimepart->node);
6216 } while (gtk_tree_model_iter_next(model, &iter));
6221 static gchar *compose_quote_list_of_addresses(gchar *str)
6223 GSList *list = NULL, *item = NULL;
6224 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6226 list = address_list_append_with_comments(list, str);
6227 for (item = list; item != NULL; item = item->next) {
6228 gchar *spec = item->data;
6229 gchar *endofname = strstr(spec, " <");
6230 if (endofname != NULL) {
6233 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6234 qqname = escape_internal_quotes(qname, '"');
6236 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6237 gchar *addr = g_strdup(endofname);
6238 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6239 faddr = g_strconcat(name, addr, NULL);
6242 debug_print("new auto-quoted address: '%s'", faddr);
6246 result = g_strdup((faddr != NULL)? faddr: spec);
6248 result = g_strconcat(result,
6250 (faddr != NULL)? faddr: spec,
6253 if (faddr != NULL) {
6258 slist_free_strings_full(list);
6263 #define IS_IN_CUSTOM_HEADER(header) \
6264 (compose->account->add_customhdr && \
6265 custom_header_find(compose->account->customhdr_list, header) != NULL)
6267 static void compose_add_headerfield_from_headerlist(Compose *compose,
6269 const gchar *fieldname,
6270 const gchar *seperator)
6272 gchar *str, *fieldname_w_colon;
6273 gboolean add_field = FALSE;
6275 ComposeHeaderEntry *headerentry;
6276 const gchar *headerentryname;
6277 const gchar *trans_fieldname;
6280 if (IS_IN_CUSTOM_HEADER(fieldname))
6283 debug_print("Adding %s-fields\n", fieldname);
6285 fieldstr = g_string_sized_new(64);
6287 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6288 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6290 for (list = compose->header_list; list; list = list->next) {
6291 headerentry = ((ComposeHeaderEntry *)list->data);
6292 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6294 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6295 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6297 str = compose_quote_list_of_addresses(ustr);
6299 if (str != NULL && str[0] != '\0') {
6301 g_string_append(fieldstr, seperator);
6302 g_string_append(fieldstr, str);
6311 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6312 compose_convert_header
6313 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6314 strlen(fieldname) + 2, TRUE);
6315 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6319 g_free(fieldname_w_colon);
6320 g_string_free(fieldstr, TRUE);
6325 static gchar *compose_get_manual_headers_info(Compose *compose)
6327 GString *sh_header = g_string_new(" ");
6329 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6331 for (list = compose->header_list; list; list = list->next) {
6332 ComposeHeaderEntry *headerentry;
6335 gchar *headername_wcolon;
6336 const gchar *headername_trans;
6338 gboolean standard_header = FALSE;
6340 headerentry = ((ComposeHeaderEntry *)list->data);
6342 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6344 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6349 if (!strstr(tmp, ":")) {
6350 headername_wcolon = g_strconcat(tmp, ":", NULL);
6351 headername = g_strdup(tmp);
6353 headername_wcolon = g_strdup(tmp);
6354 headername = g_strdup(strtok(tmp, ":"));
6358 string = std_headers;
6359 while (*string != NULL) {
6360 headername_trans = prefs_common_translated_header_name(*string);
6361 if (!strcmp(headername_trans, headername_wcolon))
6362 standard_header = TRUE;
6365 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6366 g_string_append_printf(sh_header, "%s ", headername);
6368 g_free(headername_wcolon);
6370 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6371 return g_string_free(sh_header, FALSE);
6374 static gchar *compose_get_header(Compose *compose)
6376 gchar buf[BUFFSIZE];
6377 const gchar *entry_str;
6381 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6383 gchar *from_name = NULL, *from_address = NULL;
6386 cm_return_val_if_fail(compose->account != NULL, NULL);
6387 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6389 header = g_string_sized_new(64);
6392 get_rfc822_date(buf, sizeof(buf));
6393 g_string_append_printf(header, "Date: %s\n", buf);
6397 if (compose->account->name && *compose->account->name) {
6399 QUOTE_IF_REQUIRED(buf, compose->account->name);
6400 tmp = g_strdup_printf("%s <%s>",
6401 buf, compose->account->address);
6403 tmp = g_strdup_printf("%s",
6404 compose->account->address);
6406 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6407 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6409 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6410 from_address = g_strdup(compose->account->address);
6412 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6413 /* extract name and address */
6414 if (strstr(spec, " <") && strstr(spec, ">")) {
6415 from_address = g_strdup(strrchr(spec, '<')+1);
6416 *(strrchr(from_address, '>')) = '\0';
6417 from_name = g_strdup(spec);
6418 *(strrchr(from_name, '<')) = '\0';
6421 from_address = g_strdup(spec);
6428 if (from_name && *from_name) {
6430 compose_convert_header
6431 (compose, buf, sizeof(buf), from_name,
6432 strlen("From: "), TRUE);
6433 QUOTE_IF_REQUIRED(name, buf);
6434 qname = escape_internal_quotes(name, '"');
6436 g_string_append_printf(header, "From: %s <%s>\n",
6437 qname, from_address);
6441 g_string_append_printf(header, "From: %s\n", from_address);
6444 g_free(from_address);
6447 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6450 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6453 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6457 * If this account is a NNTP account remove Bcc header from
6458 * message body since it otherwise will be publicly shown
6460 if (compose->account->protocol != A_NNTP)
6461 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6464 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6466 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6469 compose_convert_header(compose, buf, sizeof(buf), str,
6470 strlen("Subject: "), FALSE);
6471 g_string_append_printf(header, "Subject: %s\n", buf);
6477 if (compose->account->set_domain && compose->account->domain) {
6478 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6479 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6480 g_snprintf(buf, sizeof(buf), "%s",
6481 strchr(compose->account->address, '@') ?
6482 strchr(compose->account->address, '@')+1 :
6483 compose->account->address);
6485 g_snprintf(buf, sizeof(buf), "%s", "");
6488 if (compose->account->gen_msgid) {
6490 if (compose->account->msgid_with_addr) {
6491 addr = compose->account->address;
6493 generate_msgid(buf, sizeof(buf), addr);
6494 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6496 g_free(compose->msgid);
6497 compose->msgid = g_strdup(buf);
6499 compose->msgid = NULL;
6502 if (compose->remove_references == FALSE) {
6504 if (compose->inreplyto && compose->to_list)
6505 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6508 if (compose->references)
6509 g_string_append_printf(header, "References: %s\n", compose->references);
6513 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6516 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6519 if (compose->account->organization &&
6520 strlen(compose->account->organization) &&
6521 !IS_IN_CUSTOM_HEADER("Organization")) {
6522 compose_convert_header(compose, buf, sizeof(buf),
6523 compose->account->organization,
6524 strlen("Organization: "), FALSE);
6525 g_string_append_printf(header, "Organization: %s\n", buf);
6528 /* Program version and system info */
6529 if (compose->account->gen_xmailer &&
6530 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6531 !compose->newsgroup_list) {
6532 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6534 gtk_major_version, gtk_minor_version, gtk_micro_version,
6537 if (compose->account->gen_xmailer &&
6538 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6539 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6541 gtk_major_version, gtk_minor_version, gtk_micro_version,
6545 /* custom headers */
6546 if (compose->account->add_customhdr) {
6549 for (cur = compose->account->customhdr_list; cur != NULL;
6551 CustomHeader *chdr = (CustomHeader *)cur->data;
6553 if (custom_header_is_allowed(chdr->name)
6554 && chdr->value != NULL
6555 && *(chdr->value) != '\0') {
6556 compose_convert_header
6557 (compose, buf, sizeof(buf),
6559 strlen(chdr->name) + 2, FALSE);
6560 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6565 /* Automatic Faces and X-Faces */
6566 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6567 g_string_append_printf(header, "X-Face: %s\n", buf);
6569 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6570 g_string_append_printf(header, "X-Face: %s\n", buf);
6572 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6573 g_string_append_printf(header, "Face: %s\n", buf);
6575 else if (get_default_face (buf, sizeof(buf)) == 0) {
6576 g_string_append_printf(header, "Face: %s\n", buf);
6580 switch (compose->priority) {
6581 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6582 "X-Priority: 1 (Highest)\n");
6584 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6585 "X-Priority: 2 (High)\n");
6587 case PRIORITY_NORMAL: break;
6588 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6589 "X-Priority: 4 (Low)\n");
6591 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6592 "X-Priority: 5 (Lowest)\n");
6594 default: debug_print("compose: priority unknown : %d\n",
6598 /* Request Return Receipt */
6599 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6600 if (compose->return_receipt) {
6601 if (compose->account->name
6602 && *compose->account->name) {
6603 compose_convert_header(compose, buf, sizeof(buf),
6604 compose->account->name,
6605 strlen("Disposition-Notification-To: "),
6607 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6609 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6613 /* get special headers */
6614 for (list = compose->header_list; list; list = list->next) {
6615 ComposeHeaderEntry *headerentry;
6618 gchar *headername_wcolon;
6619 const gchar *headername_trans;
6622 gboolean standard_header = FALSE;
6624 headerentry = ((ComposeHeaderEntry *)list->data);
6626 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6628 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6633 if (!strstr(tmp, ":")) {
6634 headername_wcolon = g_strconcat(tmp, ":", NULL);
6635 headername = g_strdup(tmp);
6637 headername_wcolon = g_strdup(tmp);
6638 headername = g_strdup(strtok(tmp, ":"));
6642 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6643 Xstrdup_a(headervalue, entry_str, return NULL);
6644 subst_char(headervalue, '\r', ' ');
6645 subst_char(headervalue, '\n', ' ');
6646 string = std_headers;
6647 while (*string != NULL) {
6648 headername_trans = prefs_common_translated_header_name(*string);
6649 if (!strcmp(headername_trans, headername_wcolon))
6650 standard_header = TRUE;
6653 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6654 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6657 g_free(headername_wcolon);
6661 g_string_free(header, FALSE);
6666 #undef IS_IN_CUSTOM_HEADER
6668 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6669 gint header_len, gboolean addr_field)
6671 gchar *tmpstr = NULL;
6672 const gchar *out_codeset = NULL;
6674 cm_return_if_fail(src != NULL);
6675 cm_return_if_fail(dest != NULL);
6677 if (len < 1) return;
6679 tmpstr = g_strdup(src);
6681 subst_char(tmpstr, '\n', ' ');
6682 subst_char(tmpstr, '\r', ' ');
6685 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6686 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6687 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6692 codeconv_set_strict(TRUE);
6693 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6694 conv_get_charset_str(compose->out_encoding));
6695 codeconv_set_strict(FALSE);
6697 if (!dest || *dest == '\0') {
6698 gchar *test_conv_global_out = NULL;
6699 gchar *test_conv_reply = NULL;
6701 /* automatic mode. be automatic. */
6702 codeconv_set_strict(TRUE);
6704 out_codeset = conv_get_outgoing_charset_str();
6706 debug_print("trying to convert to %s\n", out_codeset);
6707 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6710 if (!test_conv_global_out && compose->orig_charset
6711 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6712 out_codeset = compose->orig_charset;
6713 debug_print("failure; trying to convert to %s\n", out_codeset);
6714 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6717 if (!test_conv_global_out && !test_conv_reply) {
6719 out_codeset = CS_INTERNAL;
6720 debug_print("finally using %s\n", out_codeset);
6722 g_free(test_conv_global_out);
6723 g_free(test_conv_reply);
6724 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6726 codeconv_set_strict(FALSE);
6731 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6735 cm_return_if_fail(user_data != NULL);
6737 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6738 g_strstrip(address);
6739 if (*address != '\0') {
6740 gchar *name = procheader_get_fromname(address);
6741 extract_address(address);
6742 #ifndef USE_NEW_ADDRBOOK
6743 addressbook_add_contact(name, address, NULL, NULL);
6745 debug_print("%s: %s\n", name, address);
6746 if (addressadd_selection(name, address, NULL, NULL)) {
6747 debug_print( "addressbook_add_contact - added\n" );
6754 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6756 GtkWidget *menuitem;
6759 cm_return_if_fail(menu != NULL);
6760 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6762 menuitem = gtk_separator_menu_item_new();
6763 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6764 gtk_widget_show(menuitem);
6766 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6767 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6769 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6770 g_strstrip(address);
6771 if (*address == '\0') {
6772 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6775 g_signal_connect(G_OBJECT(menuitem), "activate",
6776 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6777 gtk_widget_show(menuitem);
6780 void compose_add_extra_header(gchar *header, GtkListStore *model)
6783 if (strcmp(header, "")) {
6784 COMBOBOX_ADD(model, header, COMPOSE_TO);
6788 void compose_add_extra_header_entries(GtkListStore *model)
6792 gchar buf[BUFFSIZE];
6795 if (extra_headers == NULL) {
6796 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6797 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6798 debug_print("extra headers file not found\n");
6799 goto extra_headers_done;
6801 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6802 lastc = strlen(buf) - 1; /* remove trailing control chars */
6803 while (lastc >= 0 && buf[lastc] != ':')
6804 buf[lastc--] = '\0';
6805 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6806 buf[lastc] = '\0'; /* remove trailing : for comparison */
6807 if (custom_header_is_allowed(buf)) {
6809 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6812 g_message("disallowed extra header line: %s\n", buf);
6816 g_message("invalid extra header line: %s\n", buf);
6822 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6823 extra_headers = g_slist_reverse(extra_headers);
6825 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6828 static void compose_create_header_entry(Compose *compose)
6830 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6837 const gchar *header = NULL;
6838 ComposeHeaderEntry *headerentry;
6839 gboolean standard_header = FALSE;
6840 GtkListStore *model;
6843 headerentry = g_new0(ComposeHeaderEntry, 1);
6845 /* Combo box model */
6846 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6847 #if !GTK_CHECK_VERSION(2, 24, 0)
6848 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6850 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6852 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6854 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6856 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6857 COMPOSE_NEWSGROUPS);
6858 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6860 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6861 COMPOSE_FOLLOWUPTO);
6862 compose_add_extra_header_entries(model);
6865 #if GTK_CHECK_VERSION(2, 24, 0)
6866 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6867 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6868 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6869 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6870 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6872 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6873 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6874 G_CALLBACK(compose_grab_focus_cb), compose);
6875 gtk_widget_show(combo);
6877 /* Putting only the combobox child into focus chain of its parent causes
6878 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6879 * This eliminates need to pres Tab twice in order to really get from the
6880 * combobox to next widget. */
6882 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6883 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6886 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6887 compose->header_nextrow, compose->header_nextrow+1,
6888 GTK_SHRINK, GTK_FILL, 0, 0);
6889 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6890 const gchar *last_header_entry = gtk_entry_get_text(
6891 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6893 while (*string != NULL) {
6894 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6895 standard_header = TRUE;
6898 if (standard_header)
6899 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6901 if (!compose->header_last || !standard_header) {
6902 switch(compose->account->protocol) {
6904 header = prefs_common_translated_header_name("Newsgroups:");
6907 header = prefs_common_translated_header_name("To:");
6912 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6914 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6915 G_CALLBACK(compose_grab_focus_cb), compose);
6917 /* Entry field with cleanup button */
6918 button = gtk_button_new();
6919 gtk_button_set_image(GTK_BUTTON(button),
6920 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6921 gtk_widget_show(button);
6922 CLAWS_SET_TIP(button,
6923 _("Delete entry contents"));
6924 entry = gtk_entry_new();
6925 gtk_widget_show(entry);
6926 CLAWS_SET_TIP(entry,
6927 _("Use <tab> to autocomplete from addressbook"));
6928 hbox = gtk_hbox_new (FALSE, 0);
6929 gtk_widget_show(hbox);
6930 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6931 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6932 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6933 compose->header_nextrow, compose->header_nextrow+1,
6934 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6936 g_signal_connect(G_OBJECT(entry), "key-press-event",
6937 G_CALLBACK(compose_headerentry_key_press_event_cb),
6939 g_signal_connect(G_OBJECT(entry), "changed",
6940 G_CALLBACK(compose_headerentry_changed_cb),
6942 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6943 G_CALLBACK(compose_grab_focus_cb), compose);
6945 g_signal_connect(G_OBJECT(button), "clicked",
6946 G_CALLBACK(compose_headerentry_button_clicked_cb),
6950 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6951 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6952 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6953 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6954 G_CALLBACK(compose_header_drag_received_cb),
6956 g_signal_connect(G_OBJECT(entry), "drag-drop",
6957 G_CALLBACK(compose_drag_drop),
6959 g_signal_connect(G_OBJECT(entry), "populate-popup",
6960 G_CALLBACK(compose_entry_popup_extend),
6963 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6965 headerentry->compose = compose;
6966 headerentry->combo = combo;
6967 headerentry->entry = entry;
6968 headerentry->button = button;
6969 headerentry->hbox = hbox;
6970 headerentry->headernum = compose->header_nextrow;
6971 headerentry->type = PREF_NONE;
6973 compose->header_nextrow++;
6974 compose->header_last = headerentry;
6975 compose->header_list =
6976 g_slist_append(compose->header_list,
6980 static void compose_add_header_entry(Compose *compose, const gchar *header,
6981 gchar *text, ComposePrefType pref_type)
6983 ComposeHeaderEntry *last_header = compose->header_last;
6984 gchar *tmp = g_strdup(text), *email;
6985 gboolean replyto_hdr;
6987 replyto_hdr = (!strcasecmp(header,
6988 prefs_common_translated_header_name("Reply-To:")) ||
6990 prefs_common_translated_header_name("Followup-To:")) ||
6992 prefs_common_translated_header_name("In-Reply-To:")));
6994 extract_address(tmp);
6995 email = g_utf8_strdown(tmp, -1);
6997 if (replyto_hdr == FALSE &&
6998 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7000 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7001 header, text, (gint) pref_type);
7007 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7008 gtk_entry_set_text(GTK_ENTRY(
7009 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7011 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7012 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7013 last_header->type = pref_type;
7015 if (replyto_hdr == FALSE)
7016 g_hash_table_insert(compose->email_hashtable, email,
7017 GUINT_TO_POINTER(1));
7024 static void compose_destroy_headerentry(Compose *compose,
7025 ComposeHeaderEntry *headerentry)
7027 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7030 extract_address(text);
7031 email = g_utf8_strdown(text, -1);
7032 g_hash_table_remove(compose->email_hashtable, email);
7036 gtk_widget_destroy(headerentry->combo);
7037 gtk_widget_destroy(headerentry->entry);
7038 gtk_widget_destroy(headerentry->button);
7039 gtk_widget_destroy(headerentry->hbox);
7040 g_free(headerentry);
7043 static void compose_remove_header_entries(Compose *compose)
7046 for (list = compose->header_list; list; list = list->next)
7047 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7049 compose->header_last = NULL;
7050 g_slist_free(compose->header_list);
7051 compose->header_list = NULL;
7052 compose->header_nextrow = 1;
7053 compose_create_header_entry(compose);
7056 static GtkWidget *compose_create_header(Compose *compose)
7058 GtkWidget *from_optmenu_hbox;
7059 GtkWidget *header_scrolledwin_main;
7060 GtkWidget *header_table_main;
7061 GtkWidget *header_scrolledwin;
7062 GtkWidget *header_table;
7064 /* parent with account selection and from header */
7065 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7066 gtk_widget_show(header_scrolledwin_main);
7067 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7069 header_table_main = gtk_table_new(2, 2, FALSE);
7070 gtk_widget_show(header_table_main);
7071 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7072 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7073 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7075 from_optmenu_hbox = compose_account_option_menu_create(compose);
7076 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7077 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7079 /* child with header labels and entries */
7080 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7081 gtk_widget_show(header_scrolledwin);
7082 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7084 header_table = gtk_table_new(2, 2, FALSE);
7085 gtk_widget_show(header_table);
7086 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7087 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7088 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7090 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7091 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7093 compose->header_table = header_table;
7094 compose->header_list = NULL;
7095 compose->header_nextrow = 0;
7097 compose_create_header_entry(compose);
7099 compose->table = NULL;
7101 return header_scrolledwin_main;
7104 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7106 Compose *compose = (Compose *)data;
7107 GdkEventButton event;
7110 event.time = gtk_get_current_event_time();
7112 return attach_button_pressed(compose->attach_clist, &event, compose);
7115 static GtkWidget *compose_create_attach(Compose *compose)
7117 GtkWidget *attach_scrwin;
7118 GtkWidget *attach_clist;
7120 GtkListStore *store;
7121 GtkCellRenderer *renderer;
7122 GtkTreeViewColumn *column;
7123 GtkTreeSelection *selection;
7125 /* attachment list */
7126 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7127 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7128 GTK_POLICY_AUTOMATIC,
7129 GTK_POLICY_AUTOMATIC);
7130 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7132 store = gtk_list_store_new(N_ATTACH_COLS,
7138 G_TYPE_AUTO_POINTER,
7140 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7141 (GTK_TREE_MODEL(store)));
7142 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7143 g_object_unref(store);
7145 renderer = gtk_cell_renderer_text_new();
7146 column = gtk_tree_view_column_new_with_attributes
7147 (_("Mime type"), renderer, "text",
7148 COL_MIMETYPE, NULL);
7149 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7151 renderer = gtk_cell_renderer_text_new();
7152 column = gtk_tree_view_column_new_with_attributes
7153 (_("Size"), renderer, "text",
7155 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7157 renderer = gtk_cell_renderer_text_new();
7158 column = gtk_tree_view_column_new_with_attributes
7159 (_("Name"), renderer, "text",
7161 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7163 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7164 prefs_common.use_stripes_everywhere);
7165 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7166 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7168 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7169 G_CALLBACK(attach_selected), compose);
7170 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7171 G_CALLBACK(attach_button_pressed), compose);
7172 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7173 G_CALLBACK(popup_attach_button_pressed), compose);
7174 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7175 G_CALLBACK(attach_key_pressed), compose);
7178 gtk_drag_dest_set(attach_clist,
7179 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7180 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7181 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7182 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7183 G_CALLBACK(compose_attach_drag_received_cb),
7185 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7186 G_CALLBACK(compose_drag_drop),
7189 compose->attach_scrwin = attach_scrwin;
7190 compose->attach_clist = attach_clist;
7192 return attach_scrwin;
7195 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7196 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7198 static GtkWidget *compose_create_others(Compose *compose)
7201 GtkWidget *savemsg_checkbtn;
7202 GtkWidget *savemsg_combo;
7203 GtkWidget *savemsg_select;
7206 gchar *folderidentifier;
7208 /* Table for settings */
7209 table = gtk_table_new(3, 1, FALSE);
7210 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7211 gtk_widget_show(table);
7212 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7215 /* Save Message to folder */
7216 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7217 gtk_widget_show(savemsg_checkbtn);
7218 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7219 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7222 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7223 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7225 #if !GTK_CHECK_VERSION(2, 24, 0)
7226 savemsg_combo = gtk_combo_box_entry_new_text();
7228 savemsg_combo = gtk_combo_box_text_new_with_entry();
7230 compose->savemsg_checkbtn = savemsg_checkbtn;
7231 compose->savemsg_combo = savemsg_combo;
7232 gtk_widget_show(savemsg_combo);
7234 if (prefs_common.compose_save_to_history)
7235 #if !GTK_CHECK_VERSION(2, 24, 0)
7236 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7237 prefs_common.compose_save_to_history);
7239 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7240 prefs_common.compose_save_to_history);
7242 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7243 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7244 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7245 G_CALLBACK(compose_grab_focus_cb), compose);
7246 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7247 folderidentifier = folder_item_get_identifier(account_get_special_folder
7248 (compose->account, F_OUTBOX));
7249 compose_set_save_to(compose, folderidentifier);
7250 g_free(folderidentifier);
7253 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7254 gtk_widget_show(savemsg_select);
7255 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7256 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7257 G_CALLBACK(compose_savemsg_select_cb),
7263 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7265 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7266 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7269 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7274 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7277 path = folder_item_get_identifier(dest);
7279 compose_set_save_to(compose, path);
7283 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7284 GdkAtom clip, GtkTextIter *insert_place);
7287 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7291 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7293 if (event->button == 3) {
7295 GtkTextIter sel_start, sel_end;
7296 gboolean stuff_selected;
7298 /* move the cursor to allow GtkAspell to check the word
7299 * under the mouse */
7300 if (event->x && event->y) {
7301 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7302 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7304 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7307 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7308 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7311 stuff_selected = gtk_text_buffer_get_selection_bounds(
7313 &sel_start, &sel_end);
7315 gtk_text_buffer_place_cursor (buffer, &iter);
7316 /* reselect stuff */
7318 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7319 gtk_text_buffer_select_range(buffer,
7320 &sel_start, &sel_end);
7322 return FALSE; /* pass the event so that the right-click goes through */
7325 if (event->button == 2) {
7330 /* get the middle-click position to paste at the correct place */
7331 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7332 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7334 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7337 entry_paste_clipboard(compose, text,
7338 prefs_common.linewrap_pastes,
7339 GDK_SELECTION_PRIMARY, &iter);
7347 static void compose_spell_menu_changed(void *data)
7349 Compose *compose = (Compose *)data;
7351 GtkWidget *menuitem;
7352 GtkWidget *parent_item;
7353 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7356 if (compose->gtkaspell == NULL)
7359 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7360 "/Menu/Spelling/Options");
7362 /* setting the submenu removes /Spelling/Options from the factory
7363 * so we need to save it */
7365 if (parent_item == NULL) {
7366 parent_item = compose->aspell_options_menu;
7367 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7369 compose->aspell_options_menu = parent_item;
7371 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7373 spell_menu = g_slist_reverse(spell_menu);
7374 for (items = spell_menu;
7375 items; items = items->next) {
7376 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7377 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7378 gtk_widget_show(GTK_WIDGET(menuitem));
7380 g_slist_free(spell_menu);
7382 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7383 gtk_widget_show(parent_item);
7386 static void compose_dict_changed(void *data)
7388 Compose *compose = (Compose *) data;
7390 if(!compose->gtkaspell)
7392 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7395 gtkaspell_highlight_all(compose->gtkaspell);
7396 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7400 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7402 Compose *compose = (Compose *)data;
7403 GdkEventButton event;
7406 event.time = gtk_get_current_event_time();
7410 return text_clicked(compose->text, &event, compose);
7413 static gboolean compose_force_window_origin = TRUE;
7414 static Compose *compose_create(PrefsAccount *account,
7423 GtkWidget *handlebox;
7425 GtkWidget *notebook;
7427 GtkWidget *attach_hbox;
7428 GtkWidget *attach_lab1;
7429 GtkWidget *attach_lab2;
7434 GtkWidget *subject_hbox;
7435 GtkWidget *subject_frame;
7436 GtkWidget *subject_entry;
7440 GtkWidget *edit_vbox;
7441 GtkWidget *ruler_hbox;
7443 GtkWidget *scrolledwin;
7445 GtkTextBuffer *buffer;
7446 GtkClipboard *clipboard;
7448 UndoMain *undostruct;
7450 GtkWidget *popupmenu;
7451 GtkWidget *tmpl_menu;
7452 GtkActionGroup *action_group = NULL;
7455 GtkAspell * gtkaspell = NULL;
7458 static GdkGeometry geometry;
7460 cm_return_val_if_fail(account != NULL, NULL);
7462 debug_print("Creating compose window...\n");
7463 compose = g_new0(Compose, 1);
7465 compose->batch = batch;
7466 compose->account = account;
7467 compose->folder = folder;
7469 compose->mutex = cm_mutex_new();
7470 compose->set_cursor_pos = -1;
7472 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7474 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7475 gtk_widget_set_size_request(window, prefs_common.compose_width,
7476 prefs_common.compose_height);
7478 if (!geometry.max_width) {
7479 geometry.max_width = gdk_screen_width();
7480 geometry.max_height = gdk_screen_height();
7483 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7484 &geometry, GDK_HINT_MAX_SIZE);
7485 if (!geometry.min_width) {
7486 geometry.min_width = 600;
7487 geometry.min_height = 440;
7489 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7490 &geometry, GDK_HINT_MIN_SIZE);
7492 #ifndef GENERIC_UMPC
7493 if (compose_force_window_origin)
7494 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7495 prefs_common.compose_y);
7497 g_signal_connect(G_OBJECT(window), "delete_event",
7498 G_CALLBACK(compose_delete_cb), compose);
7499 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7500 gtk_widget_realize(window);
7502 gtkut_widget_set_composer_icon(window);
7504 vbox = gtk_vbox_new(FALSE, 0);
7505 gtk_container_add(GTK_CONTAINER(window), vbox);
7507 compose->ui_manager = gtk_ui_manager_new();
7508 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7509 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7510 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7511 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7512 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7513 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7514 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7515 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7516 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7517 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7636 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)
7637 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)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7643 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)
7644 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)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7649 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)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7653 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)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7659 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)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7665 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)
7666 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)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7679 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)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7697 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7698 gtk_widget_show_all(menubar);
7700 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7701 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7703 if (prefs_common.toolbar_detachable) {
7704 handlebox = gtk_handle_box_new();
7706 handlebox = gtk_hbox_new(FALSE, 0);
7708 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7710 gtk_widget_realize(handlebox);
7711 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7714 vbox2 = gtk_vbox_new(FALSE, 2);
7715 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7716 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7719 notebook = gtk_notebook_new();
7720 gtk_widget_show(notebook);
7722 /* header labels and entries */
7723 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7724 compose_create_header(compose),
7725 gtk_label_new_with_mnemonic(_("Hea_der")));
7726 /* attachment list */
7727 attach_hbox = gtk_hbox_new(FALSE, 0);
7728 gtk_widget_show(attach_hbox);
7730 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7731 gtk_widget_show(attach_lab1);
7732 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7734 attach_lab2 = gtk_label_new("");
7735 gtk_widget_show(attach_lab2);
7736 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7738 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7739 compose_create_attach(compose),
7742 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7743 compose_create_others(compose),
7744 gtk_label_new_with_mnemonic(_("Othe_rs")));
7747 subject_hbox = gtk_hbox_new(FALSE, 0);
7748 gtk_widget_show(subject_hbox);
7750 subject_frame = gtk_frame_new(NULL);
7751 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7752 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7753 gtk_widget_show(subject_frame);
7755 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7756 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7757 gtk_widget_show(subject);
7759 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7760 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7761 gtk_widget_show(label);
7764 subject_entry = claws_spell_entry_new();
7766 subject_entry = gtk_entry_new();
7768 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7769 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7770 G_CALLBACK(compose_grab_focus_cb), compose);
7771 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7772 gtk_widget_show(subject_entry);
7773 compose->subject_entry = subject_entry;
7774 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7776 edit_vbox = gtk_vbox_new(FALSE, 0);
7778 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7781 ruler_hbox = gtk_hbox_new(FALSE, 0);
7782 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7784 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7785 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7786 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7790 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7791 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7792 GTK_POLICY_AUTOMATIC,
7793 GTK_POLICY_AUTOMATIC);
7794 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7796 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7798 text = gtk_text_view_new();
7799 if (prefs_common.show_compose_margin) {
7800 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7801 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7803 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7804 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7805 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7806 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7807 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7809 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7810 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7811 G_CALLBACK(compose_edit_size_alloc),
7813 g_signal_connect(G_OBJECT(buffer), "changed",
7814 G_CALLBACK(compose_changed_cb), compose);
7815 g_signal_connect(G_OBJECT(text), "grab_focus",
7816 G_CALLBACK(compose_grab_focus_cb), compose);
7817 g_signal_connect(G_OBJECT(buffer), "insert_text",
7818 G_CALLBACK(text_inserted), compose);
7819 g_signal_connect(G_OBJECT(text), "button_press_event",
7820 G_CALLBACK(text_clicked), compose);
7821 g_signal_connect(G_OBJECT(text), "popup-menu",
7822 G_CALLBACK(compose_popup_menu), compose);
7823 g_signal_connect(G_OBJECT(subject_entry), "changed",
7824 G_CALLBACK(compose_changed_cb), compose);
7825 g_signal_connect(G_OBJECT(subject_entry), "activate",
7826 G_CALLBACK(compose_subject_entry_activated), compose);
7829 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7830 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7831 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7832 g_signal_connect(G_OBJECT(text), "drag_data_received",
7833 G_CALLBACK(compose_insert_drag_received_cb),
7835 g_signal_connect(G_OBJECT(text), "drag-drop",
7836 G_CALLBACK(compose_drag_drop),
7838 g_signal_connect(G_OBJECT(text), "key-press-event",
7839 G_CALLBACK(completion_set_focus_to_subject),
7841 gtk_widget_show_all(vbox);
7843 /* pane between attach clist and text */
7844 paned = gtk_vpaned_new();
7845 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7846 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7847 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7848 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7849 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7850 G_CALLBACK(compose_notebook_size_alloc), paned);
7852 gtk_widget_show_all(paned);
7855 if (prefs_common.textfont) {
7856 PangoFontDescription *font_desc;
7858 font_desc = pango_font_description_from_string
7859 (prefs_common.textfont);
7861 gtk_widget_modify_font(text, font_desc);
7862 pango_font_description_free(font_desc);
7866 gtk_action_group_add_actions(action_group, compose_popup_entries,
7867 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7868 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7869 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7870 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7871 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7872 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7873 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7875 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7877 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7878 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7879 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7881 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7883 undostruct = undo_init(text);
7884 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7887 address_completion_start(window);
7889 compose->window = window;
7890 compose->vbox = vbox;
7891 compose->menubar = menubar;
7892 compose->handlebox = handlebox;
7894 compose->vbox2 = vbox2;
7896 compose->paned = paned;
7898 compose->attach_label = attach_lab2;
7900 compose->notebook = notebook;
7901 compose->edit_vbox = edit_vbox;
7902 compose->ruler_hbox = ruler_hbox;
7903 compose->ruler = ruler;
7904 compose->scrolledwin = scrolledwin;
7905 compose->text = text;
7907 compose->focused_editable = NULL;
7909 compose->popupmenu = popupmenu;
7911 compose->tmpl_menu = tmpl_menu;
7913 compose->mode = mode;
7914 compose->rmode = mode;
7916 compose->targetinfo = NULL;
7917 compose->replyinfo = NULL;
7918 compose->fwdinfo = NULL;
7920 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7921 g_str_equal, (GDestroyNotify) g_free, NULL);
7923 compose->replyto = NULL;
7925 compose->bcc = NULL;
7926 compose->followup_to = NULL;
7928 compose->ml_post = NULL;
7930 compose->inreplyto = NULL;
7931 compose->references = NULL;
7932 compose->msgid = NULL;
7933 compose->boundary = NULL;
7935 compose->autowrap = prefs_common.autowrap;
7936 compose->autoindent = prefs_common.auto_indent;
7937 compose->use_signing = FALSE;
7938 compose->use_encryption = FALSE;
7939 compose->privacy_system = NULL;
7941 compose->modified = FALSE;
7943 compose->return_receipt = FALSE;
7945 compose->to_list = NULL;
7946 compose->newsgroup_list = NULL;
7948 compose->undostruct = undostruct;
7950 compose->sig_str = NULL;
7952 compose->exteditor_file = NULL;
7953 compose->exteditor_pid = -1;
7954 compose->exteditor_tag = -1;
7955 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7957 compose->folder_update_callback_id =
7958 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
7959 compose_update_folder_hook,
7960 (gpointer) compose);
7963 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7964 if (mode != COMPOSE_REDIRECT) {
7965 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7966 strcmp(prefs_common.dictionary, "")) {
7967 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7968 prefs_common.alt_dictionary,
7969 conv_get_locale_charset_str(),
7970 prefs_common.misspelled_col,
7971 prefs_common.check_while_typing,
7972 prefs_common.recheck_when_changing_dict,
7973 prefs_common.use_alternate,
7974 prefs_common.use_both_dicts,
7975 GTK_TEXT_VIEW(text),
7976 GTK_WINDOW(compose->window),
7977 compose_dict_changed,
7978 compose_spell_menu_changed,
7981 alertpanel_error(_("Spell checker could not "
7983 gtkaspell_checkers_strerror());
7984 gtkaspell_checkers_reset_error();
7986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7990 compose->gtkaspell = gtkaspell;
7991 compose_spell_menu_changed(compose);
7992 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7995 compose_select_account(compose, account, TRUE);
7997 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7998 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8000 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8001 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8003 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8004 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8006 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8007 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8009 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8010 if (account->protocol != A_NNTP)
8011 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8012 prefs_common_translated_header_name("To:"));
8014 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8015 prefs_common_translated_header_name("Newsgroups:"));
8017 #ifndef USE_NEW_ADDRBOOK
8018 addressbook_set_target_compose(compose);
8020 if (mode != COMPOSE_REDIRECT)
8021 compose_set_template_menu(compose);
8023 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8026 compose_list = g_list_append(compose_list, compose);
8028 if (!prefs_common.show_ruler)
8029 gtk_widget_hide(ruler_hbox);
8031 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8034 compose->priority = PRIORITY_NORMAL;
8035 compose_update_priority_menu_item(compose);
8037 compose_set_out_encoding(compose);
8040 compose_update_actions_menu(compose);
8042 /* Privacy Systems menu */
8043 compose_update_privacy_systems_menu(compose);
8045 activate_privacy_system(compose, account, TRUE);
8046 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8048 gtk_widget_realize(window);
8050 gtk_widget_show(window);
8056 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8061 GtkWidget *optmenubox;
8062 GtkWidget *fromlabel;
8065 GtkWidget *from_name = NULL;
8067 gint num = 0, def_menu = 0;
8069 accounts = account_get_list();
8070 cm_return_val_if_fail(accounts != NULL, NULL);
8072 optmenubox = gtk_event_box_new();
8073 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8074 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8076 hbox = gtk_hbox_new(FALSE, 4);
8077 from_name = gtk_entry_new();
8079 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8080 G_CALLBACK(compose_grab_focus_cb), compose);
8081 g_signal_connect_after(G_OBJECT(from_name), "activate",
8082 G_CALLBACK(from_name_activate_cb), optmenu);
8084 for (; accounts != NULL; accounts = accounts->next, num++) {
8085 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8086 gchar *name, *from = NULL;
8088 if (ac == compose->account) def_menu = num;
8090 name = g_markup_printf_escaped(_("<i>%s</i>"),
8093 if (ac == compose->account) {
8094 if (ac->name && *ac->name) {
8096 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8097 from = g_strdup_printf("%s <%s>",
8099 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8101 from = g_strdup_printf("%s",
8103 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8106 COMBOBOX_ADD(menu, name, ac->account_id);
8111 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8113 g_signal_connect(G_OBJECT(optmenu), "changed",
8114 G_CALLBACK(account_activated),
8116 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8117 G_CALLBACK(compose_entry_popup_extend),
8120 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8121 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8123 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8124 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8125 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8127 /* Putting only the GtkEntry into focus chain of parent hbox causes
8128 * the account selector combobox next to it to be unreachable when
8129 * navigating widgets in GtkTable with up/down arrow keys.
8130 * Note: gtk_widget_set_can_focus() was not enough. */
8132 l = g_list_prepend(l, from_name);
8133 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8136 CLAWS_SET_TIP(optmenubox,
8137 _("Account to use for this email"));
8138 CLAWS_SET_TIP(from_name,
8139 _("Sender address to be used"));
8141 compose->account_combo = optmenu;
8142 compose->from_name = from_name;
8147 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8149 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8150 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8151 Compose *compose = (Compose *) data;
8153 compose->priority = value;
8157 static void compose_reply_change_mode(Compose *compose,
8160 gboolean was_modified = compose->modified;
8162 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8164 cm_return_if_fail(compose->replyinfo != NULL);
8166 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8168 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8170 if (action == COMPOSE_REPLY_TO_ALL)
8172 if (action == COMPOSE_REPLY_TO_SENDER)
8174 if (action == COMPOSE_REPLY_TO_LIST)
8177 compose_remove_header_entries(compose);
8178 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8179 if (compose->account->set_autocc && compose->account->auto_cc)
8180 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8182 if (compose->account->set_autobcc && compose->account->auto_bcc)
8183 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8185 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8186 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8187 compose_show_first_last_header(compose, TRUE);
8188 compose->modified = was_modified;
8189 compose_set_title(compose);
8192 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8194 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8195 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8196 Compose *compose = (Compose *) data;
8199 compose_reply_change_mode(compose, value);
8202 static void compose_update_priority_menu_item(Compose * compose)
8204 GtkWidget *menuitem = NULL;
8205 switch (compose->priority) {
8206 case PRIORITY_HIGHEST:
8207 menuitem = gtk_ui_manager_get_widget
8208 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8211 menuitem = gtk_ui_manager_get_widget
8212 (compose->ui_manager, "/Menu/Options/Priority/High");
8214 case PRIORITY_NORMAL:
8215 menuitem = gtk_ui_manager_get_widget
8216 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8219 menuitem = gtk_ui_manager_get_widget
8220 (compose->ui_manager, "/Menu/Options/Priority/Low");
8222 case PRIORITY_LOWEST:
8223 menuitem = gtk_ui_manager_get_widget
8224 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8227 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8230 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8232 Compose *compose = (Compose *) data;
8234 gboolean can_sign = FALSE, can_encrypt = FALSE;
8236 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8238 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8241 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8242 g_free(compose->privacy_system);
8243 compose->privacy_system = NULL;
8244 if (systemid != NULL) {
8245 compose->privacy_system = g_strdup(systemid);
8247 can_sign = privacy_system_can_sign(systemid);
8248 can_encrypt = privacy_system_can_encrypt(systemid);
8251 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8253 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8254 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8257 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8259 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8260 GtkWidget *menuitem = NULL;
8261 GList *children, *amenu;
8262 gboolean can_sign = FALSE, can_encrypt = FALSE;
8263 gboolean found = FALSE;
8265 if (compose->privacy_system != NULL) {
8267 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8268 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8269 cm_return_if_fail(menuitem != NULL);
8271 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8274 while (amenu != NULL) {
8275 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8276 if (systemid != NULL) {
8277 if (strcmp(systemid, compose->privacy_system) == 0 &&
8278 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8279 menuitem = GTK_WIDGET(amenu->data);
8281 can_sign = privacy_system_can_sign(systemid);
8282 can_encrypt = privacy_system_can_encrypt(systemid);
8286 } else if (strlen(compose->privacy_system) == 0 &&
8287 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8288 menuitem = GTK_WIDGET(amenu->data);
8291 can_encrypt = FALSE;
8296 amenu = amenu->next;
8298 g_list_free(children);
8299 if (menuitem != NULL)
8300 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8302 if (warn && !found && strlen(compose->privacy_system)) {
8303 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8304 "will not be able to sign or encrypt this message."),
8305 compose->privacy_system);
8309 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8310 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8313 static void compose_set_out_encoding(Compose *compose)
8315 CharSet out_encoding;
8316 const gchar *branch = NULL;
8317 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8319 switch(out_encoding) {
8320 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8321 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8322 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8323 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8324 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8325 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8326 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8327 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8328 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8329 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8330 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8331 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8332 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8333 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8334 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8335 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8336 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8337 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8338 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8339 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8340 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8341 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8342 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8343 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8344 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8345 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8346 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8347 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8348 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8349 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8350 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8351 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8352 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8354 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8357 static void compose_set_template_menu(Compose *compose)
8359 GSList *tmpl_list, *cur;
8363 tmpl_list = template_get_config();
8365 menu = gtk_menu_new();
8367 gtk_menu_set_accel_group (GTK_MENU (menu),
8368 gtk_ui_manager_get_accel_group(compose->ui_manager));
8369 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8370 Template *tmpl = (Template *)cur->data;
8371 gchar *accel_path = NULL;
8372 item = gtk_menu_item_new_with_label(tmpl->name);
8373 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8374 g_signal_connect(G_OBJECT(item), "activate",
8375 G_CALLBACK(compose_template_activate_cb),
8377 g_object_set_data(G_OBJECT(item), "template", tmpl);
8378 gtk_widget_show(item);
8379 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8380 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8384 gtk_widget_show(menu);
8385 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8388 void compose_update_actions_menu(Compose *compose)
8390 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8393 static void compose_update_privacy_systems_menu(Compose *compose)
8395 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8396 GSList *systems, *cur;
8398 GtkWidget *system_none;
8400 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8401 GtkWidget *privacy_menu = gtk_menu_new();
8403 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8404 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8406 g_signal_connect(G_OBJECT(system_none), "activate",
8407 G_CALLBACK(compose_set_privacy_system_cb), compose);
8409 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8410 gtk_widget_show(system_none);
8412 systems = privacy_get_system_ids();
8413 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8414 gchar *systemid = cur->data;
8416 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8417 widget = gtk_radio_menu_item_new_with_label(group,
8418 privacy_system_get_name(systemid));
8419 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8420 g_strdup(systemid), g_free);
8421 g_signal_connect(G_OBJECT(widget), "activate",
8422 G_CALLBACK(compose_set_privacy_system_cb), compose);
8424 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8425 gtk_widget_show(widget);
8428 g_slist_free(systems);
8429 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8430 gtk_widget_show_all(privacy_menu);
8431 gtk_widget_show_all(privacy_menuitem);
8434 void compose_reflect_prefs_all(void)
8439 for (cur = compose_list; cur != NULL; cur = cur->next) {
8440 compose = (Compose *)cur->data;
8441 compose_set_template_menu(compose);
8445 void compose_reflect_prefs_pixmap_theme(void)
8450 for (cur = compose_list; cur != NULL; cur = cur->next) {
8451 compose = (Compose *)cur->data;
8452 toolbar_update(TOOLBAR_COMPOSE, compose);
8456 static const gchar *compose_quote_char_from_context(Compose *compose)
8458 const gchar *qmark = NULL;
8460 cm_return_val_if_fail(compose != NULL, NULL);
8462 switch (compose->mode) {
8463 /* use forward-specific quote char */
8464 case COMPOSE_FORWARD:
8465 case COMPOSE_FORWARD_AS_ATTACH:
8466 case COMPOSE_FORWARD_INLINE:
8467 if (compose->folder && compose->folder->prefs &&
8468 compose->folder->prefs->forward_with_format)
8469 qmark = compose->folder->prefs->forward_quotemark;
8470 else if (compose->account->forward_with_format)
8471 qmark = compose->account->forward_quotemark;
8473 qmark = prefs_common.fw_quotemark;
8476 /* use reply-specific quote char in all other modes */
8478 if (compose->folder && compose->folder->prefs &&
8479 compose->folder->prefs->reply_with_format)
8480 qmark = compose->folder->prefs->reply_quotemark;
8481 else if (compose->account->reply_with_format)
8482 qmark = compose->account->reply_quotemark;
8484 qmark = prefs_common.quotemark;
8488 if (qmark == NULL || *qmark == '\0')
8494 static void compose_template_apply(Compose *compose, Template *tmpl,
8498 GtkTextBuffer *buffer;
8502 gchar *parsed_str = NULL;
8503 gint cursor_pos = 0;
8504 const gchar *err_msg = _("The body of the template has an error at line %d.");
8507 /* process the body */
8509 text = GTK_TEXT_VIEW(compose->text);
8510 buffer = gtk_text_view_get_buffer(text);
8513 qmark = compose_quote_char_from_context(compose);
8515 if (compose->replyinfo != NULL) {
8518 gtk_text_buffer_set_text(buffer, "", -1);
8519 mark = gtk_text_buffer_get_insert(buffer);
8520 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8522 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8523 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8525 } else if (compose->fwdinfo != NULL) {
8528 gtk_text_buffer_set_text(buffer, "", -1);
8529 mark = gtk_text_buffer_get_insert(buffer);
8530 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8532 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8533 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8536 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8538 GtkTextIter start, end;
8541 gtk_text_buffer_get_start_iter(buffer, &start);
8542 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8543 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8545 /* clear the buffer now */
8547 gtk_text_buffer_set_text(buffer, "", -1);
8549 parsed_str = compose_quote_fmt(compose, dummyinfo,
8550 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8551 procmsg_msginfo_free( dummyinfo );
8557 gtk_text_buffer_set_text(buffer, "", -1);
8558 mark = gtk_text_buffer_get_insert(buffer);
8559 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8562 if (replace && parsed_str && compose->account->auto_sig)
8563 compose_insert_sig(compose, FALSE);
8565 if (replace && parsed_str) {
8566 gtk_text_buffer_get_start_iter(buffer, &iter);
8567 gtk_text_buffer_place_cursor(buffer, &iter);
8571 cursor_pos = quote_fmt_get_cursor_pos();
8572 compose->set_cursor_pos = cursor_pos;
8573 if (cursor_pos == -1)
8575 gtk_text_buffer_get_start_iter(buffer, &iter);
8576 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8577 gtk_text_buffer_place_cursor(buffer, &iter);
8580 /* process the other fields */
8582 compose_template_apply_fields(compose, tmpl);
8583 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8584 quote_fmt_reset_vartable();
8585 compose_changed_cb(NULL, compose);
8588 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8589 gtkaspell_highlight_all(compose->gtkaspell);
8593 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8595 MsgInfo* dummyinfo = NULL;
8596 MsgInfo *msginfo = NULL;
8599 if (compose->replyinfo != NULL)
8600 msginfo = compose->replyinfo;
8601 else if (compose->fwdinfo != NULL)
8602 msginfo = compose->fwdinfo;
8604 dummyinfo = compose_msginfo_new_from_compose(compose);
8605 msginfo = dummyinfo;
8608 if (tmpl->from && *tmpl->from != '\0') {
8610 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8611 compose->gtkaspell);
8613 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8615 quote_fmt_scan_string(tmpl->from);
8618 buf = quote_fmt_get_buffer();
8620 alertpanel_error(_("Template From format error."));
8622 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8626 if (tmpl->to && *tmpl->to != '\0') {
8628 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8629 compose->gtkaspell);
8631 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8633 quote_fmt_scan_string(tmpl->to);
8636 buf = quote_fmt_get_buffer();
8638 alertpanel_error(_("Template To format error."));
8640 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8644 if (tmpl->cc && *tmpl->cc != '\0') {
8646 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8647 compose->gtkaspell);
8649 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8651 quote_fmt_scan_string(tmpl->cc);
8654 buf = quote_fmt_get_buffer();
8656 alertpanel_error(_("Template Cc format error."));
8658 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8662 if (tmpl->bcc && *tmpl->bcc != '\0') {
8664 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8665 compose->gtkaspell);
8667 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8669 quote_fmt_scan_string(tmpl->bcc);
8672 buf = quote_fmt_get_buffer();
8674 alertpanel_error(_("Template Bcc format error."));
8676 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8680 if (tmpl->replyto && *tmpl->replyto != '\0') {
8682 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8683 compose->gtkaspell);
8685 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8687 quote_fmt_scan_string(tmpl->replyto);
8690 buf = quote_fmt_get_buffer();
8692 alertpanel_error(_("Template Reply-To format error."));
8694 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8698 /* process the subject */
8699 if (tmpl->subject && *tmpl->subject != '\0') {
8701 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8702 compose->gtkaspell);
8704 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8706 quote_fmt_scan_string(tmpl->subject);
8709 buf = quote_fmt_get_buffer();
8711 alertpanel_error(_("Template subject format error."));
8713 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8717 procmsg_msginfo_free( dummyinfo );
8720 static void compose_destroy(Compose *compose)
8722 GtkAllocation allocation;
8723 GtkTextBuffer *buffer;
8724 GtkClipboard *clipboard;
8726 compose_list = g_list_remove(compose_list, compose);
8728 if (compose->updating) {
8729 debug_print("danger, not destroying anything now\n");
8730 compose->deferred_destroy = TRUE;
8734 /* NOTE: address_completion_end() does nothing with the window
8735 * however this may change. */
8736 address_completion_end(compose->window);
8738 slist_free_strings_full(compose->to_list);
8739 slist_free_strings_full(compose->newsgroup_list);
8740 slist_free_strings_full(compose->header_list);
8742 slist_free_strings_full(extra_headers);
8743 extra_headers = NULL;
8745 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8747 g_hash_table_destroy(compose->email_hashtable);
8749 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8750 compose->folder_update_callback_id);
8752 procmsg_msginfo_free(compose->targetinfo);
8753 procmsg_msginfo_free(compose->replyinfo);
8754 procmsg_msginfo_free(compose->fwdinfo);
8756 g_free(compose->replyto);
8757 g_free(compose->cc);
8758 g_free(compose->bcc);
8759 g_free(compose->newsgroups);
8760 g_free(compose->followup_to);
8762 g_free(compose->ml_post);
8764 g_free(compose->inreplyto);
8765 g_free(compose->references);
8766 g_free(compose->msgid);
8767 g_free(compose->boundary);
8769 g_free(compose->redirect_filename);
8770 if (compose->undostruct)
8771 undo_destroy(compose->undostruct);
8773 g_free(compose->sig_str);
8775 g_free(compose->exteditor_file);
8777 g_free(compose->orig_charset);
8779 g_free(compose->privacy_system);
8781 #ifndef USE_NEW_ADDRBOOK
8782 if (addressbook_get_target_compose() == compose)
8783 addressbook_set_target_compose(NULL);
8786 if (compose->gtkaspell) {
8787 gtkaspell_delete(compose->gtkaspell);
8788 compose->gtkaspell = NULL;
8792 if (!compose->batch) {
8793 gtk_widget_get_allocation(compose->window, &allocation);
8794 prefs_common.compose_width = allocation.width;
8795 prefs_common.compose_height = allocation.height;
8798 if (!gtk_widget_get_parent(compose->paned))
8799 gtk_widget_destroy(compose->paned);
8800 gtk_widget_destroy(compose->popupmenu);
8802 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8803 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8804 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8806 gtk_widget_destroy(compose->window);
8807 toolbar_destroy(compose->toolbar);
8808 g_free(compose->toolbar);
8809 cm_mutex_free(compose->mutex);
8813 static void compose_attach_info_free(AttachInfo *ainfo)
8815 g_free(ainfo->file);
8816 g_free(ainfo->content_type);
8817 g_free(ainfo->name);
8818 g_free(ainfo->charset);
8822 static void compose_attach_update_label(Compose *compose)
8827 GtkTreeModel *model;
8832 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8833 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8834 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8838 while(gtk_tree_model_iter_next(model, &iter))
8841 text = g_strdup_printf("(%d)", i);
8842 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8846 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8848 Compose *compose = (Compose *)data;
8849 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8850 GtkTreeSelection *selection;
8852 GtkTreeModel *model;
8854 selection = gtk_tree_view_get_selection(tree_view);
8855 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8860 for (cur = sel; cur != NULL; cur = cur->next) {
8861 GtkTreePath *path = cur->data;
8862 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8865 gtk_tree_path_free(path);
8868 for (cur = sel; cur != NULL; cur = cur->next) {
8869 GtkTreeRowReference *ref = cur->data;
8870 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8873 if (gtk_tree_model_get_iter(model, &iter, path))
8874 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8876 gtk_tree_path_free(path);
8877 gtk_tree_row_reference_free(ref);
8881 compose_attach_update_label(compose);
8884 static struct _AttachProperty
8887 GtkWidget *mimetype_entry;
8888 GtkWidget *encoding_optmenu;
8889 GtkWidget *path_entry;
8890 GtkWidget *filename_entry;
8892 GtkWidget *cancel_btn;
8895 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8897 gtk_tree_path_free((GtkTreePath *)ptr);
8900 static void compose_attach_property(GtkAction *action, gpointer data)
8902 Compose *compose = (Compose *)data;
8903 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8905 GtkComboBox *optmenu;
8906 GtkTreeSelection *selection;
8908 GtkTreeModel *model;
8911 static gboolean cancelled;
8913 /* only if one selected */
8914 selection = gtk_tree_view_get_selection(tree_view);
8915 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8918 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8922 path = (GtkTreePath *) sel->data;
8923 gtk_tree_model_get_iter(model, &iter, path);
8924 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8927 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8933 if (!attach_prop.window)
8934 compose_attach_property_create(&cancelled);
8935 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8936 gtk_widget_grab_focus(attach_prop.ok_btn);
8937 gtk_widget_show(attach_prop.window);
8938 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8939 GTK_WINDOW(compose->window));
8941 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8942 if (ainfo->encoding == ENC_UNKNOWN)
8943 combobox_select_by_data(optmenu, ENC_BASE64);
8945 combobox_select_by_data(optmenu, ainfo->encoding);
8947 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8948 ainfo->content_type ? ainfo->content_type : "");
8949 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8950 ainfo->file ? ainfo->file : "");
8951 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8952 ainfo->name ? ainfo->name : "");
8955 const gchar *entry_text;
8957 gchar *cnttype = NULL;
8964 gtk_widget_hide(attach_prop.window);
8965 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8970 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8971 if (*entry_text != '\0') {
8974 text = g_strstrip(g_strdup(entry_text));
8975 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8976 cnttype = g_strdup(text);
8979 alertpanel_error(_("Invalid MIME type."));
8985 ainfo->encoding = combobox_get_active_data(optmenu);
8987 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8988 if (*entry_text != '\0') {
8989 if (is_file_exist(entry_text) &&
8990 (size = get_file_size(entry_text)) > 0)
8991 file = g_strdup(entry_text);
8994 (_("File doesn't exist or is empty."));
9000 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9001 if (*entry_text != '\0') {
9002 g_free(ainfo->name);
9003 ainfo->name = g_strdup(entry_text);
9007 g_free(ainfo->content_type);
9008 ainfo->content_type = cnttype;
9011 g_free(ainfo->file);
9015 ainfo->size = (goffset)size;
9017 /* update tree store */
9018 text = to_human_readable(ainfo->size);
9019 gtk_tree_model_get_iter(model, &iter, path);
9020 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9021 COL_MIMETYPE, ainfo->content_type,
9023 COL_NAME, ainfo->name,
9024 COL_CHARSET, ainfo->charset,
9030 gtk_tree_path_free(path);
9033 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9035 label = gtk_label_new(str); \
9036 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9037 GTK_FILL, 0, 0, 0); \
9038 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9040 entry = gtk_entry_new(); \
9041 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9042 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9045 static void compose_attach_property_create(gboolean *cancelled)
9051 GtkWidget *mimetype_entry;
9054 GtkListStore *optmenu_menu;
9055 GtkWidget *path_entry;
9056 GtkWidget *filename_entry;
9059 GtkWidget *cancel_btn;
9060 GList *mime_type_list, *strlist;
9063 debug_print("Creating attach_property window...\n");
9065 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9066 gtk_widget_set_size_request(window, 480, -1);
9067 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9068 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9069 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9070 g_signal_connect(G_OBJECT(window), "delete_event",
9071 G_CALLBACK(attach_property_delete_event),
9073 g_signal_connect(G_OBJECT(window), "key_press_event",
9074 G_CALLBACK(attach_property_key_pressed),
9077 vbox = gtk_vbox_new(FALSE, 8);
9078 gtk_container_add(GTK_CONTAINER(window), vbox);
9080 table = gtk_table_new(4, 2, FALSE);
9081 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9082 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9083 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9085 label = gtk_label_new(_("MIME type"));
9086 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9088 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9089 #if !GTK_CHECK_VERSION(2, 24, 0)
9090 mimetype_entry = gtk_combo_box_entry_new_text();
9092 mimetype_entry = gtk_combo_box_text_new_with_entry();
9094 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9095 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9097 /* stuff with list */
9098 mime_type_list = procmime_get_mime_type_list();
9100 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9101 MimeType *type = (MimeType *) mime_type_list->data;
9104 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9106 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9109 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9110 (GCompareFunc)strcmp2);
9113 for (mime_type_list = strlist; mime_type_list != NULL;
9114 mime_type_list = mime_type_list->next) {
9115 #if !GTK_CHECK_VERSION(2, 24, 0)
9116 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9118 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9120 g_free(mime_type_list->data);
9122 g_list_free(strlist);
9123 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9124 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9126 label = gtk_label_new(_("Encoding"));
9127 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9129 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9131 hbox = gtk_hbox_new(FALSE, 0);
9132 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9133 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9135 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9136 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9138 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9139 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9140 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9141 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9142 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9144 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9146 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9147 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9149 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9150 &ok_btn, GTK_STOCK_OK,
9152 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9153 gtk_widget_grab_default(ok_btn);
9155 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9156 G_CALLBACK(attach_property_ok),
9158 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9159 G_CALLBACK(attach_property_cancel),
9162 gtk_widget_show_all(vbox);
9164 attach_prop.window = window;
9165 attach_prop.mimetype_entry = mimetype_entry;
9166 attach_prop.encoding_optmenu = optmenu;
9167 attach_prop.path_entry = path_entry;
9168 attach_prop.filename_entry = filename_entry;
9169 attach_prop.ok_btn = ok_btn;
9170 attach_prop.cancel_btn = cancel_btn;
9173 #undef SET_LABEL_AND_ENTRY
9175 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9181 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9187 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9188 gboolean *cancelled)
9196 static gboolean attach_property_key_pressed(GtkWidget *widget,
9198 gboolean *cancelled)
9200 if (event && event->keyval == GDK_KEY_Escape) {
9204 if (event && event->keyval == GDK_KEY_Return) {
9212 static void compose_exec_ext_editor(Compose *compose)
9219 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9220 G_DIR_SEPARATOR, compose);
9222 if (pipe(pipe_fds) < 0) {
9228 if ((pid = fork()) < 0) {
9235 /* close the write side of the pipe */
9238 compose->exteditor_file = g_strdup(tmp);
9239 compose->exteditor_pid = pid;
9241 compose_set_ext_editor_sensitive(compose, FALSE);
9244 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9246 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9248 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9252 } else { /* process-monitoring process */
9258 /* close the read side of the pipe */
9261 if (compose_write_body_to_file(compose, tmp) < 0) {
9262 fd_write_all(pipe_fds[1], "2\n", 2);
9266 pid_ed = compose_exec_ext_editor_real(tmp);
9268 fd_write_all(pipe_fds[1], "1\n", 2);
9272 /* wait until editor is terminated */
9273 waitpid(pid_ed, NULL, 0);
9275 fd_write_all(pipe_fds[1], "0\n", 2);
9282 #endif /* G_OS_UNIX */
9286 static gint compose_exec_ext_editor_real(const gchar *file)
9293 cm_return_val_if_fail(file != NULL, -1);
9295 if ((pid = fork()) < 0) {
9300 if (pid != 0) return pid;
9302 /* grandchild process */
9304 if (setpgid(0, getppid()))
9307 if (prefs_common_get_ext_editor_cmd() &&
9308 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9309 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9310 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9312 if (prefs_common_get_ext_editor_cmd())
9313 g_warning("External editor command-line is invalid: '%s'\n",
9314 prefs_common_get_ext_editor_cmd());
9315 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9318 cmdline = strsplit_with_quote(buf, " ", 1024);
9319 execvp(cmdline[0], cmdline);
9322 g_strfreev(cmdline);
9327 static gboolean compose_ext_editor_kill(Compose *compose)
9329 pid_t pgid = compose->exteditor_pid * -1;
9332 ret = kill(pgid, 0);
9334 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9338 msg = g_strdup_printf
9339 (_("The external editor is still working.\n"
9340 "Force terminating the process?\n"
9341 "process group id: %d"), -pgid);
9342 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9343 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9347 if (val == G_ALERTALTERNATE) {
9348 g_source_remove(compose->exteditor_tag);
9349 g_io_channel_shutdown(compose->exteditor_ch,
9351 g_io_channel_unref(compose->exteditor_ch);
9353 if (kill(pgid, SIGTERM) < 0) perror("kill");
9354 waitpid(compose->exteditor_pid, NULL, 0);
9356 g_warning("Terminated process group id: %d", -pgid);
9357 g_warning("Temporary file: %s",
9358 compose->exteditor_file);
9360 compose_set_ext_editor_sensitive(compose, TRUE);
9362 g_free(compose->exteditor_file);
9363 compose->exteditor_file = NULL;
9364 compose->exteditor_pid = -1;
9365 compose->exteditor_ch = NULL;
9366 compose->exteditor_tag = -1;
9374 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9378 Compose *compose = (Compose *)data;
9381 debug_print("Compose: input from monitoring process\n");
9383 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9385 g_io_channel_shutdown(source, FALSE, NULL);
9386 g_io_channel_unref(source);
9388 waitpid(compose->exteditor_pid, NULL, 0);
9390 if (buf[0] == '0') { /* success */
9391 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9392 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9393 GtkTextIter start, end;
9396 gtk_text_buffer_set_text(buffer, "", -1);
9397 compose_insert_file(compose, compose->exteditor_file);
9398 compose_changed_cb(NULL, compose);
9399 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9401 if (claws_unlink(compose->exteditor_file) < 0)
9402 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9404 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9405 gtk_text_buffer_get_start_iter(buffer, &start);
9406 gtk_text_buffer_get_end_iter(buffer, &end);
9407 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9408 if (chars && strlen(chars) > 0)
9409 compose->modified = TRUE;
9411 } else if (buf[0] == '1') { /* failed */
9412 g_warning("Couldn't exec external editor\n");
9413 if (claws_unlink(compose->exteditor_file) < 0)
9414 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9415 } else if (buf[0] == '2') {
9416 g_warning("Couldn't write to file\n");
9417 } else if (buf[0] == '3') {
9418 g_warning("Pipe read failed\n");
9421 compose_set_ext_editor_sensitive(compose, TRUE);
9423 g_free(compose->exteditor_file);
9424 compose->exteditor_file = NULL;
9425 compose->exteditor_pid = -1;
9426 compose->exteditor_ch = NULL;
9427 compose->exteditor_tag = -1;
9432 static void compose_set_ext_editor_sensitive(Compose *compose,
9435 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9436 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9437 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9438 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9439 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9440 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9441 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9442 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9444 gtk_widget_set_sensitive(compose->text, sensitive);
9445 if (compose->toolbar->send_btn)
9446 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9447 if (compose->toolbar->sendl_btn)
9448 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9449 if (compose->toolbar->draft_btn)
9450 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9451 if (compose->toolbar->insert_btn)
9452 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9453 if (compose->toolbar->sig_btn)
9454 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9455 if (compose->toolbar->exteditor_btn)
9456 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9457 if (compose->toolbar->linewrap_current_btn)
9458 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9459 if (compose->toolbar->linewrap_all_btn)
9460 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9462 #endif /* G_OS_UNIX */
9465 * compose_undo_state_changed:
9467 * Change the sensivity of the menuentries undo and redo
9469 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9470 gint redo_state, gpointer data)
9472 Compose *compose = (Compose *)data;
9474 switch (undo_state) {
9475 case UNDO_STATE_TRUE:
9476 if (!undostruct->undo_state) {
9477 undostruct->undo_state = TRUE;
9478 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9481 case UNDO_STATE_FALSE:
9482 if (undostruct->undo_state) {
9483 undostruct->undo_state = FALSE;
9484 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9487 case UNDO_STATE_UNCHANGED:
9489 case UNDO_STATE_REFRESH:
9490 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9493 g_warning("Undo state not recognized");
9497 switch (redo_state) {
9498 case UNDO_STATE_TRUE:
9499 if (!undostruct->redo_state) {
9500 undostruct->redo_state = TRUE;
9501 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9504 case UNDO_STATE_FALSE:
9505 if (undostruct->redo_state) {
9506 undostruct->redo_state = FALSE;
9507 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9510 case UNDO_STATE_UNCHANGED:
9512 case UNDO_STATE_REFRESH:
9513 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9516 g_warning("Redo state not recognized");
9521 /* callback functions */
9523 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9524 GtkAllocation *allocation,
9527 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9530 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9531 * includes "non-client" (windows-izm) in calculation, so this calculation
9532 * may not be accurate.
9534 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9535 GtkAllocation *allocation,
9536 GtkSHRuler *shruler)
9538 if (prefs_common.show_ruler) {
9539 gint char_width = 0, char_height = 0;
9540 gint line_width_in_chars;
9542 gtkut_get_font_size(GTK_WIDGET(widget),
9543 &char_width, &char_height);
9544 line_width_in_chars =
9545 (allocation->width - allocation->x) / char_width;
9547 /* got the maximum */
9548 gtk_shruler_set_range(GTK_SHRULER(shruler),
9549 0.0, line_width_in_chars, 0);
9558 ComposePrefType type;
9559 gboolean entry_marked;
9562 static void account_activated(GtkComboBox *optmenu, gpointer data)
9564 Compose *compose = (Compose *)data;
9567 gchar *folderidentifier;
9568 gint account_id = 0;
9571 GSList *list, *saved_list = NULL;
9572 HeaderEntryState *state;
9573 GtkRcStyle *style = NULL;
9574 #if !GTK_CHECK_VERSION(3, 0, 0)
9575 static GdkColor yellow;
9576 static gboolean color_set = FALSE;
9578 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9581 /* Get ID of active account in the combo box */
9582 menu = gtk_combo_box_get_model(optmenu);
9583 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9584 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9586 ac = account_find_from_id(account_id);
9587 cm_return_if_fail(ac != NULL);
9589 if (ac != compose->account) {
9590 compose_select_account(compose, ac, FALSE);
9592 for (list = compose->header_list; list; list = list->next) {
9593 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9595 if (hentry->type == PREF_ACCOUNT || !list->next) {
9596 compose_destroy_headerentry(compose, hentry);
9600 state = g_malloc0(sizeof(HeaderEntryState));
9601 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9602 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9603 state->entry = gtk_editable_get_chars(
9604 GTK_EDITABLE(hentry->entry), 0, -1);
9605 state->type = hentry->type;
9607 #if !GTK_CHECK_VERSION(3, 0, 0)
9609 gdk_color_parse("#f5f6be", &yellow);
9610 color_set = gdk_colormap_alloc_color(
9611 gdk_colormap_get_system(),
9612 &yellow, FALSE, TRUE);
9616 style = gtk_widget_get_modifier_style(hentry->entry);
9617 state->entry_marked = gdk_color_equal(&yellow,
9618 &style->base[GTK_STATE_NORMAL]);
9620 saved_list = g_slist_append(saved_list, state);
9621 compose_destroy_headerentry(compose, hentry);
9624 compose->header_last = NULL;
9625 g_slist_free(compose->header_list);
9626 compose->header_list = NULL;
9627 compose->header_nextrow = 1;
9628 compose_create_header_entry(compose);
9630 if (ac->set_autocc && ac->auto_cc)
9631 compose_entry_append(compose, ac->auto_cc,
9632 COMPOSE_CC, PREF_ACCOUNT);
9634 if (ac->set_autobcc && ac->auto_bcc)
9635 compose_entry_append(compose, ac->auto_bcc,
9636 COMPOSE_BCC, PREF_ACCOUNT);
9638 if (ac->set_autoreplyto && ac->auto_replyto)
9639 compose_entry_append(compose, ac->auto_replyto,
9640 COMPOSE_REPLYTO, PREF_ACCOUNT);
9642 for (list = saved_list; list; list = list->next) {
9643 state = (HeaderEntryState *) list->data;
9645 compose_add_header_entry(compose, state->header,
9646 state->entry, state->type);
9647 if (state->entry_marked)
9648 compose_entry_mark_default_to(compose, state->entry);
9650 g_free(state->header);
9651 g_free(state->entry);
9654 g_slist_free(saved_list);
9656 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9657 (ac->protocol == A_NNTP) ?
9658 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9661 /* Set message save folder */
9662 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9663 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9665 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9666 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9668 compose_set_save_to(compose, NULL);
9669 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9670 folderidentifier = folder_item_get_identifier(account_get_special_folder
9671 (compose->account, F_OUTBOX));
9672 compose_set_save_to(compose, folderidentifier);
9673 g_free(folderidentifier);
9677 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9678 GtkTreeViewColumn *column, Compose *compose)
9680 compose_attach_property(NULL, compose);
9683 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9686 Compose *compose = (Compose *)data;
9687 GtkTreeSelection *attach_selection;
9688 gint attach_nr_selected;
9690 if (!event) return FALSE;
9692 if (event->button == 3) {
9693 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9694 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9696 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9697 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9699 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9700 NULL, NULL, event->button, event->time);
9707 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9710 Compose *compose = (Compose *)data;
9712 if (!event) return FALSE;
9714 switch (event->keyval) {
9715 case GDK_KEY_Delete:
9716 compose_attach_remove_selected(NULL, compose);
9722 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9724 toolbar_comp_set_sensitive(compose, allow);
9725 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9726 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9728 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9730 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9731 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9732 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9734 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9738 static void compose_send_cb(GtkAction *action, gpointer data)
9740 Compose *compose = (Compose *)data;
9742 if (prefs_common.work_offline &&
9743 !inc_offline_should_override(TRUE,
9744 _("Claws Mail needs network access in order "
9745 "to send this email.")))
9748 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9749 g_source_remove(compose->draft_timeout_tag);
9750 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9753 compose_send(compose);
9756 static void compose_send_later_cb(GtkAction *action, gpointer data)
9758 Compose *compose = (Compose *)data;
9762 compose_allow_user_actions(compose, FALSE);
9763 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9764 compose_allow_user_actions(compose, TRUE);
9768 compose_close(compose);
9769 } else if (val == -1) {
9770 alertpanel_error(_("Could not queue message."));
9771 } else if (val == -2) {
9772 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9773 } else if (val == -3) {
9774 if (privacy_peek_error())
9775 alertpanel_error(_("Could not queue message for sending:\n\n"
9776 "Signature failed: %s"), privacy_get_error());
9777 } else if (val == -4) {
9778 alertpanel_error(_("Could not queue message for sending:\n\n"
9779 "Charset conversion failed."));
9780 } else if (val == -5) {
9781 alertpanel_error(_("Could not queue message for sending:\n\n"
9782 "Couldn't get recipient encryption key."));
9783 } else if (val == -6) {
9786 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9789 #define DRAFTED_AT_EXIT "drafted_at_exit"
9790 static void compose_register_draft(MsgInfo *info)
9792 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9793 DRAFTED_AT_EXIT, NULL);
9794 FILE *fp = g_fopen(filepath, "ab");
9797 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9805 gboolean compose_draft (gpointer data, guint action)
9807 Compose *compose = (Compose *)data;
9812 MsgFlags flag = {0, 0};
9813 static gboolean lock = FALSE;
9814 MsgInfo *newmsginfo;
9816 gboolean target_locked = FALSE;
9817 gboolean err = FALSE;
9819 if (lock) return FALSE;
9821 if (compose->sending)
9824 draft = account_get_special_folder(compose->account, F_DRAFT);
9825 cm_return_val_if_fail(draft != NULL, FALSE);
9827 if (!g_mutex_trylock(compose->mutex)) {
9828 /* we don't want to lock the mutex once it's available,
9829 * because as the only other part of compose.c locking
9830 * it is compose_close - which means once unlocked,
9831 * the compose struct will be freed */
9832 debug_print("couldn't lock mutex, probably sending\n");
9838 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9839 G_DIR_SEPARATOR, compose);
9840 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9841 FILE_OP_ERROR(tmp, "fopen");
9845 /* chmod for security */
9846 if (change_file_mode_rw(fp, tmp) < 0) {
9847 FILE_OP_ERROR(tmp, "chmod");
9848 g_warning("can't change file mode\n");
9851 /* Save draft infos */
9852 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9853 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9855 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9856 gchar *savefolderid;
9858 savefolderid = compose_get_save_to(compose);
9859 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9860 g_free(savefolderid);
9862 if (compose->return_receipt) {
9863 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9865 if (compose->privacy_system) {
9866 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9867 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9868 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9871 /* Message-ID of message replying to */
9872 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9873 gchar *folderid = NULL;
9875 if (compose->replyinfo->folder)
9876 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9877 if (folderid == NULL)
9878 folderid = g_strdup("NULL");
9880 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9883 /* Message-ID of message forwarding to */
9884 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9885 gchar *folderid = NULL;
9887 if (compose->fwdinfo->folder)
9888 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9889 if (folderid == NULL)
9890 folderid = g_strdup("NULL");
9892 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9896 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9897 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9899 sheaders = compose_get_manual_headers_info(compose);
9900 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9903 /* end of headers */
9904 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9911 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9915 if (fclose(fp) == EOF) {
9919 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9920 if (compose->targetinfo) {
9921 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9923 flag.perm_flags |= MSG_LOCKED;
9925 flag.tmp_flags = MSG_DRAFT;
9927 folder_item_scan(draft);
9928 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9929 MsgInfo *tmpinfo = NULL;
9930 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9931 if (compose->msgid) {
9932 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9935 msgnum = tmpinfo->msgnum;
9936 procmsg_msginfo_free(tmpinfo);
9937 debug_print("got draft msgnum %d from scanning\n", msgnum);
9939 debug_print("didn't get draft msgnum after scanning\n");
9942 debug_print("got draft msgnum %d from adding\n", msgnum);
9948 if (action != COMPOSE_AUTO_SAVE) {
9949 if (action != COMPOSE_DRAFT_FOR_EXIT)
9950 alertpanel_error(_("Could not save draft."));
9953 gtkut_window_popup(compose->window);
9954 val = alertpanel_full(_("Could not save draft"),
9955 _("Could not save draft.\n"
9956 "Do you want to cancel exit or discard this email?"),
9957 _("_Cancel exit"), _("_Discard email"), NULL,
9958 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9959 if (val == G_ALERTALTERNATE) {
9961 g_mutex_unlock(compose->mutex); /* must be done before closing */
9962 compose_close(compose);
9966 g_mutex_unlock(compose->mutex); /* must be done before closing */
9975 if (compose->mode == COMPOSE_REEDIT) {
9976 compose_remove_reedit_target(compose, TRUE);
9979 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9982 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9984 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9986 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9987 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9988 procmsg_msginfo_set_flags(newmsginfo, 0,
9989 MSG_HAS_ATTACHMENT);
9991 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9992 compose_register_draft(newmsginfo);
9994 procmsg_msginfo_free(newmsginfo);
9997 folder_item_scan(draft);
9999 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10001 g_mutex_unlock(compose->mutex); /* must be done before closing */
10002 compose_close(compose);
10008 path = folder_item_fetch_msg(draft, msgnum);
10009 if (path == NULL) {
10010 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10013 if (g_stat(path, &s) < 0) {
10014 FILE_OP_ERROR(path, "stat");
10020 procmsg_msginfo_free(compose->targetinfo);
10021 compose->targetinfo = procmsg_msginfo_new();
10022 compose->targetinfo->msgnum = msgnum;
10023 compose->targetinfo->size = (goffset)s.st_size;
10024 compose->targetinfo->mtime = s.st_mtime;
10025 compose->targetinfo->folder = draft;
10027 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10028 compose->mode = COMPOSE_REEDIT;
10030 if (action == COMPOSE_AUTO_SAVE) {
10031 compose->autosaved_draft = compose->targetinfo;
10033 compose->modified = FALSE;
10034 compose_set_title(compose);
10038 g_mutex_unlock(compose->mutex);
10042 void compose_clear_exit_drafts(void)
10044 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10045 DRAFTED_AT_EXIT, NULL);
10046 if (is_file_exist(filepath))
10047 claws_unlink(filepath);
10052 void compose_reopen_exit_drafts(void)
10054 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10055 DRAFTED_AT_EXIT, NULL);
10056 FILE *fp = g_fopen(filepath, "rb");
10060 while (fgets(buf, sizeof(buf), fp)) {
10061 gchar **parts = g_strsplit(buf, "\t", 2);
10062 const gchar *folder = parts[0];
10063 int msgnum = parts[1] ? atoi(parts[1]):-1;
10065 if (folder && *folder && msgnum > -1) {
10066 FolderItem *item = folder_find_item_from_identifier(folder);
10067 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10069 compose_reedit(info, FALSE);
10076 compose_clear_exit_drafts();
10079 static void compose_save_cb(GtkAction *action, gpointer data)
10081 Compose *compose = (Compose *)data;
10082 compose_draft(compose, COMPOSE_KEEP_EDITING);
10083 compose->rmode = COMPOSE_REEDIT;
10086 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10088 if (compose && file_list) {
10091 for ( tmp = file_list; tmp; tmp = tmp->next) {
10092 gchar *file = (gchar *) tmp->data;
10093 gchar *utf8_filename = conv_filename_to_utf8(file);
10094 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10095 compose_changed_cb(NULL, compose);
10100 g_free(utf8_filename);
10105 static void compose_attach_cb(GtkAction *action, gpointer data)
10107 Compose *compose = (Compose *)data;
10110 if (compose->redirect_filename != NULL)
10113 /* Set focus_window properly, in case we were called via popup menu,
10114 * which unsets it (via focus_out_event callback on compose window). */
10115 manage_window_focus_in(compose->window, NULL, NULL);
10117 file_list = filesel_select_multiple_files_open(_("Select file"));
10120 compose_attach_from_list(compose, file_list, TRUE);
10121 g_list_free(file_list);
10125 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10127 Compose *compose = (Compose *)data;
10129 gint files_inserted = 0;
10131 file_list = filesel_select_multiple_files_open(_("Select file"));
10136 for ( tmp = file_list; tmp; tmp = tmp->next) {
10137 gchar *file = (gchar *) tmp->data;
10138 gchar *filedup = g_strdup(file);
10139 gchar *shortfile = g_path_get_basename(filedup);
10140 ComposeInsertResult res;
10141 /* insert the file if the file is short or if the user confirmed that
10142 he/she wants to insert the large file */
10143 res = compose_insert_file(compose, file);
10144 if (res == COMPOSE_INSERT_READ_ERROR) {
10145 alertpanel_error(_("File '%s' could not be read."), shortfile);
10146 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10147 alertpanel_error(_("File '%s' contained invalid characters\n"
10148 "for the current encoding, insertion may be incorrect."),
10150 } else if (res == COMPOSE_INSERT_SUCCESS)
10157 g_list_free(file_list);
10161 if (files_inserted > 0 && compose->gtkaspell &&
10162 compose->gtkaspell->check_while_typing)
10163 gtkaspell_highlight_all(compose->gtkaspell);
10167 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10169 Compose *compose = (Compose *)data;
10171 compose_insert_sig(compose, FALSE);
10174 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10176 Compose *compose = (Compose *)data;
10178 compose_insert_sig(compose, TRUE);
10181 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10185 Compose *compose = (Compose *)data;
10187 gtkut_widget_get_uposition(widget, &x, &y);
10188 if (!compose->batch) {
10189 prefs_common.compose_x = x;
10190 prefs_common.compose_y = y;
10192 if (compose->sending || compose->updating)
10194 compose_close_cb(NULL, compose);
10198 void compose_close_toolbar(Compose *compose)
10200 compose_close_cb(NULL, compose);
10203 static gboolean compose_can_autosave(Compose *compose)
10205 if (compose->privacy_system && compose->use_encryption)
10206 return prefs_common.autosave && prefs_common.autosave_encrypted;
10208 return prefs_common.autosave;
10211 static void compose_close_cb(GtkAction *action, gpointer data)
10213 Compose *compose = (Compose *)data;
10217 if (compose->exteditor_tag != -1) {
10218 if (!compose_ext_editor_kill(compose))
10223 if (compose->modified) {
10224 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10225 if (!g_mutex_trylock(compose->mutex)) {
10226 /* we don't want to lock the mutex once it's available,
10227 * because as the only other part of compose.c locking
10228 * it is compose_close - which means once unlocked,
10229 * the compose struct will be freed */
10230 debug_print("couldn't lock mutex, probably sending\n");
10234 val = alertpanel(_("Discard message"),
10235 _("This message has been modified. Discard it?"),
10236 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10238 val = alertpanel(_("Save changes"),
10239 _("This message has been modified. Save the latest changes?"),
10240 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10242 g_mutex_unlock(compose->mutex);
10244 case G_ALERTDEFAULT:
10245 if (compose_can_autosave(compose) && !reedit)
10246 compose_remove_draft(compose);
10248 case G_ALERTALTERNATE:
10249 compose_draft(data, COMPOSE_QUIT_EDITING);
10256 compose_close(compose);
10259 static void compose_print_cb(GtkAction *action, gpointer data)
10261 Compose *compose = (Compose *) data;
10263 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10264 if (compose->targetinfo)
10265 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10268 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10270 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10271 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10272 Compose *compose = (Compose *) data;
10275 compose->out_encoding = (CharSet)value;
10278 static void compose_address_cb(GtkAction *action, gpointer data)
10280 Compose *compose = (Compose *)data;
10282 #ifndef USE_NEW_ADDRBOOK
10283 addressbook_open(compose);
10285 GError* error = NULL;
10286 addressbook_connect_signals(compose);
10287 addressbook_dbus_open(TRUE, &error);
10289 g_warning("%s", error->message);
10290 g_error_free(error);
10295 static void about_show_cb(GtkAction *action, gpointer data)
10300 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10302 Compose *compose = (Compose *)data;
10307 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10308 cm_return_if_fail(tmpl != NULL);
10310 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10312 val = alertpanel(_("Apply template"), msg,
10313 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10316 if (val == G_ALERTDEFAULT)
10317 compose_template_apply(compose, tmpl, TRUE);
10318 else if (val == G_ALERTALTERNATE)
10319 compose_template_apply(compose, tmpl, FALSE);
10322 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10324 Compose *compose = (Compose *)data;
10326 compose_exec_ext_editor(compose);
10329 static void compose_undo_cb(GtkAction *action, gpointer data)
10331 Compose *compose = (Compose *)data;
10332 gboolean prev_autowrap = compose->autowrap;
10334 compose->autowrap = FALSE;
10335 undo_undo(compose->undostruct);
10336 compose->autowrap = prev_autowrap;
10339 static void compose_redo_cb(GtkAction *action, gpointer data)
10341 Compose *compose = (Compose *)data;
10342 gboolean prev_autowrap = compose->autowrap;
10344 compose->autowrap = FALSE;
10345 undo_redo(compose->undostruct);
10346 compose->autowrap = prev_autowrap;
10349 static void entry_cut_clipboard(GtkWidget *entry)
10351 if (GTK_IS_EDITABLE(entry))
10352 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10353 else if (GTK_IS_TEXT_VIEW(entry))
10354 gtk_text_buffer_cut_clipboard(
10355 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10356 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10360 static void entry_copy_clipboard(GtkWidget *entry)
10362 if (GTK_IS_EDITABLE(entry))
10363 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10364 else if (GTK_IS_TEXT_VIEW(entry))
10365 gtk_text_buffer_copy_clipboard(
10366 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10367 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10370 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10371 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10373 if (GTK_IS_TEXT_VIEW(entry)) {
10374 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10375 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10376 GtkTextIter start_iter, end_iter;
10378 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10380 if (contents == NULL)
10383 /* we shouldn't delete the selection when middle-click-pasting, or we
10384 * can't mid-click-paste our own selection */
10385 if (clip != GDK_SELECTION_PRIMARY) {
10386 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10387 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10390 if (insert_place == NULL) {
10391 /* if insert_place isn't specified, insert at the cursor.
10392 * used for Ctrl-V pasting */
10393 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10394 start = gtk_text_iter_get_offset(&start_iter);
10395 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10397 /* if insert_place is specified, paste here.
10398 * used for mid-click-pasting */
10399 start = gtk_text_iter_get_offset(insert_place);
10400 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10401 if (prefs_common.primary_paste_unselects)
10402 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10406 /* paste unwrapped: mark the paste so it's not wrapped later */
10407 end = start + strlen(contents);
10408 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10409 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10410 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10411 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10412 /* rewrap paragraph now (after a mid-click-paste) */
10413 mark_start = gtk_text_buffer_get_insert(buffer);
10414 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10415 gtk_text_iter_backward_char(&start_iter);
10416 compose_beautify_paragraph(compose, &start_iter, TRUE);
10418 } else if (GTK_IS_EDITABLE(entry))
10419 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10421 compose->modified = TRUE;
10424 static void entry_allsel(GtkWidget *entry)
10426 if (GTK_IS_EDITABLE(entry))
10427 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10428 else if (GTK_IS_TEXT_VIEW(entry)) {
10429 GtkTextIter startiter, enditer;
10430 GtkTextBuffer *textbuf;
10432 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10433 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10434 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10436 gtk_text_buffer_move_mark_by_name(textbuf,
10437 "selection_bound", &startiter);
10438 gtk_text_buffer_move_mark_by_name(textbuf,
10439 "insert", &enditer);
10443 static void compose_cut_cb(GtkAction *action, gpointer data)
10445 Compose *compose = (Compose *)data;
10446 if (compose->focused_editable
10447 #ifndef GENERIC_UMPC
10448 && gtk_widget_has_focus(compose->focused_editable)
10451 entry_cut_clipboard(compose->focused_editable);
10454 static void compose_copy_cb(GtkAction *action, gpointer data)
10456 Compose *compose = (Compose *)data;
10457 if (compose->focused_editable
10458 #ifndef GENERIC_UMPC
10459 && gtk_widget_has_focus(compose->focused_editable)
10462 entry_copy_clipboard(compose->focused_editable);
10465 static void compose_paste_cb(GtkAction *action, gpointer data)
10467 Compose *compose = (Compose *)data;
10468 gint prev_autowrap;
10469 GtkTextBuffer *buffer;
10471 if (compose->focused_editable &&
10472 #ifndef GENERIC_UMPC
10473 gtk_widget_has_focus(compose->focused_editable)
10476 entry_paste_clipboard(compose, compose->focused_editable,
10477 prefs_common.linewrap_pastes,
10478 GDK_SELECTION_CLIPBOARD, NULL);
10483 #ifndef GENERIC_UMPC
10484 gtk_widget_has_focus(compose->text) &&
10486 compose->gtkaspell &&
10487 compose->gtkaspell->check_while_typing)
10488 gtkaspell_highlight_all(compose->gtkaspell);
10492 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10494 Compose *compose = (Compose *)data;
10495 gint wrap_quote = prefs_common.linewrap_quote;
10496 if (compose->focused_editable
10497 #ifndef GENERIC_UMPC
10498 && gtk_widget_has_focus(compose->focused_editable)
10501 /* let text_insert() (called directly or at a later time
10502 * after the gtk_editable_paste_clipboard) know that
10503 * text is to be inserted as a quotation. implemented
10504 * by using a simple refcount... */
10505 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10506 G_OBJECT(compose->focused_editable),
10507 "paste_as_quotation"));
10508 g_object_set_data(G_OBJECT(compose->focused_editable),
10509 "paste_as_quotation",
10510 GINT_TO_POINTER(paste_as_quotation + 1));
10511 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10512 entry_paste_clipboard(compose, compose->focused_editable,
10513 prefs_common.linewrap_pastes,
10514 GDK_SELECTION_CLIPBOARD, NULL);
10515 prefs_common.linewrap_quote = wrap_quote;
10519 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10521 Compose *compose = (Compose *)data;
10522 gint prev_autowrap;
10523 GtkTextBuffer *buffer;
10525 if (compose->focused_editable
10526 #ifndef GENERIC_UMPC
10527 && gtk_widget_has_focus(compose->focused_editable)
10530 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10531 GDK_SELECTION_CLIPBOARD, NULL);
10536 #ifndef GENERIC_UMPC
10537 gtk_widget_has_focus(compose->text) &&
10539 compose->gtkaspell &&
10540 compose->gtkaspell->check_while_typing)
10541 gtkaspell_highlight_all(compose->gtkaspell);
10545 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10547 Compose *compose = (Compose *)data;
10548 gint prev_autowrap;
10549 GtkTextBuffer *buffer;
10551 if (compose->focused_editable
10552 #ifndef GENERIC_UMPC
10553 && gtk_widget_has_focus(compose->focused_editable)
10556 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10557 GDK_SELECTION_CLIPBOARD, NULL);
10562 #ifndef GENERIC_UMPC
10563 gtk_widget_has_focus(compose->text) &&
10565 compose->gtkaspell &&
10566 compose->gtkaspell->check_while_typing)
10567 gtkaspell_highlight_all(compose->gtkaspell);
10571 static void compose_allsel_cb(GtkAction *action, gpointer data)
10573 Compose *compose = (Compose *)data;
10574 if (compose->focused_editable
10575 #ifndef GENERIC_UMPC
10576 && gtk_widget_has_focus(compose->focused_editable)
10579 entry_allsel(compose->focused_editable);
10582 static void textview_move_beginning_of_line (GtkTextView *text)
10584 GtkTextBuffer *buffer;
10588 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10590 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10591 mark = gtk_text_buffer_get_insert(buffer);
10592 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10593 gtk_text_iter_set_line_offset(&ins, 0);
10594 gtk_text_buffer_place_cursor(buffer, &ins);
10597 static void textview_move_forward_character (GtkTextView *text)
10599 GtkTextBuffer *buffer;
10603 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10605 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10606 mark = gtk_text_buffer_get_insert(buffer);
10607 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10608 if (gtk_text_iter_forward_cursor_position(&ins))
10609 gtk_text_buffer_place_cursor(buffer, &ins);
10612 static void textview_move_backward_character (GtkTextView *text)
10614 GtkTextBuffer *buffer;
10618 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10620 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10621 mark = gtk_text_buffer_get_insert(buffer);
10622 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10623 if (gtk_text_iter_backward_cursor_position(&ins))
10624 gtk_text_buffer_place_cursor(buffer, &ins);
10627 static void textview_move_forward_word (GtkTextView *text)
10629 GtkTextBuffer *buffer;
10634 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10636 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10637 mark = gtk_text_buffer_get_insert(buffer);
10638 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10639 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10640 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10641 gtk_text_iter_backward_word_start(&ins);
10642 gtk_text_buffer_place_cursor(buffer, &ins);
10646 static void textview_move_backward_word (GtkTextView *text)
10648 GtkTextBuffer *buffer;
10652 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10654 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10655 mark = gtk_text_buffer_get_insert(buffer);
10656 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10657 if (gtk_text_iter_backward_word_starts(&ins, 1))
10658 gtk_text_buffer_place_cursor(buffer, &ins);
10661 static void textview_move_end_of_line (GtkTextView *text)
10663 GtkTextBuffer *buffer;
10667 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10669 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10670 mark = gtk_text_buffer_get_insert(buffer);
10671 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10672 if (gtk_text_iter_forward_to_line_end(&ins))
10673 gtk_text_buffer_place_cursor(buffer, &ins);
10676 static void textview_move_next_line (GtkTextView *text)
10678 GtkTextBuffer *buffer;
10683 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10685 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10686 mark = gtk_text_buffer_get_insert(buffer);
10687 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10688 offset = gtk_text_iter_get_line_offset(&ins);
10689 if (gtk_text_iter_forward_line(&ins)) {
10690 gtk_text_iter_set_line_offset(&ins, offset);
10691 gtk_text_buffer_place_cursor(buffer, &ins);
10695 static void textview_move_previous_line (GtkTextView *text)
10697 GtkTextBuffer *buffer;
10702 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10704 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10705 mark = gtk_text_buffer_get_insert(buffer);
10706 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10707 offset = gtk_text_iter_get_line_offset(&ins);
10708 if (gtk_text_iter_backward_line(&ins)) {
10709 gtk_text_iter_set_line_offset(&ins, offset);
10710 gtk_text_buffer_place_cursor(buffer, &ins);
10714 static void textview_delete_forward_character (GtkTextView *text)
10716 GtkTextBuffer *buffer;
10718 GtkTextIter ins, end_iter;
10720 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10722 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10723 mark = gtk_text_buffer_get_insert(buffer);
10724 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10726 if (gtk_text_iter_forward_char(&end_iter)) {
10727 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10731 static void textview_delete_backward_character (GtkTextView *text)
10733 GtkTextBuffer *buffer;
10735 GtkTextIter ins, end_iter;
10737 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10739 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10740 mark = gtk_text_buffer_get_insert(buffer);
10741 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10743 if (gtk_text_iter_backward_char(&end_iter)) {
10744 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10748 static void textview_delete_forward_word (GtkTextView *text)
10750 GtkTextBuffer *buffer;
10752 GtkTextIter ins, end_iter;
10754 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10756 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10757 mark = gtk_text_buffer_get_insert(buffer);
10758 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10760 if (gtk_text_iter_forward_word_end(&end_iter)) {
10761 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10765 static void textview_delete_backward_word (GtkTextView *text)
10767 GtkTextBuffer *buffer;
10769 GtkTextIter ins, end_iter;
10771 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10773 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10774 mark = gtk_text_buffer_get_insert(buffer);
10775 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10777 if (gtk_text_iter_backward_word_start(&end_iter)) {
10778 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10782 static void textview_delete_line (GtkTextView *text)
10784 GtkTextBuffer *buffer;
10786 GtkTextIter ins, start_iter, end_iter;
10788 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10790 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10791 mark = gtk_text_buffer_get_insert(buffer);
10792 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10795 gtk_text_iter_set_line_offset(&start_iter, 0);
10798 if (gtk_text_iter_ends_line(&end_iter)){
10799 if (!gtk_text_iter_forward_char(&end_iter))
10800 gtk_text_iter_backward_char(&start_iter);
10803 gtk_text_iter_forward_to_line_end(&end_iter);
10804 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10807 static void textview_delete_to_line_end (GtkTextView *text)
10809 GtkTextBuffer *buffer;
10811 GtkTextIter ins, end_iter;
10813 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10815 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10816 mark = gtk_text_buffer_get_insert(buffer);
10817 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10819 if (gtk_text_iter_ends_line(&end_iter))
10820 gtk_text_iter_forward_char(&end_iter);
10822 gtk_text_iter_forward_to_line_end(&end_iter);
10823 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10826 #define DO_ACTION(name, act) { \
10827 if(!strcmp(name, a_name)) { \
10831 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10833 const gchar *a_name = gtk_action_get_name(action);
10834 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10835 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10836 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10837 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10838 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10839 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10840 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10841 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10842 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10843 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10844 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10845 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10846 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10847 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10851 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10853 Compose *compose = (Compose *)data;
10854 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10855 ComposeCallAdvancedAction action = -1;
10857 action = compose_call_advanced_action_from_path(gaction);
10860 void (*do_action) (GtkTextView *text);
10861 } action_table[] = {
10862 {textview_move_beginning_of_line},
10863 {textview_move_forward_character},
10864 {textview_move_backward_character},
10865 {textview_move_forward_word},
10866 {textview_move_backward_word},
10867 {textview_move_end_of_line},
10868 {textview_move_next_line},
10869 {textview_move_previous_line},
10870 {textview_delete_forward_character},
10871 {textview_delete_backward_character},
10872 {textview_delete_forward_word},
10873 {textview_delete_backward_word},
10874 {textview_delete_line},
10875 {textview_delete_to_line_end}
10878 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10880 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10881 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10882 if (action_table[action].do_action)
10883 action_table[action].do_action(text);
10885 g_warning("Not implemented yet.");
10889 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10891 GtkAllocation allocation;
10895 if (GTK_IS_EDITABLE(widget)) {
10896 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10897 gtk_editable_set_position(GTK_EDITABLE(widget),
10900 if ((parent = gtk_widget_get_parent(widget))
10901 && (parent = gtk_widget_get_parent(parent))
10902 && (parent = gtk_widget_get_parent(parent))) {
10903 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10904 gtk_widget_get_allocation(widget, &allocation);
10905 gint y = allocation.y;
10906 gint height = allocation.height;
10907 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10908 (GTK_SCROLLED_WINDOW(parent));
10910 gfloat value = gtk_adjustment_get_value(shown);
10911 gfloat upper = gtk_adjustment_get_upper(shown);
10912 gfloat page_size = gtk_adjustment_get_page_size(shown);
10913 if (y < (int)value) {
10914 gtk_adjustment_set_value(shown, y - 1);
10916 if ((y + height) > ((int)value + (int)page_size)) {
10917 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10918 gtk_adjustment_set_value(shown,
10919 y + height - (int)page_size - 1);
10921 gtk_adjustment_set_value(shown,
10922 (int)upper - (int)page_size - 1);
10929 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10930 compose->focused_editable = widget;
10932 #ifdef GENERIC_UMPC
10933 if (GTK_IS_TEXT_VIEW(widget)
10934 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10935 g_object_ref(compose->notebook);
10936 g_object_ref(compose->edit_vbox);
10937 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10938 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10939 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10940 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10941 g_object_unref(compose->notebook);
10942 g_object_unref(compose->edit_vbox);
10943 g_signal_handlers_block_by_func(G_OBJECT(widget),
10944 G_CALLBACK(compose_grab_focus_cb),
10946 gtk_widget_grab_focus(widget);
10947 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10948 G_CALLBACK(compose_grab_focus_cb),
10950 } else if (!GTK_IS_TEXT_VIEW(widget)
10951 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10952 g_object_ref(compose->notebook);
10953 g_object_ref(compose->edit_vbox);
10954 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10955 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10956 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10957 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10958 g_object_unref(compose->notebook);
10959 g_object_unref(compose->edit_vbox);
10960 g_signal_handlers_block_by_func(G_OBJECT(widget),
10961 G_CALLBACK(compose_grab_focus_cb),
10963 gtk_widget_grab_focus(widget);
10964 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10965 G_CALLBACK(compose_grab_focus_cb),
10971 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10973 compose->modified = TRUE;
10974 // compose_beautify_paragraph(compose, NULL, TRUE);
10975 #ifndef GENERIC_UMPC
10976 compose_set_title(compose);
10980 static void compose_wrap_cb(GtkAction *action, gpointer data)
10982 Compose *compose = (Compose *)data;
10983 compose_beautify_paragraph(compose, NULL, TRUE);
10986 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10988 Compose *compose = (Compose *)data;
10989 compose_wrap_all_full(compose, TRUE);
10992 static void compose_find_cb(GtkAction *action, gpointer data)
10994 Compose *compose = (Compose *)data;
10996 message_search_compose(compose);
10999 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11002 Compose *compose = (Compose *)data;
11003 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11004 if (compose->autowrap)
11005 compose_wrap_all_full(compose, TRUE);
11006 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11009 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11012 Compose *compose = (Compose *)data;
11013 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11016 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11018 Compose *compose = (Compose *)data;
11020 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11023 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11025 Compose *compose = (Compose *)data;
11027 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11030 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11032 g_free(compose->privacy_system);
11034 compose->privacy_system = g_strdup(account->default_privacy_system);
11035 compose_update_privacy_system_menu_item(compose, warn);
11038 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11040 Compose *compose = (Compose *)data;
11042 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11043 gtk_widget_show(compose->ruler_hbox);
11044 prefs_common.show_ruler = TRUE;
11046 gtk_widget_hide(compose->ruler_hbox);
11047 gtk_widget_queue_resize(compose->edit_vbox);
11048 prefs_common.show_ruler = FALSE;
11052 static void compose_attach_drag_received_cb (GtkWidget *widget,
11053 GdkDragContext *context,
11056 GtkSelectionData *data,
11059 gpointer user_data)
11061 Compose *compose = (Compose *)user_data;
11065 type = gtk_selection_data_get_data_type(data);
11066 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11068 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11070 ) && gtk_drag_get_source_widget(context) !=
11071 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11072 list = uri_list_extract_filenames(
11073 (const gchar *)gtk_selection_data_get_data(data));
11074 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11075 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11076 compose_attach_append
11077 (compose, (const gchar *)tmp->data,
11078 utf8_filename, NULL, NULL);
11079 g_free(utf8_filename);
11081 if (list) compose_changed_cb(NULL, compose);
11082 list_free_strings(list);
11084 } else if (gtk_drag_get_source_widget(context)
11085 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11086 /* comes from our summaryview */
11087 SummaryView * summaryview = NULL;
11088 GSList * list = NULL, *cur = NULL;
11090 if (mainwindow_get_mainwindow())
11091 summaryview = mainwindow_get_mainwindow()->summaryview;
11094 list = summary_get_selected_msg_list(summaryview);
11096 for (cur = list; cur; cur = cur->next) {
11097 MsgInfo *msginfo = (MsgInfo *)cur->data;
11098 gchar *file = NULL;
11100 file = procmsg_get_message_file_full(msginfo,
11103 compose_attach_append(compose, (const gchar *)file,
11104 (const gchar *)file, "message/rfc822", NULL);
11108 g_slist_free(list);
11112 static gboolean compose_drag_drop(GtkWidget *widget,
11113 GdkDragContext *drag_context,
11115 guint time, gpointer user_data)
11117 /* not handling this signal makes compose_insert_drag_received_cb
11122 static gboolean completion_set_focus_to_subject
11123 (GtkWidget *widget,
11124 GdkEventKey *event,
11127 cm_return_val_if_fail(compose != NULL, FALSE);
11129 /* make backtab move to subject field */
11130 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11131 gtk_widget_grab_focus(compose->subject_entry);
11137 static void compose_insert_drag_received_cb (GtkWidget *widget,
11138 GdkDragContext *drag_context,
11141 GtkSelectionData *data,
11144 gpointer user_data)
11146 Compose *compose = (Compose *)user_data;
11150 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11152 type = gtk_selection_data_get_data_type(data);
11154 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11156 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11158 AlertValue val = G_ALERTDEFAULT;
11159 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11161 list = uri_list_extract_filenames(ddata);
11162 if (list == NULL && strstr(ddata, "://")) {
11163 /* Assume a list of no files, and data has ://, is a remote link */
11164 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11165 gchar *tmpfile = get_tmp_file();
11166 str_write_to_file(tmpdata, tmpfile);
11168 compose_insert_file(compose, tmpfile);
11169 claws_unlink(tmpfile);
11171 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11172 compose_beautify_paragraph(compose, NULL, TRUE);
11175 switch (prefs_common.compose_dnd_mode) {
11176 case COMPOSE_DND_ASK:
11177 val = alertpanel_full(_("Insert or attach?"),
11178 _("Do you want to insert the contents of the file(s) "
11179 "into the message body, or attach it to the email?"),
11180 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11181 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11183 case COMPOSE_DND_INSERT:
11184 val = G_ALERTALTERNATE;
11186 case COMPOSE_DND_ATTACH:
11187 val = G_ALERTOTHER;
11190 /* unexpected case */
11191 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11194 if (val & G_ALERTDISABLE) {
11195 val &= ~G_ALERTDISABLE;
11196 /* remember what action to perform by default, only if we don't click Cancel */
11197 if (val == G_ALERTALTERNATE)
11198 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11199 else if (val == G_ALERTOTHER)
11200 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11203 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11204 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11205 list_free_strings(list);
11208 } else if (val == G_ALERTOTHER) {
11209 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11210 list_free_strings(list);
11215 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11216 compose_insert_file(compose, (const gchar *)tmp->data);
11218 list_free_strings(list);
11220 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11225 static void compose_header_drag_received_cb (GtkWidget *widget,
11226 GdkDragContext *drag_context,
11229 GtkSelectionData *data,
11232 gpointer user_data)
11234 GtkEditable *entry = (GtkEditable *)user_data;
11235 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11237 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11240 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11241 gchar *decoded=g_new(gchar, strlen(email));
11244 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11245 gtk_editable_delete_text(entry, 0, -1);
11246 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11247 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11251 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11254 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11256 Compose *compose = (Compose *)data;
11258 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11259 compose->return_receipt = TRUE;
11261 compose->return_receipt = FALSE;
11264 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11266 Compose *compose = (Compose *)data;
11268 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11269 compose->remove_references = TRUE;
11271 compose->remove_references = FALSE;
11274 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11275 ComposeHeaderEntry *headerentry)
11277 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11281 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11282 GdkEventKey *event,
11283 ComposeHeaderEntry *headerentry)
11285 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11286 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11287 !(event->state & GDK_MODIFIER_MASK) &&
11288 (event->keyval == GDK_KEY_BackSpace) &&
11289 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11290 gtk_container_remove
11291 (GTK_CONTAINER(headerentry->compose->header_table),
11292 headerentry->combo);
11293 gtk_container_remove
11294 (GTK_CONTAINER(headerentry->compose->header_table),
11295 headerentry->entry);
11296 headerentry->compose->header_list =
11297 g_slist_remove(headerentry->compose->header_list,
11299 g_free(headerentry);
11300 } else if (event->keyval == GDK_KEY_Tab) {
11301 if (headerentry->compose->header_last == headerentry) {
11302 /* Override default next focus, and give it to subject_entry
11303 * instead of notebook tabs
11305 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11306 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11313 static gboolean scroll_postpone(gpointer data)
11315 Compose *compose = (Compose *)data;
11317 if (compose->batch)
11320 GTK_EVENTS_FLUSH();
11321 compose_show_first_last_header(compose, FALSE);
11325 static void compose_headerentry_changed_cb(GtkWidget *entry,
11326 ComposeHeaderEntry *headerentry)
11328 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11329 compose_create_header_entry(headerentry->compose);
11330 g_signal_handlers_disconnect_matched
11331 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11332 0, 0, NULL, NULL, headerentry);
11334 if (!headerentry->compose->batch)
11335 g_timeout_add(0, scroll_postpone, headerentry->compose);
11339 static gboolean compose_defer_auto_save_draft(Compose *compose)
11341 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11342 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11346 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11348 GtkAdjustment *vadj;
11350 cm_return_if_fail(compose);
11355 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11356 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11357 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11358 gtk_widget_get_parent(compose->header_table)));
11359 gtk_adjustment_set_value(vadj, (show_first ?
11360 gtk_adjustment_get_lower(vadj) :
11361 (gtk_adjustment_get_upper(vadj) -
11362 gtk_adjustment_get_page_size(vadj))));
11363 gtk_adjustment_changed(vadj);
11366 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11367 const gchar *text, gint len, Compose *compose)
11369 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11370 (G_OBJECT(compose->text), "paste_as_quotation"));
11373 cm_return_if_fail(text != NULL);
11375 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11376 G_CALLBACK(text_inserted),
11378 if (paste_as_quotation) {
11380 const gchar *qmark;
11382 GtkTextIter start_iter;
11385 len = strlen(text);
11387 new_text = g_strndup(text, len);
11389 qmark = compose_quote_char_from_context(compose);
11391 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11392 gtk_text_buffer_place_cursor(buffer, iter);
11394 pos = gtk_text_iter_get_offset(iter);
11396 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11397 _("Quote format error at line %d."));
11398 quote_fmt_reset_vartable();
11400 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11401 GINT_TO_POINTER(paste_as_quotation - 1));
11403 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11404 gtk_text_buffer_place_cursor(buffer, iter);
11405 gtk_text_buffer_delete_mark(buffer, mark);
11407 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11408 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11409 compose_beautify_paragraph(compose, &start_iter, FALSE);
11410 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11411 gtk_text_buffer_delete_mark(buffer, mark);
11413 if (strcmp(text, "\n") || compose->automatic_break
11414 || gtk_text_iter_starts_line(iter)) {
11415 GtkTextIter before_ins;
11416 gtk_text_buffer_insert(buffer, iter, text, len);
11417 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11418 before_ins = *iter;
11419 gtk_text_iter_backward_chars(&before_ins, len);
11420 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11423 /* check if the preceding is just whitespace or quote */
11424 GtkTextIter start_line;
11425 gchar *tmp = NULL, *quote = NULL;
11426 gint quote_len = 0, is_normal = 0;
11427 start_line = *iter;
11428 gtk_text_iter_set_line_offset(&start_line, 0);
11429 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11432 if (*tmp == '\0') {
11435 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11443 gtk_text_buffer_insert(buffer, iter, text, len);
11445 gtk_text_buffer_insert_with_tags_by_name(buffer,
11446 iter, text, len, "no_join", NULL);
11451 if (!paste_as_quotation) {
11452 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11453 compose_beautify_paragraph(compose, iter, FALSE);
11454 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11455 gtk_text_buffer_delete_mark(buffer, mark);
11458 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11459 G_CALLBACK(text_inserted),
11461 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11463 if (compose_can_autosave(compose) &&
11464 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11465 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11466 compose->draft_timeout_tag = g_timeout_add
11467 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11471 static void compose_check_all(GtkAction *action, gpointer data)
11473 Compose *compose = (Compose *)data;
11474 if (!compose->gtkaspell)
11477 if (gtk_widget_has_focus(compose->subject_entry))
11478 claws_spell_entry_check_all(
11479 CLAWS_SPELL_ENTRY(compose->subject_entry));
11481 gtkaspell_check_all(compose->gtkaspell);
11484 static void compose_highlight_all(GtkAction *action, gpointer data)
11486 Compose *compose = (Compose *)data;
11487 if (compose->gtkaspell) {
11488 claws_spell_entry_recheck_all(
11489 CLAWS_SPELL_ENTRY(compose->subject_entry));
11490 gtkaspell_highlight_all(compose->gtkaspell);
11494 static void compose_check_backwards(GtkAction *action, gpointer data)
11496 Compose *compose = (Compose *)data;
11497 if (!compose->gtkaspell) {
11498 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11502 if (gtk_widget_has_focus(compose->subject_entry))
11503 claws_spell_entry_check_backwards(
11504 CLAWS_SPELL_ENTRY(compose->subject_entry));
11506 gtkaspell_check_backwards(compose->gtkaspell);
11509 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11511 Compose *compose = (Compose *)data;
11512 if (!compose->gtkaspell) {
11513 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11517 if (gtk_widget_has_focus(compose->subject_entry))
11518 claws_spell_entry_check_forwards_go(
11519 CLAWS_SPELL_ENTRY(compose->subject_entry));
11521 gtkaspell_check_forwards_go(compose->gtkaspell);
11526 *\brief Guess originating forward account from MsgInfo and several
11527 * "common preference" settings. Return NULL if no guess.
11529 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11531 PrefsAccount *account = NULL;
11533 cm_return_val_if_fail(msginfo, NULL);
11534 cm_return_val_if_fail(msginfo->folder, NULL);
11535 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11537 if (msginfo->folder->prefs->enable_default_account)
11538 account = account_find_from_id(msginfo->folder->prefs->default_account);
11541 account = msginfo->folder->folder->account;
11543 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11545 Xstrdup_a(to, msginfo->to, return NULL);
11546 extract_address(to);
11547 account = account_find_from_address(to, FALSE);
11550 if (!account && prefs_common.forward_account_autosel) {
11551 gchar cc[BUFFSIZE];
11552 if (!procheader_get_header_from_msginfo
11553 (msginfo, cc,sizeof cc , "Cc:")) {
11554 gchar *buf = cc + strlen("Cc:");
11555 extract_address(buf);
11556 account = account_find_from_address(buf, FALSE);
11560 if (!account && prefs_common.forward_account_autosel) {
11561 gchar deliveredto[BUFFSIZE];
11562 if (!procheader_get_header_from_msginfo
11563 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11564 gchar *buf = deliveredto + strlen("Delivered-To:");
11565 extract_address(buf);
11566 account = account_find_from_address(buf, FALSE);
11573 gboolean compose_close(Compose *compose)
11577 cm_return_val_if_fail(compose, FALSE);
11579 if (!g_mutex_trylock(compose->mutex)) {
11580 /* we have to wait for the (possibly deferred by auto-save)
11581 * drafting to be done, before destroying the compose under
11583 debug_print("waiting for drafting to finish...\n");
11584 compose_allow_user_actions(compose, FALSE);
11585 if (compose->close_timeout_tag == 0) {
11586 compose->close_timeout_tag =
11587 g_timeout_add (500, (GSourceFunc) compose_close,
11593 if (compose->draft_timeout_tag >= 0) {
11594 g_source_remove(compose->draft_timeout_tag);
11595 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11598 gtkut_widget_get_uposition(compose->window, &x, &y);
11599 if (!compose->batch) {
11600 prefs_common.compose_x = x;
11601 prefs_common.compose_y = y;
11603 g_mutex_unlock(compose->mutex);
11604 compose_destroy(compose);
11609 * Add entry field for each address in list.
11610 * \param compose E-Mail composition object.
11611 * \param listAddress List of (formatted) E-Mail addresses.
11613 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11616 node = listAddress;
11618 addr = ( gchar * ) node->data;
11619 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11620 node = g_list_next( node );
11624 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11625 guint action, gboolean opening_multiple)
11627 gchar *body = NULL;
11628 GSList *new_msglist = NULL;
11629 MsgInfo *tmp_msginfo = NULL;
11630 gboolean originally_enc = FALSE;
11631 gboolean originally_sig = FALSE;
11632 Compose *compose = NULL;
11633 gchar *s_system = NULL;
11635 cm_return_if_fail(msgview != NULL);
11637 cm_return_if_fail(msginfo_list != NULL);
11639 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11640 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11641 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11643 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11644 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11645 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11646 orig_msginfo, mimeinfo);
11647 if (tmp_msginfo != NULL) {
11648 new_msglist = g_slist_append(NULL, tmp_msginfo);
11650 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11651 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11652 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11654 tmp_msginfo->folder = orig_msginfo->folder;
11655 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11656 if (orig_msginfo->tags) {
11657 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11658 tmp_msginfo->folder->tags_dirty = TRUE;
11664 if (!opening_multiple)
11665 body = messageview_get_selection(msgview);
11668 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11669 procmsg_msginfo_free(tmp_msginfo);
11670 g_slist_free(new_msglist);
11672 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11674 if (compose && originally_enc) {
11675 compose_force_encryption(compose, compose->account, FALSE, s_system);
11678 if (compose && originally_sig && compose->account->default_sign_reply) {
11679 compose_force_signing(compose, compose->account, s_system);
11683 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11686 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11689 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11690 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11691 GSList *cur = msginfo_list;
11692 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11693 "messages. Opening the windows "
11694 "could take some time. Do you "
11695 "want to continue?"),
11696 g_slist_length(msginfo_list));
11697 if (g_slist_length(msginfo_list) > 9
11698 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11699 != G_ALERTALTERNATE) {
11704 /* We'll open multiple compose windows */
11705 /* let the WM place the next windows */
11706 compose_force_window_origin = FALSE;
11707 for (; cur; cur = cur->next) {
11709 tmplist.data = cur->data;
11710 tmplist.next = NULL;
11711 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11713 compose_force_window_origin = TRUE;
11715 /* forwarding multiple mails as attachments is done via a
11716 * single compose window */
11717 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11721 void compose_check_for_email_account(Compose *compose)
11723 PrefsAccount *ac = NULL, *curr = NULL;
11729 if (compose->account && compose->account->protocol == A_NNTP) {
11730 ac = account_get_cur_account();
11731 if (ac->protocol == A_NNTP) {
11732 list = account_get_list();
11734 for( ; list != NULL ; list = g_list_next(list)) {
11735 curr = (PrefsAccount *) list->data;
11736 if (curr->protocol != A_NNTP) {
11742 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11747 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11748 const gchar *address)
11750 GSList *msginfo_list = NULL;
11751 gchar *body = messageview_get_selection(msgview);
11754 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11756 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11757 compose_check_for_email_account(compose);
11758 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11759 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11760 compose_reply_set_subject(compose, msginfo);
11763 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11766 void compose_set_position(Compose *compose, gint pos)
11768 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11770 gtkut_text_view_set_position(text, pos);
11773 gboolean compose_search_string(Compose *compose,
11774 const gchar *str, gboolean case_sens)
11776 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11778 return gtkut_text_view_search_string(text, str, case_sens);
11781 gboolean compose_search_string_backward(Compose *compose,
11782 const gchar *str, gboolean case_sens)
11784 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11786 return gtkut_text_view_search_string_backward(text, str, case_sens);
11789 /* allocate a msginfo structure and populate its data from a compose data structure */
11790 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11792 MsgInfo *newmsginfo;
11794 gchar buf[BUFFSIZE];
11796 cm_return_val_if_fail( compose != NULL, NULL );
11798 newmsginfo = procmsg_msginfo_new();
11801 get_rfc822_date(buf, sizeof(buf));
11802 newmsginfo->date = g_strdup(buf);
11805 if (compose->from_name) {
11806 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11807 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11811 if (compose->subject_entry)
11812 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11814 /* to, cc, reply-to, newsgroups */
11815 for (list = compose->header_list; list; list = list->next) {
11816 gchar *header = gtk_editable_get_chars(
11818 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11819 gchar *entry = gtk_editable_get_chars(
11820 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11822 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11823 if ( newmsginfo->to == NULL ) {
11824 newmsginfo->to = g_strdup(entry);
11825 } else if (entry && *entry) {
11826 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11827 g_free(newmsginfo->to);
11828 newmsginfo->to = tmp;
11831 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11832 if ( newmsginfo->cc == NULL ) {
11833 newmsginfo->cc = g_strdup(entry);
11834 } else if (entry && *entry) {
11835 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11836 g_free(newmsginfo->cc);
11837 newmsginfo->cc = tmp;
11840 if ( strcasecmp(header,
11841 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11842 if ( newmsginfo->newsgroups == NULL ) {
11843 newmsginfo->newsgroups = g_strdup(entry);
11844 } else if (entry && *entry) {
11845 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11846 g_free(newmsginfo->newsgroups);
11847 newmsginfo->newsgroups = tmp;
11855 /* other data is unset */
11861 /* update compose's dictionaries from folder dict settings */
11862 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11863 FolderItem *folder_item)
11865 cm_return_if_fail(compose != NULL);
11867 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11868 FolderItemPrefs *prefs = folder_item->prefs;
11870 if (prefs->enable_default_dictionary)
11871 gtkaspell_change_dict(compose->gtkaspell,
11872 prefs->default_dictionary, FALSE);
11873 if (folder_item->prefs->enable_default_alt_dictionary)
11874 gtkaspell_change_alt_dict(compose->gtkaspell,
11875 prefs->default_alt_dictionary);
11876 if (prefs->enable_default_dictionary
11877 || prefs->enable_default_alt_dictionary)
11878 compose_spell_menu_changed(compose);
11883 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11885 Compose *compose = (Compose *)data;
11887 cm_return_if_fail(compose != NULL);
11889 gtk_widget_grab_focus(compose->text);
11892 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
11894 gtk_combo_box_popup(GTK_COMBO_BOX(data));