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