2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
87 #include "quoted-printable.h"
91 #include "gtkshruler.h"
93 #include "alertpanel.h"
94 #include "manage_window.h"
96 #include "folder_item_prefs.h"
97 #include "addr_compl.h"
98 #include "quote_fmt.h"
100 #include "foldersel.h"
103 #include "message_search.h"
104 #include "combobox.h"
108 #include "autofaces.h"
109 #include "spell_entry.h"
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
140 } ComposeCallAdvancedAction;
144 PRIORITY_HIGHEST = 1,
153 COMPOSE_INSERT_SUCCESS,
154 COMPOSE_INSERT_READ_ERROR,
155 COMPOSE_INSERT_INVALID_CHARACTER,
156 COMPOSE_INSERT_NO_FILE
157 } ComposeInsertResult;
161 COMPOSE_WRITE_FOR_SEND,
162 COMPOSE_WRITE_FOR_STORE
167 COMPOSE_QUOTE_FORCED,
174 SUBJECT_FIELD_PRESENT,
179 #define B64_LINE_SIZE 57
180 #define B64_BUFFSIZE 77
182 #define MAX_REFERENCES_LEN 999
184 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
185 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
187 static GList *compose_list = NULL;
188 static GSList *extra_headers = NULL;
190 static Compose *compose_generic_new (PrefsAccount *account,
194 GList *listAddress );
196 static Compose *compose_create (PrefsAccount *account,
201 static void compose_entry_mark_default_to (Compose *compose,
202 const gchar *address);
203 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
204 ComposeQuoteMode quote_mode,
208 static Compose *compose_forward_multiple (PrefsAccount *account,
209 GSList *msginfo_list);
210 static Compose *compose_reply (MsgInfo *msginfo,
211 ComposeQuoteMode quote_mode,
216 static Compose *compose_reply_mode (ComposeMode mode,
217 GSList *msginfo_list,
219 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
220 static void compose_update_privacy_systems_menu(Compose *compose);
222 static GtkWidget *compose_account_option_menu_create
224 static void compose_set_out_encoding (Compose *compose);
225 static void compose_set_template_menu (Compose *compose);
226 static void compose_destroy (Compose *compose);
228 static MailField compose_entries_set (Compose *compose,
230 ComposeEntryType to_type);
231 static gint compose_parse_header (Compose *compose,
233 static gint compose_parse_manual_headers (Compose *compose,
235 HeaderEntry *entries);
236 static gchar *compose_parse_references (const gchar *ref,
239 static gchar *compose_quote_fmt (Compose *compose,
245 gboolean need_unescape,
246 const gchar *err_msg);
248 static void compose_reply_set_entry (Compose *compose,
254 followup_and_reply_to);
255 static void compose_reedit_set_entry (Compose *compose,
258 static void compose_insert_sig (Compose *compose,
260 static ComposeInsertResult compose_insert_file (Compose *compose,
263 static gboolean compose_attach_append (Compose *compose,
266 const gchar *content_type,
267 const gchar *charset);
268 static void compose_attach_parts (Compose *compose,
271 static gboolean compose_beautify_paragraph (Compose *compose,
272 GtkTextIter *par_iter,
274 static void compose_wrap_all (Compose *compose);
275 static void compose_wrap_all_full (Compose *compose,
278 static void compose_set_title (Compose *compose);
279 static void compose_select_account (Compose *compose,
280 PrefsAccount *account,
283 static PrefsAccount *compose_current_mail_account(void);
284 /* static gint compose_send (Compose *compose); */
285 static gboolean compose_check_for_valid_recipient
287 static gboolean compose_check_entries (Compose *compose,
288 gboolean check_everything);
289 static gint compose_write_to_file (Compose *compose,
292 gboolean attach_parts);
293 static gint compose_write_body_to_file (Compose *compose,
295 static gint compose_remove_reedit_target (Compose *compose,
297 static void compose_remove_draft (Compose *compose);
298 static gint compose_queue_sub (Compose *compose,
302 gboolean check_subject,
303 gboolean remove_reedit_target);
304 static int compose_add_attachments (Compose *compose,
306 static gchar *compose_get_header (Compose *compose);
307 static gchar *compose_get_manual_headers_info (Compose *compose);
309 static void compose_convert_header (Compose *compose,
314 gboolean addr_field);
316 static void compose_attach_info_free (AttachInfo *ainfo);
317 static void compose_attach_remove_selected (GtkAction *action,
320 static void compose_template_apply (Compose *compose,
323 static void compose_attach_property (GtkAction *action,
325 static void compose_attach_property_create (gboolean *cancelled);
326 static void attach_property_ok (GtkWidget *widget,
327 gboolean *cancelled);
328 static void attach_property_cancel (GtkWidget *widget,
329 gboolean *cancelled);
330 static gint attach_property_delete_event (GtkWidget *widget,
332 gboolean *cancelled);
333 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 gboolean *cancelled);
337 static void compose_exec_ext_editor (Compose *compose);
339 static gint compose_exec_ext_editor_real (const gchar *file);
340 static gboolean compose_ext_editor_kill (Compose *compose);
341 static gboolean compose_input_cb (GIOChannel *source,
342 GIOCondition condition,
344 static void compose_set_ext_editor_sensitive (Compose *compose,
346 #endif /* G_OS_UNIX */
348 static void compose_undo_state_changed (UndoMain *undostruct,
353 static void compose_create_header_entry (Compose *compose);
354 static void compose_add_header_entry (Compose *compose, const gchar *header,
355 gchar *text, ComposePrefType pref_type);
356 static void compose_remove_header_entries(Compose *compose);
358 static void compose_update_priority_menu_item(Compose * compose);
360 static void compose_spell_menu_changed (void *data);
361 static void compose_dict_changed (void *data);
363 static void compose_add_field_list ( Compose *compose,
364 GList *listAddress );
366 /* callback functions */
368 static void compose_notebook_size_alloc (GtkNotebook *notebook,
369 GtkAllocation *allocation,
371 static gboolean compose_edit_size_alloc (GtkEditable *widget,
372 GtkAllocation *allocation,
373 GtkSHRuler *shruler);
374 static void account_activated (GtkComboBox *optmenu,
376 static void attach_selected (GtkTreeView *tree_view,
377 GtkTreePath *tree_path,
378 GtkTreeViewColumn *column,
380 static gboolean attach_button_pressed (GtkWidget *widget,
381 GdkEventButton *event,
383 static gboolean attach_key_pressed (GtkWidget *widget,
386 static void compose_send_cb (GtkAction *action, gpointer data);
387 static void compose_send_later_cb (GtkAction *action, gpointer data);
389 static void compose_save_cb (GtkAction *action,
392 static void compose_attach_cb (GtkAction *action,
394 static void compose_insert_file_cb (GtkAction *action,
396 static void compose_insert_sig_cb (GtkAction *action,
398 static void compose_replace_sig_cb (GtkAction *action,
401 static void compose_close_cb (GtkAction *action,
403 static void compose_print_cb (GtkAction *action,
406 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
408 static void compose_address_cb (GtkAction *action,
410 static void about_show_cb (GtkAction *action,
412 static void compose_template_activate_cb(GtkWidget *widget,
415 static void compose_ext_editor_cb (GtkAction *action,
418 static gint compose_delete_cb (GtkWidget *widget,
422 static void compose_undo_cb (GtkAction *action,
424 static void compose_redo_cb (GtkAction *action,
426 static void compose_cut_cb (GtkAction *action,
428 static void compose_copy_cb (GtkAction *action,
430 static void compose_paste_cb (GtkAction *action,
432 static void compose_paste_as_quote_cb (GtkAction *action,
434 static void compose_paste_no_wrap_cb (GtkAction *action,
436 static void compose_paste_wrap_cb (GtkAction *action,
438 static void compose_allsel_cb (GtkAction *action,
441 static void compose_advanced_action_cb (GtkAction *action,
444 static void compose_grab_focus_cb (GtkWidget *widget,
447 static void compose_changed_cb (GtkTextBuffer *textbuf,
450 static void compose_wrap_cb (GtkAction *action,
452 static void compose_wrap_all_cb (GtkAction *action,
454 static void compose_find_cb (GtkAction *action,
456 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
458 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
461 static void compose_toggle_ruler_cb (GtkToggleAction *action,
463 static void compose_toggle_sign_cb (GtkToggleAction *action,
465 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
467 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
468 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
469 static void activate_privacy_system (Compose *compose,
470 PrefsAccount *account,
472 static void compose_use_signing(Compose *compose, gboolean use_signing);
473 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
474 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
476 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
478 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
479 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
480 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
482 static void compose_attach_drag_received_cb (GtkWidget *widget,
483 GdkDragContext *drag_context,
486 GtkSelectionData *data,
490 static void compose_insert_drag_received_cb (GtkWidget *widget,
491 GdkDragContext *drag_context,
494 GtkSelectionData *data,
498 static void compose_header_drag_received_cb (GtkWidget *widget,
499 GdkDragContext *drag_context,
502 GtkSelectionData *data,
507 static gboolean compose_drag_drop (GtkWidget *widget,
508 GdkDragContext *drag_context,
510 guint time, gpointer user_data);
511 static gboolean completion_set_focus_to_subject
516 static void text_inserted (GtkTextBuffer *buffer,
521 static Compose *compose_generic_reply(MsgInfo *msginfo,
522 ComposeQuoteMode quote_mode,
526 gboolean followup_and_reply_to,
529 static void compose_headerentry_changed_cb (GtkWidget *entry,
530 ComposeHeaderEntry *headerentry);
531 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
533 ComposeHeaderEntry *headerentry);
534 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
535 ComposeHeaderEntry *headerentry);
537 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
539 static void compose_allow_user_actions (Compose *compose, gboolean allow);
541 static void compose_nothing_cb (GtkAction *action, gpointer data)
547 static void compose_check_all (GtkAction *action, gpointer data);
548 static void compose_highlight_all (GtkAction *action, gpointer data);
549 static void compose_check_backwards (GtkAction *action, gpointer data);
550 static void compose_check_forwards_go (GtkAction *action, gpointer data);
553 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
555 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
558 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
559 FolderItem *folder_item);
561 static void compose_attach_update_label(Compose *compose);
562 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
563 gboolean respect_default_to);
564 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
565 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
567 static GtkActionEntry compose_popup_entries[] =
569 {"Compose", NULL, "Compose" },
570 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
571 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
572 {"Compose/---", NULL, "---", NULL, NULL, NULL },
573 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
576 static GtkActionEntry compose_entries[] =
578 {"Menu", NULL, "Menu" },
580 {"Message", NULL, N_("_Message") },
581 {"Edit", NULL, N_("_Edit") },
583 {"Spelling", NULL, N_("_Spelling") },
585 {"Options", NULL, N_("_Options") },
586 {"Tools", NULL, N_("_Tools") },
587 {"Help", NULL, N_("_Help") },
589 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
590 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
591 {"Message/---", NULL, "---" },
593 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
594 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
595 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
596 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
597 /* {"Message/---", NULL, "---" }, */
598 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
599 /* {"Message/---", NULL, "---" }, */
600 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
601 /* {"Message/---", NULL, "---" }, */
602 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
605 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
606 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
607 {"Edit/---", NULL, "---" },
609 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
610 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
611 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
613 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
614 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
615 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
616 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
618 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
620 {"Edit/Advanced", NULL, N_("A_dvanced") },
621 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
622 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
623 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
624 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
625 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
626 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
627 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
628 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
629 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
630 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
631 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
632 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
633 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
634 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
641 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
646 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
647 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
648 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
649 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
651 {"Spelling/---", NULL, "---" },
652 {"Spelling/Options", NULL, N_("_Options") },
657 {"Options/ReplyMode", NULL, N_("Reply _mode") },
658 {"Options/---", NULL, "---" },
659 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
660 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662 /* {"Options/---", NULL, "---" }, */
664 {"Options/Priority", NULL, N_("_Priority") },
666 {"Options/Encoding", NULL, N_("Character _encoding") },
667 {"Options/Encoding/---", NULL, "---" },
668 #define ENC_ACTION(cs_char,c_char,string) \
669 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
671 {"Options/Encoding/Western", NULL, N_("Western European") },
672 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
673 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
674 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
675 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
676 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
677 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
678 {"Options/Encoding/Korean", NULL, N_("Korean") },
679 {"Options/Encoding/Thai", NULL, N_("Thai") },
682 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
684 {"Tools/Template", NULL, N_("_Template") },
685 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
686 {"Tools/Actions", NULL, N_("Actio_ns") },
687 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
690 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
693 static GtkToggleActionEntry compose_toggle_entries[] =
695 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
696 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
697 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
698 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
699 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
700 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
701 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
704 static GtkRadioActionEntry compose_radio_rm_entries[] =
706 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
707 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
708 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
709 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
712 static GtkRadioActionEntry compose_radio_prio_entries[] =
714 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
716 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
717 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
718 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
721 static GtkRadioActionEntry compose_radio_enc_entries[] =
723 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
757 static GtkTargetEntry compose_mime_types[] =
759 {"text/uri-list", 0, 0},
760 {"UTF8_STRING", 0, 0},
764 static gboolean compose_put_existing_to_front(MsgInfo *info)
766 const GList *compose_list = compose_get_compose_list();
767 const GList *elem = NULL;
770 for (elem = compose_list; elem != NULL && elem->data != NULL;
772 Compose *c = (Compose*)elem->data;
774 if (!c->targetinfo || !c->targetinfo->msgid ||
778 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
779 gtkut_window_popup(c->window);
787 static GdkColor quote_color1 =
788 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
789 static GdkColor quote_color2 =
790 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_color3 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
794 static GdkColor quote_bgcolor1 =
795 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
796 static GdkColor quote_bgcolor2 =
797 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor quote_bgcolor3 =
799 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
801 static GdkColor signature_color = {
808 static GdkColor uri_color = {
815 static void compose_create_tags(GtkTextView *text, Compose *compose)
817 GtkTextBuffer *buffer;
818 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 #if !GTK_CHECK_VERSION(2, 24, 0)
826 buffer = gtk_text_view_get_buffer(text);
828 if (prefs_common.enable_color) {
829 /* grab the quote colors, converting from an int to a GdkColor */
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
836 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
838 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
840 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
842 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
844 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
847 signature_color = quote_color1 = quote_color2 = quote_color3 =
848 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
851 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
852 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
853 "foreground-gdk", "e_color1,
854 "paragraph-background-gdk", "e_bgcolor1,
856 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
857 "foreground-gdk", "e_color2,
858 "paragraph-background-gdk", "e_bgcolor2,
860 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
861 "foreground-gdk", "e_color3,
862 "paragraph-background-gdk", "e_bgcolor3,
865 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
866 "foreground-gdk", "e_color1,
868 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
869 "foreground-gdk", "e_color2,
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-gdk", "e_color3,
876 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
877 "foreground-gdk", &signature_color,
880 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
881 "foreground-gdk", &uri_color,
883 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
884 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
886 #if !GTK_CHECK_VERSION(2, 24, 0)
887 color[0] = quote_color1;
888 color[1] = quote_color2;
889 color[2] = quote_color3;
890 color[3] = quote_bgcolor1;
891 color[4] = quote_bgcolor2;
892 color[5] = quote_bgcolor3;
893 color[6] = signature_color;
894 color[7] = uri_color;
896 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
897 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
899 for (i = 0; i < 8; i++) {
900 if (success[i] == FALSE) {
901 g_warning("Compose: color allocation failed.\n");
902 quote_color1 = quote_color2 = quote_color3 =
903 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
904 signature_color = uri_color = black;
910 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
913 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
916 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
918 return compose_generic_new(account, mailto, item, NULL, NULL);
921 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
923 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
926 #define SCROLL_TO_CURSOR(compose) { \
927 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
928 gtk_text_view_get_buffer( \
929 GTK_TEXT_VIEW(compose->text))); \
930 gtk_text_view_scroll_mark_onscreen( \
931 GTK_TEXT_VIEW(compose->text), \
935 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
938 if (folderidentifier) {
939 #if !GTK_CHECK_VERSION(2, 24, 0)
940 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
942 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
944 prefs_common.compose_save_to_history = add_history(
945 prefs_common.compose_save_to_history, folderidentifier);
946 #if !GTK_CHECK_VERSION(2, 24, 0)
947 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
948 prefs_common.compose_save_to_history);
950 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
951 prefs_common.compose_save_to_history);
955 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
956 if (folderidentifier)
957 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
959 gtk_entry_set_text(GTK_ENTRY(entry), "");
962 static gchar *compose_get_save_to(Compose *compose)
965 gchar *result = NULL;
966 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
967 result = gtk_editable_get_chars(entry, 0, -1);
970 #if !GTK_CHECK_VERSION(2, 24, 0)
971 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
973 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
975 prefs_common.compose_save_to_history = add_history(
976 prefs_common.compose_save_to_history, result);
977 #if !GTK_CHECK_VERSION(2, 24, 0)
978 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
979 prefs_common.compose_save_to_history);
981 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
982 prefs_common.compose_save_to_history);
988 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
989 GList *attach_files, GList *listAddress )
992 GtkTextView *textview;
993 GtkTextBuffer *textbuf;
995 const gchar *subject_format = NULL;
996 const gchar *body_format = NULL;
997 gchar *mailto_from = NULL;
998 PrefsAccount *mailto_account = NULL;
999 MsgInfo* dummyinfo = NULL;
1000 gint cursor_pos = -1;
1001 MailField mfield = NO_FIELD_PRESENT;
1005 /* check if mailto defines a from */
1006 if (mailto && *mailto != '\0') {
1007 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1008 /* mailto defines a from, check if we can get account prefs from it,
1009 if not, the account prefs will be guessed using other ways, but we'll keep
1012 mailto_account = account_find_from_address(mailto_from, TRUE);
1013 if (mailto_account == NULL) {
1015 Xstrdup_a(tmp_from, mailto_from, return NULL);
1016 extract_address(tmp_from);
1017 mailto_account = account_find_from_address(tmp_from, TRUE);
1021 account = mailto_account;
1024 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1025 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1026 account = account_find_from_id(item->prefs->default_account);
1028 /* if no account prefs set, fallback to the current one */
1029 if (!account) account = cur_account;
1030 cm_return_val_if_fail(account != NULL, NULL);
1032 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1034 /* override from name if mailto asked for it */
1036 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1037 g_free(mailto_from);
1039 /* override from name according to folder properties */
1040 if (item && item->prefs &&
1041 item->prefs->compose_with_format &&
1042 item->prefs->compose_override_from_format &&
1043 *item->prefs->compose_override_from_format != '\0') {
1048 dummyinfo = compose_msginfo_new_from_compose(compose);
1050 /* decode \-escape sequences in the internal representation of the quote format */
1051 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1052 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1055 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1056 compose->gtkaspell);
1058 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1060 quote_fmt_scan_string(tmp);
1063 buf = quote_fmt_get_buffer();
1065 alertpanel_error(_("New message From format error."));
1067 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1068 quote_fmt_reset_vartable();
1073 compose->replyinfo = NULL;
1074 compose->fwdinfo = NULL;
1076 textview = GTK_TEXT_VIEW(compose->text);
1077 textbuf = gtk_text_view_get_buffer(textview);
1078 compose_create_tags(textview, compose);
1080 undo_block(compose->undostruct);
1082 compose_set_dictionaries_from_folder_prefs(compose, item);
1085 if (account->auto_sig)
1086 compose_insert_sig(compose, FALSE);
1087 gtk_text_buffer_get_start_iter(textbuf, &iter);
1088 gtk_text_buffer_place_cursor(textbuf, &iter);
1090 if (account->protocol != A_NNTP) {
1091 if (mailto && *mailto != '\0') {
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1095 compose_set_folder_prefs(compose, item, TRUE);
1097 if (item && item->ret_rcpt) {
1098 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1101 if (mailto && *mailto != '\0') {
1102 if (!strchr(mailto, '@'))
1103 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1105 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1106 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1107 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1108 mfield = TO_FIELD_PRESENT;
1111 * CLAWS: just don't allow return receipt request, even if the user
1112 * may want to send an email. simple but foolproof.
1114 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1116 compose_add_field_list( compose, listAddress );
1118 if (item && item->prefs && item->prefs->compose_with_format) {
1119 subject_format = item->prefs->compose_subject_format;
1120 body_format = item->prefs->compose_body_format;
1121 } else if (account->compose_with_format) {
1122 subject_format = account->compose_subject_format;
1123 body_format = account->compose_body_format;
1124 } else if (prefs_common.compose_with_format) {
1125 subject_format = prefs_common.compose_subject_format;
1126 body_format = prefs_common.compose_body_format;
1129 if (subject_format || body_format) {
1132 && *subject_format != '\0' )
1134 gchar *subject = NULL;
1139 dummyinfo = compose_msginfo_new_from_compose(compose);
1141 /* decode \-escape sequences in the internal representation of the quote format */
1142 tmp = g_malloc(strlen(subject_format)+1);
1143 pref_get_unescaped_pref(tmp, subject_format);
1145 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1148 compose->gtkaspell);
1150 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1152 quote_fmt_scan_string(tmp);
1155 buf = quote_fmt_get_buffer();
1157 alertpanel_error(_("New message subject format error."));
1159 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1160 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1161 quote_fmt_reset_vartable();
1165 mfield = SUBJECT_FIELD_PRESENT;
1169 && *body_format != '\0' )
1172 GtkTextBuffer *buffer;
1173 GtkTextIter start, end;
1177 dummyinfo = compose_msginfo_new_from_compose(compose);
1179 text = GTK_TEXT_VIEW(compose->text);
1180 buffer = gtk_text_view_get_buffer(text);
1181 gtk_text_buffer_get_start_iter(buffer, &start);
1182 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1183 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1185 compose_quote_fmt(compose, dummyinfo,
1187 NULL, tmp, FALSE, TRUE,
1188 _("The body of the \"New message\" template has an error at line %d."));
1189 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1190 quote_fmt_reset_vartable();
1194 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1195 gtkaspell_highlight_all(compose->gtkaspell);
1197 mfield = BODY_FIELD_PRESENT;
1201 procmsg_msginfo_free( dummyinfo );
1207 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1208 ainfo = (AttachInfo *) curr->data;
1209 compose_attach_append(compose, ainfo->file, ainfo->file,
1210 ainfo->content_type, ainfo->charset);
1214 compose_show_first_last_header(compose, TRUE);
1216 /* Set save folder */
1217 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1218 gchar *folderidentifier;
1220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1221 folderidentifier = folder_item_get_identifier(item);
1222 compose_set_save_to(compose, folderidentifier);
1223 g_free(folderidentifier);
1226 /* Place cursor according to provided input (mfield) */
1228 case NO_FIELD_PRESENT:
1229 if (compose->header_last)
1230 gtk_widget_grab_focus(compose->header_last->entry);
1232 case TO_FIELD_PRESENT:
1233 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1235 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1238 gtk_widget_grab_focus(compose->subject_entry);
1240 case SUBJECT_FIELD_PRESENT:
1241 textview = GTK_TEXT_VIEW(compose->text);
1244 textbuf = gtk_text_view_get_buffer(textview);
1247 mark = gtk_text_buffer_get_insert(textbuf);
1248 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1249 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1251 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1252 * only defers where it comes to the variable body
1253 * is not null. If no body is present compose->text
1254 * will be null in which case you cannot place the
1255 * cursor inside the component so. An empty component
1256 * is therefore created before placing the cursor
1258 case BODY_FIELD_PRESENT:
1259 cursor_pos = quote_fmt_get_cursor_pos();
1260 if (cursor_pos == -1)
1261 gtk_widget_grab_focus(compose->header_last->entry);
1263 gtk_widget_grab_focus(compose->text);
1267 undo_unblock(compose->undostruct);
1269 if (prefs_common.auto_exteditor)
1270 compose_exec_ext_editor(compose);
1272 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1274 SCROLL_TO_CURSOR(compose);
1276 compose->modified = FALSE;
1277 compose_set_title(compose);
1279 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1284 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1285 gboolean override_pref, const gchar *system)
1287 const gchar *privacy = NULL;
1289 cm_return_if_fail(compose != NULL);
1290 cm_return_if_fail(account != NULL);
1292 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1295 if (account->default_privacy_system && strlen(account->default_privacy_system))
1296 privacy = account->default_privacy_system;
1300 GSList *privacy_avail = privacy_get_system_ids();
1301 if (privacy_avail && g_slist_length(privacy_avail)) {
1302 privacy = (gchar *)(privacy_avail->data);
1305 if (privacy != NULL) {
1307 g_free(compose->privacy_system);
1308 compose->privacy_system = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 else if (*(compose->privacy_system) == '\0') {
1313 g_free(compose->privacy_system);
1314 compose->privacy_system = g_strdup(privacy);
1316 compose_update_privacy_system_menu_item(compose, FALSE);
1317 compose_use_encryption(compose, TRUE);
1321 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1323 const gchar *privacy = NULL;
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1341 if (compose->privacy_system == NULL)
1342 compose->privacy_system = g_strdup(privacy);
1343 compose_update_privacy_system_menu_item(compose, FALSE);
1344 compose_use_signing(compose, TRUE);
1348 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1352 Compose *compose = NULL;
1354 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1356 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1357 cm_return_val_if_fail(msginfo != NULL, NULL);
1359 list_len = g_slist_length(msginfo_list);
1363 case COMPOSE_REPLY_TO_ADDRESS:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1365 FALSE, prefs_common.default_reply_list, FALSE, body);
1367 case COMPOSE_REPLY_WITH_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1369 FALSE, prefs_common.default_reply_list, FALSE, body);
1371 case COMPOSE_REPLY_WITHOUT_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1373 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1375 case COMPOSE_REPLY_TO_SENDER:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, FALSE, TRUE, body);
1379 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1380 compose = compose_followup_and_reply_to(msginfo,
1381 COMPOSE_QUOTE_CHECK,
1382 FALSE, FALSE, body);
1384 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1386 FALSE, FALSE, TRUE, body);
1388 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1390 FALSE, FALSE, TRUE, NULL);
1392 case COMPOSE_REPLY_TO_ALL:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1394 TRUE, FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 TRUE, FALSE, FALSE, body);
1400 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 TRUE, FALSE, FALSE, NULL);
1404 case COMPOSE_REPLY_TO_LIST:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 FALSE, TRUE, FALSE, body);
1408 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 FALSE, TRUE, FALSE, body);
1412 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 FALSE, TRUE, FALSE, NULL);
1416 case COMPOSE_FORWARD:
1417 if (prefs_common.forward_as_attachment) {
1418 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1421 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1425 case COMPOSE_FORWARD_INLINE:
1426 /* check if we reply to more than one Message */
1427 if (list_len == 1) {
1428 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1431 /* more messages FALL THROUGH */
1432 case COMPOSE_FORWARD_AS_ATTACH:
1433 compose = compose_forward_multiple(NULL, msginfo_list);
1435 case COMPOSE_REDIRECT:
1436 compose = compose_redirect(NULL, msginfo, FALSE);
1439 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1442 if (compose == NULL) {
1443 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1447 compose->rmode = mode;
1448 switch (compose->rmode) {
1450 case COMPOSE_REPLY_WITH_QUOTE:
1451 case COMPOSE_REPLY_WITHOUT_QUOTE:
1452 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1453 debug_print("reply mode Normal\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1455 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1457 case COMPOSE_REPLY_TO_SENDER:
1458 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1459 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1460 debug_print("reply mode Sender\n");
1461 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1463 case COMPOSE_REPLY_TO_ALL:
1464 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1465 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1466 debug_print("reply mode All\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1469 case COMPOSE_REPLY_TO_LIST:
1470 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1472 debug_print("reply mode List\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1475 case COMPOSE_REPLY_TO_ADDRESS:
1476 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1484 static Compose *compose_reply(MsgInfo *msginfo,
1485 ComposeQuoteMode quote_mode,
1491 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1492 to_sender, FALSE, body);
1495 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1496 ComposeQuoteMode quote_mode,
1501 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1502 to_sender, TRUE, body);
1505 static void compose_extract_original_charset(Compose *compose)
1507 MsgInfo *info = NULL;
1508 if (compose->replyinfo) {
1509 info = compose->replyinfo;
1510 } else if (compose->fwdinfo) {
1511 info = compose->fwdinfo;
1512 } else if (compose->targetinfo) {
1513 info = compose->targetinfo;
1516 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1517 MimeInfo *partinfo = mimeinfo;
1518 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1519 partinfo = procmime_mimeinfo_next(partinfo);
1521 compose->orig_charset =
1522 g_strdup(procmime_mimeinfo_get_parameter(
1523 partinfo, "charset"));
1525 procmime_mimeinfo_free_all(mimeinfo);
1529 #define SIGNAL_BLOCK(buffer) { \
1530 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1533 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1538 #define SIGNAL_UNBLOCK(buffer) { \
1539 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1540 G_CALLBACK(compose_changed_cb), \
1542 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(text_inserted), \
1547 static Compose *compose_generic_reply(MsgInfo *msginfo,
1548 ComposeQuoteMode quote_mode,
1549 gboolean to_all, gboolean to_ml,
1551 gboolean followup_and_reply_to,
1555 PrefsAccount *account = NULL;
1556 GtkTextView *textview;
1557 GtkTextBuffer *textbuf;
1558 gboolean quote = FALSE;
1559 const gchar *qmark = NULL;
1560 const gchar *body_fmt = NULL;
1561 gchar *s_system = NULL;
1563 cm_return_val_if_fail(msginfo != NULL, NULL);
1564 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1566 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1568 cm_return_val_if_fail(account != NULL, NULL);
1570 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1572 compose->updating = TRUE;
1574 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1577 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1578 if (!compose->replyinfo)
1579 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1581 compose_extract_original_charset(compose);
1583 if (msginfo->folder && msginfo->folder->ret_rcpt)
1584 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1586 /* Set save folder */
1587 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1588 gchar *folderidentifier;
1590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1591 folderidentifier = folder_item_get_identifier(msginfo->folder);
1592 compose_set_save_to(compose, folderidentifier);
1593 g_free(folderidentifier);
1596 if (compose_parse_header(compose, msginfo) < 0) {
1597 compose->updating = FALSE;
1598 compose_destroy(compose);
1602 /* override from name according to folder properties */
1603 if (msginfo->folder && msginfo->folder->prefs &&
1604 msginfo->folder->prefs->reply_with_format &&
1605 msginfo->folder->prefs->reply_override_from_format &&
1606 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1611 /* decode \-escape sequences in the internal representation of the quote format */
1612 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1613 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1616 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1617 compose->gtkaspell);
1619 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1621 quote_fmt_scan_string(tmp);
1624 buf = quote_fmt_get_buffer();
1626 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1628 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1629 quote_fmt_reset_vartable();
1634 textview = (GTK_TEXT_VIEW(compose->text));
1635 textbuf = gtk_text_view_get_buffer(textview);
1636 compose_create_tags(textview, compose);
1638 undo_block(compose->undostruct);
1640 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1641 gtkaspell_block_check(compose->gtkaspell);
1644 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1645 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1646 /* use the reply format of folder (if enabled), or the account's one
1647 (if enabled) or fallback to the global reply format, which is always
1648 enabled (even if empty), and use the relevant quotemark */
1650 if (msginfo->folder && msginfo->folder->prefs &&
1651 msginfo->folder->prefs->reply_with_format) {
1652 qmark = msginfo->folder->prefs->reply_quotemark;
1653 body_fmt = msginfo->folder->prefs->reply_body_format;
1655 } else if (account->reply_with_format) {
1656 qmark = account->reply_quotemark;
1657 body_fmt = account->reply_body_format;
1660 qmark = prefs_common.quotemark;
1661 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1662 body_fmt = gettext(prefs_common.quotefmt);
1669 /* empty quotemark is not allowed */
1670 if (qmark == NULL || *qmark == '\0')
1672 compose_quote_fmt(compose, compose->replyinfo,
1673 body_fmt, qmark, body, FALSE, TRUE,
1674 _("The body of the \"Reply\" template has an error at line %d."));
1675 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1676 quote_fmt_reset_vartable();
1679 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1680 compose_force_encryption(compose, account, FALSE, s_system);
1683 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1684 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1685 compose_force_signing(compose, account, s_system);
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1748 !(account = compose_guess_forward_account_from_msginfo
1750 account = cur_account;
1752 if (!prefs_common.forward_as_attachment)
1753 mode = COMPOSE_FORWARD_INLINE;
1755 mode = COMPOSE_FORWARD;
1756 compose = compose_create(account, msginfo->folder, mode, batch);
1758 compose->updating = TRUE;
1759 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1760 if (!compose->fwdinfo)
1761 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1763 compose_extract_original_charset(compose);
1765 if (msginfo->subject && *msginfo->subject) {
1766 gchar *buf, *buf2, *p;
1768 buf = p = g_strdup(msginfo->subject);
1769 p += subject_get_prefix_length(p);
1770 memmove(buf, p, strlen(p) + 1);
1772 buf2 = g_strdup_printf("Fw: %s", buf);
1773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1779 /* override from name according to folder properties */
1780 if (msginfo->folder && msginfo->folder->prefs &&
1781 msginfo->folder->prefs->forward_with_format &&
1782 msginfo->folder->prefs->forward_override_from_format &&
1783 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1787 MsgInfo *full_msginfo = NULL;
1790 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1792 full_msginfo = procmsg_msginfo_copy(msginfo);
1794 /* decode \-escape sequences in the internal representation of the quote format */
1795 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1796 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1799 gtkaspell_block_check(compose->gtkaspell);
1800 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1801 compose->gtkaspell);
1803 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1805 quote_fmt_scan_string(tmp);
1808 buf = quote_fmt_get_buffer();
1810 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1812 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1813 quote_fmt_reset_vartable();
1816 procmsg_msginfo_free(full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file not exist\n", msgfile);
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1890 SIGNAL_UNBLOCK(textbuf);
1892 cursor_pos = quote_fmt_get_cursor_pos();
1893 if (cursor_pos == -1)
1894 gtk_widget_grab_focus(compose->header_last->entry);
1896 gtk_widget_grab_focus(compose->text);
1898 if (!no_extedit && prefs_common.auto_exteditor)
1899 compose_exec_ext_editor(compose);
1902 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1903 gchar *folderidentifier;
1905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1906 folderidentifier = folder_item_get_identifier(msginfo->folder);
1907 compose_set_save_to(compose, folderidentifier);
1908 g_free(folderidentifier);
1911 undo_unblock(compose->undostruct);
1913 compose->modified = FALSE;
1914 compose_set_title(compose);
1916 compose->updating = FALSE;
1917 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1918 SCROLL_TO_CURSOR(compose);
1920 if (compose->deferred_destroy) {
1921 compose_destroy(compose);
1925 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1930 #undef INSERT_FW_HEADER
1932 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1935 GtkTextView *textview;
1936 GtkTextBuffer *textbuf;
1940 gboolean single_mail = TRUE;
1942 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1944 if (g_slist_length(msginfo_list) > 1)
1945 single_mail = FALSE;
1947 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1948 if (((MsgInfo *)msginfo->data)->folder == NULL)
1951 /* guess account from first selected message */
1953 !(account = compose_guess_forward_account_from_msginfo
1954 (msginfo_list->data)))
1955 account = cur_account;
1957 cm_return_val_if_fail(account != NULL, NULL);
1959 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1960 if (msginfo->data) {
1961 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1962 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1966 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1967 g_warning("no msginfo_list");
1971 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1973 compose->updating = TRUE;
1975 /* override from name according to folder properties */
1976 if (msginfo_list->data) {
1977 MsgInfo *msginfo = msginfo_list->data;
1979 if (msginfo->folder && msginfo->folder->prefs &&
1980 msginfo->folder->prefs->forward_with_format &&
1981 msginfo->folder->prefs->forward_override_from_format &&
1982 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1987 /* decode \-escape sequences in the internal representation of the quote format */
1988 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1989 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1992 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1993 compose->gtkaspell);
1995 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1997 quote_fmt_scan_string(tmp);
2000 buf = quote_fmt_get_buffer();
2002 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2004 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2005 quote_fmt_reset_vartable();
2011 textview = GTK_TEXT_VIEW(compose->text);
2012 textbuf = gtk_text_view_get_buffer(textview);
2013 compose_create_tags(textview, compose);
2015 undo_block(compose->undostruct);
2016 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2017 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2019 if (!is_file_exist(msgfile))
2020 g_warning("%s: file not exist\n", msgfile);
2022 compose_attach_append(compose, msgfile, msgfile,
2023 "message/rfc822", NULL);
2028 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2029 if (info->subject && *info->subject) {
2030 gchar *buf, *buf2, *p;
2032 buf = p = g_strdup(info->subject);
2033 p += subject_get_prefix_length(p);
2034 memmove(buf, p, strlen(p) + 1);
2036 buf2 = g_strdup_printf("Fw: %s", buf);
2037 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2043 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2044 _("Fw: multiple emails"));
2047 SIGNAL_BLOCK(textbuf);
2049 if (account->auto_sig)
2050 compose_insert_sig(compose, FALSE);
2052 compose_wrap_all(compose);
2054 SIGNAL_UNBLOCK(textbuf);
2056 gtk_text_buffer_get_start_iter(textbuf, &iter);
2057 gtk_text_buffer_place_cursor(textbuf, &iter);
2059 gtk_widget_grab_focus(compose->header_last->entry);
2060 undo_unblock(compose->undostruct);
2061 compose->modified = FALSE;
2062 compose_set_title(compose);
2064 compose->updating = FALSE;
2065 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2066 SCROLL_TO_CURSOR(compose);
2068 if (compose->deferred_destroy) {
2069 compose_destroy(compose);
2073 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2078 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2080 GtkTextIter start = *iter;
2081 GtkTextIter end_iter;
2082 int start_pos = gtk_text_iter_get_offset(&start);
2084 if (!compose->account->sig_sep)
2087 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2088 start_pos+strlen(compose->account->sig_sep));
2090 /* check sig separator */
2091 str = gtk_text_iter_get_text(&start, &end_iter);
2092 if (!strcmp(str, compose->account->sig_sep)) {
2094 /* check end of line (\n) */
2095 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2096 start_pos+strlen(compose->account->sig_sep));
2097 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2098 start_pos+strlen(compose->account->sig_sep)+1);
2099 tmp = gtk_text_iter_get_text(&start, &end_iter);
2100 if (!strcmp(tmp,"\n")) {
2112 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2114 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2115 Compose *compose = (Compose *)data;
2116 FolderItem *old_item = NULL;
2117 FolderItem *new_item = NULL;
2118 gchar *old_id, *new_id;
2120 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2121 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2124 old_item = hookdata->item;
2125 new_item = hookdata->item2;
2127 old_id = folder_item_get_identifier(old_item);
2128 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2130 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2131 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->targetinfo->folder = new_item;
2135 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2136 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2137 compose->replyinfo->folder = new_item;
2140 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2141 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2142 compose->fwdinfo->folder = new_item;
2150 static void compose_colorize_signature(Compose *compose)
2152 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2154 GtkTextIter end_iter;
2155 gtk_text_buffer_get_start_iter(buffer, &iter);
2156 while (gtk_text_iter_forward_line(&iter))
2157 if (compose_is_sig_separator(compose, buffer, &iter)) {
2158 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2159 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2163 #define BLOCK_WRAP() { \
2164 prev_autowrap = compose->autowrap; \
2165 buffer = gtk_text_view_get_buffer( \
2166 GTK_TEXT_VIEW(compose->text)); \
2167 compose->autowrap = FALSE; \
2169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2170 G_CALLBACK(compose_changed_cb), \
2172 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2173 G_CALLBACK(text_inserted), \
2176 #define UNBLOCK_WRAP() { \
2177 compose->autowrap = prev_autowrap; \
2178 if (compose->autowrap) { \
2179 gint old = compose->draft_timeout_tag; \
2180 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2181 compose_wrap_all(compose); \
2182 compose->draft_timeout_tag = old; \
2185 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(compose_changed_cb), \
2188 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2189 G_CALLBACK(text_inserted), \
2193 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2195 Compose *compose = NULL;
2196 PrefsAccount *account = NULL;
2197 GtkTextView *textview;
2198 GtkTextBuffer *textbuf;
2202 gchar buf[BUFFSIZE];
2203 gboolean use_signing = FALSE;
2204 gboolean use_encryption = FALSE;
2205 gchar *privacy_system = NULL;
2206 int priority = PRIORITY_NORMAL;
2207 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2208 gboolean autowrap = prefs_common.autowrap;
2209 gboolean autoindent = prefs_common.auto_indent;
2210 HeaderEntry *manual_headers = NULL;
2212 cm_return_val_if_fail(msginfo != NULL, NULL);
2213 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2215 if (compose_put_existing_to_front(msginfo)) {
2219 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2220 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2221 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2222 gchar queueheader_buf[BUFFSIZE];
2225 /* Select Account from queue headers */
2226 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2227 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2228 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2229 account = account_find_from_id(id);
2231 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2232 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2233 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2234 account = account_find_from_id(id);
2236 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2237 sizeof(queueheader_buf), "NAID:")) {
2238 id = atoi(&queueheader_buf[strlen("NAID:")]);
2239 account = account_find_from_id(id);
2241 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "MAID:")) {
2243 id = atoi(&queueheader_buf[strlen("MAID:")]);
2244 account = account_find_from_id(id);
2246 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2247 sizeof(queueheader_buf), "S:")) {
2248 account = account_find_from_address(queueheader_buf, FALSE);
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2251 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2252 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2253 use_signing = param;
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2258 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2259 use_signing = param;
2262 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2263 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2264 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2265 use_encryption = param;
2267 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2268 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2269 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2270 use_encryption = param;
2272 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2273 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2277 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2278 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2284 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2286 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2287 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2288 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2290 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2291 sizeof(queueheader_buf), "X-Priority: ")) {
2292 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2295 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2296 sizeof(queueheader_buf), "RMID:")) {
2297 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2298 if (tokens[0] && tokens[1] && tokens[2]) {
2299 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2300 if (orig_item != NULL) {
2301 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "FMID:")) {
2308 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2309 if (tokens[0] && tokens[1] && tokens[2]) {
2310 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2311 if (orig_item != NULL) {
2312 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2317 /* Get manual headers */
2318 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2319 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2320 if (*listmh != '\0') {
2321 debug_print("Got manual headers: %s\n", listmh);
2322 manual_headers = procheader_entries_from_str(listmh);
2327 account = msginfo->folder->folder->account;
2330 if (!account && prefs_common.reedit_account_autosel) {
2331 gchar from[BUFFSIZE];
2332 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2333 extract_address(from);
2334 account = account_find_from_address(from, FALSE);
2338 account = cur_account;
2340 cm_return_val_if_fail(account != NULL, NULL);
2342 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2344 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2345 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2346 compose->autowrap = autowrap;
2347 compose->replyinfo = replyinfo;
2348 compose->fwdinfo = fwdinfo;
2350 compose->updating = TRUE;
2351 compose->priority = priority;
2353 if (privacy_system != NULL) {
2354 compose->privacy_system = privacy_system;
2355 compose_use_signing(compose, use_signing);
2356 compose_use_encryption(compose, use_encryption);
2357 compose_update_privacy_system_menu_item(compose, FALSE);
2359 activate_privacy_system(compose, account, FALSE);
2362 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2364 compose_extract_original_charset(compose);
2366 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2367 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2368 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2369 gchar queueheader_buf[BUFFSIZE];
2371 /* Set message save folder */
2372 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2373 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2374 compose_set_save_to(compose, &queueheader_buf[4]);
2376 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2377 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2384 if (compose_parse_header(compose, msginfo) < 0) {
2385 compose->updating = FALSE;
2386 compose_destroy(compose);
2389 compose_reedit_set_entry(compose, msginfo);
2391 textview = GTK_TEXT_VIEW(compose->text);
2392 textbuf = gtk_text_view_get_buffer(textview);
2393 compose_create_tags(textview, compose);
2395 mark = gtk_text_buffer_get_insert(textbuf);
2396 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2398 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2399 G_CALLBACK(compose_changed_cb),
2402 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2403 fp = procmime_get_first_encrypted_text_content(msginfo);
2405 compose_force_encryption(compose, account, TRUE, NULL);
2408 fp = procmime_get_first_text_content(msginfo);
2411 g_warning("Can't get text part\n");
2415 gboolean prev_autowrap;
2416 GtkTextBuffer *buffer;
2418 while (fgets(buf, sizeof(buf), fp) != NULL) {
2420 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2426 compose_attach_parts(compose, msginfo);
2428 compose_colorize_signature(compose);
2430 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2431 G_CALLBACK(compose_changed_cb),
2434 if (manual_headers != NULL) {
2435 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2436 procheader_entries_free(manual_headers);
2437 compose->updating = FALSE;
2438 compose_destroy(compose);
2441 procheader_entries_free(manual_headers);
2444 gtk_widget_grab_focus(compose->text);
2446 if (prefs_common.auto_exteditor) {
2447 compose_exec_ext_editor(compose);
2449 compose->modified = FALSE;
2450 compose_set_title(compose);
2452 compose->updating = FALSE;
2453 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2454 SCROLL_TO_CURSOR(compose);
2456 if (compose->deferred_destroy) {
2457 compose_destroy(compose);
2461 compose->sig_str = account_get_signature_str(compose->account);
2463 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2468 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2475 cm_return_val_if_fail(msginfo != NULL, NULL);
2478 account = account_get_reply_account(msginfo,
2479 prefs_common.reply_account_autosel);
2480 cm_return_val_if_fail(account != NULL, NULL);
2482 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2484 compose->updating = TRUE;
2486 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2487 compose->replyinfo = NULL;
2488 compose->fwdinfo = NULL;
2490 compose_show_first_last_header(compose, TRUE);
2492 gtk_widget_grab_focus(compose->header_last->entry);
2494 filename = procmsg_get_message_file(msginfo);
2496 if (filename == NULL) {
2497 compose->updating = FALSE;
2498 compose_destroy(compose);
2503 compose->redirect_filename = filename;
2505 /* Set save folder */
2506 item = msginfo->folder;
2507 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2508 gchar *folderidentifier;
2510 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2511 folderidentifier = folder_item_get_identifier(item);
2512 compose_set_save_to(compose, folderidentifier);
2513 g_free(folderidentifier);
2516 compose_attach_parts(compose, msginfo);
2518 if (msginfo->subject)
2519 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2521 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2523 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2524 _("The body of the \"Redirect\" template has an error at line %d."));
2525 quote_fmt_reset_vartable();
2526 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2528 compose_colorize_signature(compose);
2531 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2532 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2533 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2543 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2545 if (compose->toolbar->draft_btn)
2546 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2547 if (compose->toolbar->insert_btn)
2548 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2549 if (compose->toolbar->attach_btn)
2550 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2551 if (compose->toolbar->sig_btn)
2552 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2553 if (compose->toolbar->exteditor_btn)
2554 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2555 if (compose->toolbar->linewrap_current_btn)
2556 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2557 if (compose->toolbar->linewrap_all_btn)
2558 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2560 compose->modified = FALSE;
2561 compose_set_title(compose);
2562 compose->updating = FALSE;
2563 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2564 SCROLL_TO_CURSOR(compose);
2566 if (compose->deferred_destroy) {
2567 compose_destroy(compose);
2571 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2576 const GList *compose_get_compose_list(void)
2578 return compose_list;
2581 void compose_entry_append(Compose *compose, const gchar *address,
2582 ComposeEntryType type, ComposePrefType pref_type)
2584 const gchar *header;
2586 gboolean in_quote = FALSE;
2587 if (!address || *address == '\0') return;
2594 header = N_("Bcc:");
2596 case COMPOSE_REPLYTO:
2597 header = N_("Reply-To:");
2599 case COMPOSE_NEWSGROUPS:
2600 header = N_("Newsgroups:");
2602 case COMPOSE_FOLLOWUPTO:
2603 header = N_( "Followup-To:");
2605 case COMPOSE_INREPLYTO:
2606 header = N_( "In-Reply-To:");
2613 header = prefs_common_translated_header_name(header);
2615 cur = begin = (gchar *)address;
2617 /* we separate the line by commas, but not if we're inside a quoted
2619 while (*cur != '\0') {
2621 in_quote = !in_quote;
2622 if (*cur == ',' && !in_quote) {
2623 gchar *tmp = g_strdup(begin);
2625 tmp[cur-begin]='\0';
2628 while (*tmp == ' ' || *tmp == '\t')
2630 compose_add_header_entry(compose, header, tmp, pref_type);
2637 gchar *tmp = g_strdup(begin);
2639 tmp[cur-begin]='\0';
2640 while (*tmp == ' ' || *tmp == '\t')
2642 compose_add_header_entry(compose, header, tmp, pref_type);
2647 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2649 #if !GTK_CHECK_VERSION(3, 0, 0)
2650 static GdkColor yellow;
2651 static GdkColor black;
2652 static gboolean yellow_initialised = FALSE;
2654 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2655 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2660 #if !GTK_CHECK_VERSION(3, 0, 0)
2661 if (!yellow_initialised) {
2662 gdk_color_parse("#f5f6be", &yellow);
2663 gdk_color_parse("#000000", &black);
2664 yellow_initialised = gdk_colormap_alloc_color(
2665 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2666 yellow_initialised &= gdk_colormap_alloc_color(
2667 gdk_colormap_get_system(), &black, FALSE, TRUE);
2671 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2672 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2673 if (gtk_entry_get_text(entry) &&
2674 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (yellow_initialised) {
2678 gtk_widget_modify_base(
2679 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2680 GTK_STATE_NORMAL, &yellow);
2681 gtk_widget_modify_text(
2682 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2683 GTK_STATE_NORMAL, &black);
2684 #if !GTK_CHECK_VERSION(3, 0, 0)
2691 void compose_toolbar_cb(gint action, gpointer data)
2693 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2694 Compose *compose = (Compose*)toolbar_item->parent;
2696 cm_return_if_fail(compose != NULL);
2700 compose_send_cb(NULL, compose);
2703 compose_send_later_cb(NULL, compose);
2706 compose_draft(compose, COMPOSE_QUIT_EDITING);
2709 compose_insert_file_cb(NULL, compose);
2712 compose_attach_cb(NULL, compose);
2715 compose_insert_sig(compose, FALSE);
2718 compose_insert_sig(compose, TRUE);
2721 compose_ext_editor_cb(NULL, compose);
2723 case A_LINEWRAP_CURRENT:
2724 compose_beautify_paragraph(compose, NULL, TRUE);
2726 case A_LINEWRAP_ALL:
2727 compose_wrap_all_full(compose, TRUE);
2730 compose_address_cb(NULL, compose);
2733 case A_CHECK_SPELLING:
2734 compose_check_all(NULL, compose);
2742 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2747 gchar *subject = NULL;
2751 gchar **attach = NULL;
2752 gchar *inreplyto = NULL;
2753 MailField mfield = NO_FIELD_PRESENT;
2755 /* get mailto parts but skip from */
2756 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2759 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2760 mfield = TO_FIELD_PRESENT;
2763 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2765 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2767 if (!g_utf8_validate (subject, -1, NULL)) {
2768 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2774 mfield = SUBJECT_FIELD_PRESENT;
2777 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2778 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2781 gboolean prev_autowrap = compose->autowrap;
2783 compose->autowrap = FALSE;
2785 mark = gtk_text_buffer_get_insert(buffer);
2786 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2788 if (!g_utf8_validate (body, -1, NULL)) {
2789 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2790 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2793 gtk_text_buffer_insert(buffer, &iter, body, -1);
2795 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2797 compose->autowrap = prev_autowrap;
2798 if (compose->autowrap)
2799 compose_wrap_all(compose);
2800 mfield = BODY_FIELD_PRESENT;
2804 gint i = 0, att = 0;
2805 gchar *warn_files = NULL;
2806 while (attach[i] != NULL) {
2807 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2808 if (utf8_filename) {
2809 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2810 gchar *tmp = g_strdup_printf("%s%s\n",
2811 warn_files?warn_files:"",
2817 g_free(utf8_filename);
2819 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2824 alertpanel_notice(ngettext(
2825 "The following file has been attached: \n%s",
2826 "The following files have been attached: \n%s", att), warn_files);
2831 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2844 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2846 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2847 {"Cc:", NULL, TRUE},
2848 {"References:", NULL, FALSE},
2849 {"Bcc:", NULL, TRUE},
2850 {"Newsgroups:", NULL, TRUE},
2851 {"Followup-To:", NULL, TRUE},
2852 {"List-Post:", NULL, FALSE},
2853 {"X-Priority:", NULL, FALSE},
2854 {NULL, NULL, FALSE}};
2870 cm_return_val_if_fail(msginfo != NULL, -1);
2872 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2873 procheader_get_header_fields(fp, hentry);
2876 if (hentry[H_REPLY_TO].body != NULL) {
2877 if (hentry[H_REPLY_TO].body[0] != '\0') {
2879 conv_unmime_header(hentry[H_REPLY_TO].body,
2882 g_free(hentry[H_REPLY_TO].body);
2883 hentry[H_REPLY_TO].body = NULL;
2885 if (hentry[H_CC].body != NULL) {
2886 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2887 g_free(hentry[H_CC].body);
2888 hentry[H_CC].body = NULL;
2890 if (hentry[H_REFERENCES].body != NULL) {
2891 if (compose->mode == COMPOSE_REEDIT)
2892 compose->references = hentry[H_REFERENCES].body;
2894 compose->references = compose_parse_references
2895 (hentry[H_REFERENCES].body, msginfo->msgid);
2896 g_free(hentry[H_REFERENCES].body);
2898 hentry[H_REFERENCES].body = NULL;
2900 if (hentry[H_BCC].body != NULL) {
2901 if (compose->mode == COMPOSE_REEDIT)
2903 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2904 g_free(hentry[H_BCC].body);
2905 hentry[H_BCC].body = NULL;
2907 if (hentry[H_NEWSGROUPS].body != NULL) {
2908 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2909 hentry[H_NEWSGROUPS].body = NULL;
2911 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2912 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2913 compose->followup_to =
2914 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2917 g_free(hentry[H_FOLLOWUP_TO].body);
2918 hentry[H_FOLLOWUP_TO].body = NULL;
2920 if (hentry[H_LIST_POST].body != NULL) {
2921 gchar *to = NULL, *start = NULL;
2923 extract_address(hentry[H_LIST_POST].body);
2924 if (hentry[H_LIST_POST].body[0] != '\0') {
2925 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2927 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2928 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2931 g_free(compose->ml_post);
2932 compose->ml_post = to;
2935 g_free(hentry[H_LIST_POST].body);
2936 hentry[H_LIST_POST].body = NULL;
2939 /* CLAWS - X-Priority */
2940 if (compose->mode == COMPOSE_REEDIT)
2941 if (hentry[H_X_PRIORITY].body != NULL) {
2944 priority = atoi(hentry[H_X_PRIORITY].body);
2945 g_free(hentry[H_X_PRIORITY].body);
2947 hentry[H_X_PRIORITY].body = NULL;
2949 if (priority < PRIORITY_HIGHEST ||
2950 priority > PRIORITY_LOWEST)
2951 priority = PRIORITY_NORMAL;
2953 compose->priority = priority;
2956 if (compose->mode == COMPOSE_REEDIT) {
2957 if (msginfo->inreplyto && *msginfo->inreplyto)
2958 compose->inreplyto = g_strdup(msginfo->inreplyto);
2962 if (msginfo->msgid && *msginfo->msgid)
2963 compose->inreplyto = g_strdup(msginfo->msgid);
2965 if (!compose->references) {
2966 if (msginfo->msgid && *msginfo->msgid) {
2967 if (msginfo->inreplyto && *msginfo->inreplyto)
2968 compose->references =
2969 g_strdup_printf("<%s>\n\t<%s>",
2973 compose->references =
2974 g_strconcat("<", msginfo->msgid, ">",
2976 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2977 compose->references =
2978 g_strconcat("<", msginfo->inreplyto, ">",
2986 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2991 cm_return_val_if_fail(msginfo != NULL, -1);
2993 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2994 procheader_get_header_fields(fp, entries);
2998 while (he != NULL && he->name != NULL) {
3000 GtkListStore *model = NULL;
3002 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3003 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3004 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3005 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3006 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3013 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3015 GSList *ref_id_list, *cur;
3019 ref_id_list = references_list_append(NULL, ref);
3020 if (!ref_id_list) return NULL;
3021 if (msgid && *msgid)
3022 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3027 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3028 /* "<" + Message-ID + ">" + CR+LF+TAB */
3029 len += strlen((gchar *)cur->data) + 5;
3031 if (len > MAX_REFERENCES_LEN) {
3032 /* remove second message-ID */
3033 if (ref_id_list && ref_id_list->next &&
3034 ref_id_list->next->next) {
3035 g_free(ref_id_list->next->data);
3036 ref_id_list = g_slist_remove
3037 (ref_id_list, ref_id_list->next->data);
3039 slist_free_strings_full(ref_id_list);
3046 new_ref = g_string_new("");
3047 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3048 if (new_ref->len > 0)
3049 g_string_append(new_ref, "\n\t");
3050 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3053 slist_free_strings_full(ref_id_list);
3055 new_ref_str = new_ref->str;
3056 g_string_free(new_ref, FALSE);
3061 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3062 const gchar *fmt, const gchar *qmark,
3063 const gchar *body, gboolean rewrap,
3064 gboolean need_unescape,
3065 const gchar *err_msg)
3067 MsgInfo* dummyinfo = NULL;
3068 gchar *quote_str = NULL;
3070 gboolean prev_autowrap;
3071 const gchar *trimmed_body = body;
3072 gint cursor_pos = -1;
3073 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3074 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3079 SIGNAL_BLOCK(buffer);
3082 dummyinfo = compose_msginfo_new_from_compose(compose);
3083 msginfo = dummyinfo;
3086 if (qmark != NULL) {
3088 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3089 compose->gtkaspell);
3091 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3093 quote_fmt_scan_string(qmark);
3096 buf = quote_fmt_get_buffer();
3098 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3100 Xstrdup_a(quote_str, buf, goto error)
3103 if (fmt && *fmt != '\0') {
3106 while (*trimmed_body == '\n')
3110 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3111 compose->gtkaspell);
3113 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3115 if (need_unescape) {
3118 /* decode \-escape sequences in the internal representation of the quote format */
3119 tmp = g_malloc(strlen(fmt)+1);
3120 pref_get_unescaped_pref(tmp, fmt);
3121 quote_fmt_scan_string(tmp);
3125 quote_fmt_scan_string(fmt);
3129 buf = quote_fmt_get_buffer();
3131 gint line = quote_fmt_get_line();
3132 alertpanel_error(err_msg, line);
3138 prev_autowrap = compose->autowrap;
3139 compose->autowrap = FALSE;
3141 mark = gtk_text_buffer_get_insert(buffer);
3142 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3143 if (g_utf8_validate(buf, -1, NULL)) {
3144 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3146 gchar *tmpout = NULL;
3147 tmpout = conv_codeset_strdup
3148 (buf, conv_get_locale_charset_str_no_utf8(),
3150 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3152 tmpout = g_malloc(strlen(buf)*2+1);
3153 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3155 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3159 cursor_pos = quote_fmt_get_cursor_pos();
3160 if (cursor_pos == -1)
3161 cursor_pos = gtk_text_iter_get_offset(&iter);
3162 compose->set_cursor_pos = cursor_pos;
3164 gtk_text_buffer_get_start_iter(buffer, &iter);
3165 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3166 gtk_text_buffer_place_cursor(buffer, &iter);
3168 compose->autowrap = prev_autowrap;
3169 if (compose->autowrap && rewrap)
3170 compose_wrap_all(compose);
3177 SIGNAL_UNBLOCK(buffer);
3179 procmsg_msginfo_free( dummyinfo );
3184 /* if ml_post is of type addr@host and from is of type
3185 * addr-anything@host, return TRUE
3187 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3189 gchar *left_ml = NULL;
3190 gchar *right_ml = NULL;
3191 gchar *left_from = NULL;
3192 gchar *right_from = NULL;
3193 gboolean result = FALSE;
3195 if (!ml_post || !from)
3198 left_ml = g_strdup(ml_post);
3199 if (strstr(left_ml, "@")) {
3200 right_ml = strstr(left_ml, "@")+1;
3201 *(strstr(left_ml, "@")) = '\0';
3204 left_from = g_strdup(from);
3205 if (strstr(left_from, "@")) {
3206 right_from = strstr(left_from, "@")+1;
3207 *(strstr(left_from, "@")) = '\0';
3210 if (right_ml && right_from
3211 && !strncmp(left_from, left_ml, strlen(left_ml))
3212 && !strcmp(right_from, right_ml)) {
3221 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3222 gboolean respect_default_to)
3226 if (!folder || !folder->prefs)
3229 if (respect_default_to && folder->prefs->enable_default_to) {
3230 compose_entry_append(compose, folder->prefs->default_to,
3231 COMPOSE_TO, PREF_FOLDER);
3232 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3234 if (folder->prefs->enable_default_cc)
3235 compose_entry_append(compose, folder->prefs->default_cc,
3236 COMPOSE_CC, PREF_FOLDER);
3237 if (folder->prefs->enable_default_bcc)
3238 compose_entry_append(compose, folder->prefs->default_bcc,
3239 COMPOSE_BCC, PREF_FOLDER);
3240 if (folder->prefs->enable_default_replyto)
3241 compose_entry_append(compose, folder->prefs->default_replyto,
3242 COMPOSE_REPLYTO, PREF_FOLDER);
3245 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3250 if (!compose || !msginfo)
3253 if (msginfo->subject && *msginfo->subject) {
3254 buf = p = g_strdup(msginfo->subject);
3255 p += subject_get_prefix_length(p);
3256 memmove(buf, p, strlen(p) + 1);
3258 buf2 = g_strdup_printf("Re: %s", buf);
3259 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3264 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3267 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3268 gboolean to_all, gboolean to_ml,
3270 gboolean followup_and_reply_to)
3272 GSList *cc_list = NULL;
3275 gchar *replyto = NULL;
3276 gchar *ac_email = NULL;
3278 gboolean reply_to_ml = FALSE;
3279 gboolean default_reply_to = FALSE;
3281 cm_return_if_fail(compose->account != NULL);
3282 cm_return_if_fail(msginfo != NULL);
3284 reply_to_ml = to_ml && compose->ml_post;
3286 default_reply_to = msginfo->folder &&
3287 msginfo->folder->prefs->enable_default_reply_to;
3289 if (compose->account->protocol != A_NNTP) {
3290 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3292 if (reply_to_ml && !default_reply_to) {
3294 gboolean is_subscr = is_subscription(compose->ml_post,
3297 /* normal answer to ml post with a reply-to */
3298 compose_entry_append(compose,
3300 COMPOSE_TO, PREF_ML);
3301 if (compose->replyto)
3302 compose_entry_append(compose,
3304 COMPOSE_CC, PREF_ML);
3306 /* answer to subscription confirmation */
3307 if (compose->replyto)
3308 compose_entry_append(compose,
3310 COMPOSE_TO, PREF_ML);
3311 else if (msginfo->from)
3312 compose_entry_append(compose,
3314 COMPOSE_TO, PREF_ML);
3317 else if (!(to_all || to_sender) && default_reply_to) {
3318 compose_entry_append(compose,
3319 msginfo->folder->prefs->default_reply_to,
3320 COMPOSE_TO, PREF_FOLDER);
3321 compose_entry_mark_default_to(compose,
3322 msginfo->folder->prefs->default_reply_to);
3328 compose_entry_append(compose, msginfo->from,
3329 COMPOSE_TO, PREF_NONE);
3331 Xstrdup_a(tmp1, msginfo->from, return);
3332 extract_address(tmp1);
3333 compose_entry_append(compose,
3334 (!account_find_from_address(tmp1, FALSE))
3337 COMPOSE_TO, PREF_NONE);
3339 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3340 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3341 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3342 if (compose->replyto) {
3343 compose_entry_append(compose,
3345 COMPOSE_TO, PREF_NONE);
3347 compose_entry_append(compose,
3348 msginfo->from ? msginfo->from : "",
3349 COMPOSE_TO, PREF_NONE);
3352 /* replying to own mail, use original recp */
3353 compose_entry_append(compose,
3354 msginfo->to ? msginfo->to : "",
3355 COMPOSE_TO, PREF_NONE);
3356 compose_entry_append(compose,
3357 msginfo->cc ? msginfo->cc : "",
3358 COMPOSE_CC, PREF_NONE);
3363 if (to_sender || (compose->followup_to &&
3364 !strncmp(compose->followup_to, "poster", 6)))
3365 compose_entry_append
3367 (compose->replyto ? compose->replyto :
3368 msginfo->from ? msginfo->from : ""),
3369 COMPOSE_TO, PREF_NONE);
3371 else if (followup_and_reply_to || to_all) {
3372 compose_entry_append
3374 (compose->replyto ? compose->replyto :
3375 msginfo->from ? msginfo->from : ""),
3376 COMPOSE_TO, PREF_NONE);
3378 compose_entry_append
3380 compose->followup_to ? compose->followup_to :
3381 compose->newsgroups ? compose->newsgroups : "",
3382 COMPOSE_NEWSGROUPS, PREF_NONE);
3385 compose_entry_append
3387 compose->followup_to ? compose->followup_to :
3388 compose->newsgroups ? compose->newsgroups : "",
3389 COMPOSE_NEWSGROUPS, PREF_NONE);
3391 compose_reply_set_subject(compose, msginfo);
3393 if (to_ml && compose->ml_post) return;
3394 if (!to_all || compose->account->protocol == A_NNTP) return;
3396 if (compose->replyto) {
3397 Xstrdup_a(replyto, compose->replyto, return);
3398 extract_address(replyto);
3400 if (msginfo->from) {
3401 Xstrdup_a(from, msginfo->from, return);
3402 extract_address(from);
3405 if (replyto && from)
3406 cc_list = address_list_append_with_comments(cc_list, from);
3407 if (to_all && msginfo->folder &&
3408 msginfo->folder->prefs->enable_default_reply_to)
3409 cc_list = address_list_append_with_comments(cc_list,
3410 msginfo->folder->prefs->default_reply_to);
3411 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3412 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3414 ac_email = g_utf8_strdown(compose->account->address, -1);
3417 for (cur = cc_list; cur != NULL; cur = cur->next) {
3418 gchar *addr = g_utf8_strdown(cur->data, -1);
3419 extract_address(addr);
3421 if (strcmp(ac_email, addr))
3422 compose_entry_append(compose, (gchar *)cur->data,
3423 COMPOSE_CC, PREF_NONE);
3425 debug_print("Cc address same as compose account's, ignoring\n");
3430 slist_free_strings_full(cc_list);
3436 #define SET_ENTRY(entry, str) \
3439 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3442 #define SET_ADDRESS(type, str) \
3445 compose_entry_append(compose, str, type, PREF_NONE); \
3448 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3450 cm_return_if_fail(msginfo != NULL);
3452 SET_ENTRY(subject_entry, msginfo->subject);
3453 SET_ENTRY(from_name, msginfo->from);
3454 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3455 SET_ADDRESS(COMPOSE_CC, compose->cc);
3456 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3457 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3458 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3459 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3461 compose_update_priority_menu_item(compose);
3462 compose_update_privacy_system_menu_item(compose, FALSE);
3463 compose_show_first_last_header(compose, TRUE);
3469 static void compose_insert_sig(Compose *compose, gboolean replace)
3471 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3472 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3474 GtkTextIter iter, iter_end;
3475 gint cur_pos, ins_pos;
3476 gboolean prev_autowrap;
3477 gboolean found = FALSE;
3478 gboolean exists = FALSE;
3480 cm_return_if_fail(compose->account != NULL);
3484 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3485 G_CALLBACK(compose_changed_cb),
3488 mark = gtk_text_buffer_get_insert(buffer);
3489 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3490 cur_pos = gtk_text_iter_get_offset (&iter);
3493 gtk_text_buffer_get_end_iter(buffer, &iter);
3495 exists = (compose->sig_str != NULL);
3498 GtkTextIter first_iter, start_iter, end_iter;
3500 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3502 if (!exists || compose->sig_str[0] == '\0')
3505 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3506 compose->signature_tag);
3509 /* include previous \n\n */
3510 gtk_text_iter_backward_chars(&first_iter, 1);
3511 start_iter = first_iter;
3512 end_iter = first_iter;
3514 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3515 compose->signature_tag);
3516 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3517 compose->signature_tag);
3519 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3525 g_free(compose->sig_str);
3526 compose->sig_str = account_get_signature_str(compose->account);
3528 cur_pos = gtk_text_iter_get_offset(&iter);
3530 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3531 g_free(compose->sig_str);
3532 compose->sig_str = NULL;
3534 if (compose->sig_inserted == FALSE)
3535 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3536 compose->sig_inserted = TRUE;
3538 cur_pos = gtk_text_iter_get_offset(&iter);
3539 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3541 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3542 gtk_text_iter_forward_chars(&iter, 1);
3543 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3544 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3546 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3547 cur_pos = gtk_text_buffer_get_char_count (buffer);
3550 /* put the cursor where it should be
3551 * either where the quote_fmt says, either where it was */
3552 if (compose->set_cursor_pos < 0)
3553 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3555 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3556 compose->set_cursor_pos);
3558 compose->set_cursor_pos = -1;
3559 gtk_text_buffer_place_cursor(buffer, &iter);
3560 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3561 G_CALLBACK(compose_changed_cb),
3567 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3570 GtkTextBuffer *buffer;
3573 const gchar *cur_encoding;
3574 gchar buf[BUFFSIZE];
3577 gboolean prev_autowrap;
3578 struct stat file_stat;
3580 GString *file_contents = NULL;
3582 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3584 /* get the size of the file we are about to insert */
3585 ret = g_stat(file, &file_stat);
3587 gchar *shortfile = g_path_get_basename(file);
3588 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3590 return COMPOSE_INSERT_NO_FILE;
3591 } else if (prefs_common.warn_large_insert == TRUE) {
3593 /* ask user for confirmation if the file is large */
3594 if (prefs_common.warn_large_insert_size < 0 ||
3595 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3599 msg = g_strdup_printf(_("You are about to insert a file of %s "
3600 "in the message body. Are you sure you want to do that?"),
3601 to_human_readable(file_stat.st_size));
3602 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3603 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3606 /* do we ask for confirmation next time? */
3607 if (aval & G_ALERTDISABLE) {
3608 /* no confirmation next time, disable feature in preferences */
3609 aval &= ~G_ALERTDISABLE;
3610 prefs_common.warn_large_insert = FALSE;
3613 /* abort file insertion if user canceled action */
3614 if (aval != G_ALERTALTERNATE) {
3615 return COMPOSE_INSERT_NO_FILE;
3621 if ((fp = g_fopen(file, "rb")) == NULL) {
3622 FILE_OP_ERROR(file, "fopen");
3623 return COMPOSE_INSERT_READ_ERROR;
3626 prev_autowrap = compose->autowrap;
3627 compose->autowrap = FALSE;
3629 text = GTK_TEXT_VIEW(compose->text);
3630 buffer = gtk_text_view_get_buffer(text);
3631 mark = gtk_text_buffer_get_insert(buffer);
3632 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3634 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3635 G_CALLBACK(text_inserted),
3638 cur_encoding = conv_get_locale_charset_str_no_utf8();
3640 file_contents = g_string_new("");
3641 while (fgets(buf, sizeof(buf), fp) != NULL) {
3644 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3645 str = g_strdup(buf);
3647 str = conv_codeset_strdup
3648 (buf, cur_encoding, CS_INTERNAL);
3651 /* strip <CR> if DOS/Windows file,
3652 replace <CR> with <LF> if Macintosh file. */
3655 if (len > 0 && str[len - 1] != '\n') {
3657 if (str[len] == '\r') str[len] = '\n';
3660 file_contents = g_string_append(file_contents, str);
3664 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3665 g_string_free(file_contents, TRUE);
3667 compose_changed_cb(NULL, compose);
3668 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3669 G_CALLBACK(text_inserted),
3671 compose->autowrap = prev_autowrap;
3672 if (compose->autowrap)
3673 compose_wrap_all(compose);
3677 return COMPOSE_INSERT_SUCCESS;
3680 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3681 const gchar *filename,
3682 const gchar *content_type,
3683 const gchar *charset)
3691 GtkListStore *store;
3693 gboolean has_binary = FALSE;
3695 if (!is_file_exist(file)) {
3696 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3697 gboolean result = FALSE;
3698 if (file_from_uri && is_file_exist(file_from_uri)) {
3699 result = compose_attach_append(
3700 compose, file_from_uri,
3701 filename, content_type,
3704 g_free(file_from_uri);
3707 alertpanel_error("File %s doesn't exist\n", filename);
3710 if ((size = get_file_size(file)) < 0) {
3711 alertpanel_error("Can't get file size of %s\n", filename);
3715 /* In batch mode, we allow 0-length files to be attached no questions asked */
3716 if (size == 0 && !compose->batch) {
3717 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3718 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3719 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3720 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3723 if (aval != G_ALERTALTERNATE) {
3727 if ((fp = g_fopen(file, "rb")) == NULL) {
3728 alertpanel_error(_("Can't read %s."), filename);
3733 ainfo = g_new0(AttachInfo, 1);
3734 auto_ainfo = g_auto_pointer_new_with_free
3735 (ainfo, (GFreeFunc) compose_attach_info_free);
3736 ainfo->file = g_strdup(file);
3739 ainfo->content_type = g_strdup(content_type);
3740 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3742 MsgFlags flags = {0, 0};
3744 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3745 ainfo->encoding = ENC_7BIT;
3747 ainfo->encoding = ENC_8BIT;
3749 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3750 if (msginfo && msginfo->subject)
3751 name = g_strdup(msginfo->subject);
3753 name = g_path_get_basename(filename ? filename : file);
3755 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3757 procmsg_msginfo_free(msginfo);
3759 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3760 ainfo->charset = g_strdup(charset);
3761 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3763 ainfo->encoding = ENC_BASE64;
3765 name = g_path_get_basename(filename ? filename : file);
3766 ainfo->name = g_strdup(name);
3770 ainfo->content_type = procmime_get_mime_type(file);
3771 if (!ainfo->content_type) {
3772 ainfo->content_type =
3773 g_strdup("application/octet-stream");
3774 ainfo->encoding = ENC_BASE64;
3775 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3777 procmime_get_encoding_for_text_file(file, &has_binary);
3779 ainfo->encoding = ENC_BASE64;
3780 name = g_path_get_basename(filename ? filename : file);
3781 ainfo->name = g_strdup(name);
3785 if (ainfo->name != NULL
3786 && !strcmp(ainfo->name, ".")) {
3787 g_free(ainfo->name);
3791 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3792 g_free(ainfo->content_type);
3793 ainfo->content_type = g_strdup("application/octet-stream");
3794 g_free(ainfo->charset);
3795 ainfo->charset = NULL;
3798 ainfo->size = (goffset)size;
3799 size_text = to_human_readable((goffset)size);
3801 store = GTK_LIST_STORE(gtk_tree_view_get_model
3802 (GTK_TREE_VIEW(compose->attach_clist)));
3804 gtk_list_store_append(store, &iter);
3805 gtk_list_store_set(store, &iter,
3806 COL_MIMETYPE, ainfo->content_type,
3807 COL_SIZE, size_text,
3808 COL_NAME, ainfo->name,
3809 COL_CHARSET, ainfo->charset,
3811 COL_AUTODATA, auto_ainfo,
3814 g_auto_pointer_free(auto_ainfo);
3815 compose_attach_update_label(compose);
3819 static void compose_use_signing(Compose *compose, gboolean use_signing)
3821 compose->use_signing = use_signing;
3822 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3825 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3827 compose->use_encryption = use_encryption;
3828 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3831 #define NEXT_PART_NOT_CHILD(info) \
3833 node = info->node; \
3834 while (node->children) \
3835 node = g_node_last_child(node); \
3836 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3839 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3843 MimeInfo *firsttext = NULL;
3844 MimeInfo *encrypted = NULL;
3847 const gchar *partname = NULL;
3849 mimeinfo = procmime_scan_message(msginfo);
3850 if (!mimeinfo) return;
3852 if (mimeinfo->node->children == NULL) {
3853 procmime_mimeinfo_free_all(mimeinfo);
3857 /* find first content part */
3858 child = (MimeInfo *) mimeinfo->node->children->data;
3859 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3860 child = (MimeInfo *)child->node->children->data;
3863 if (child->type == MIMETYPE_TEXT) {
3865 debug_print("First text part found\n");
3866 } else if (compose->mode == COMPOSE_REEDIT &&
3867 child->type == MIMETYPE_APPLICATION &&
3868 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3869 encrypted = (MimeInfo *)child->node->parent->data;
3872 child = (MimeInfo *) mimeinfo->node->children->data;
3873 while (child != NULL) {
3876 if (child == encrypted) {
3877 /* skip this part of tree */
3878 NEXT_PART_NOT_CHILD(child);
3882 if (child->type == MIMETYPE_MULTIPART) {
3883 /* get the actual content */
3884 child = procmime_mimeinfo_next(child);
3888 if (child == firsttext) {
3889 child = procmime_mimeinfo_next(child);
3893 outfile = procmime_get_tmp_file_name(child);
3894 if ((err = procmime_get_part(outfile, child)) < 0)
3895 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3897 gchar *content_type;
3899 content_type = procmime_get_content_type_str(child->type, child->subtype);
3901 /* if we meet a pgp signature, we don't attach it, but
3902 * we force signing. */
3903 if ((strcmp(content_type, "application/pgp-signature") &&
3904 strcmp(content_type, "application/pkcs7-signature") &&
3905 strcmp(content_type, "application/x-pkcs7-signature"))
3906 || compose->mode == COMPOSE_REDIRECT) {
3907 partname = procmime_mimeinfo_get_parameter(child, "filename");
3908 if (partname == NULL)
3909 partname = procmime_mimeinfo_get_parameter(child, "name");
3910 if (partname == NULL)
3912 compose_attach_append(compose, outfile,
3913 partname, content_type,
3914 procmime_mimeinfo_get_parameter(child, "charset"));
3916 compose_force_signing(compose, compose->account, NULL);
3918 g_free(content_type);
3921 NEXT_PART_NOT_CHILD(child);
3923 procmime_mimeinfo_free_all(mimeinfo);
3926 #undef NEXT_PART_NOT_CHILD
3931 WAIT_FOR_INDENT_CHAR,
3932 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3935 /* return indent length, we allow:
3936 indent characters followed by indent characters or spaces/tabs,
3937 alphabets and numbers immediately followed by indent characters,
3938 and the repeating sequences of the above
3939 If quote ends with multiple spaces, only the first one is included. */
3940 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3941 const GtkTextIter *start, gint *len)
3943 GtkTextIter iter = *start;
3947 IndentState state = WAIT_FOR_INDENT_CHAR;
3950 gint alnum_count = 0;
3951 gint space_count = 0;
3954 if (prefs_common.quote_chars == NULL) {
3958 while (!gtk_text_iter_ends_line(&iter)) {
3959 wc = gtk_text_iter_get_char(&iter);
3960 if (g_unichar_iswide(wc))
3962 clen = g_unichar_to_utf8(wc, ch);
3966 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3967 is_space = g_unichar_isspace(wc);
3969 if (state == WAIT_FOR_INDENT_CHAR) {
3970 if (!is_indent && !g_unichar_isalnum(wc))
3973 quote_len += alnum_count + space_count + 1;
3974 alnum_count = space_count = 0;
3975 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3978 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3979 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3983 else if (is_indent) {
3984 quote_len += alnum_count + space_count + 1;
3985 alnum_count = space_count = 0;
3988 state = WAIT_FOR_INDENT_CHAR;
3992 gtk_text_iter_forward_char(&iter);
3995 if (quote_len > 0 && space_count > 0)
4001 if (quote_len > 0) {
4003 gtk_text_iter_forward_chars(&iter, quote_len);
4004 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4010 /* return >0 if the line is itemized */
4011 static int compose_itemized_length(GtkTextBuffer *buffer,
4012 const GtkTextIter *start)
4014 GtkTextIter iter = *start;
4019 if (gtk_text_iter_ends_line(&iter))
4024 wc = gtk_text_iter_get_char(&iter);
4025 if (!g_unichar_isspace(wc))
4027 gtk_text_iter_forward_char(&iter);
4028 if (gtk_text_iter_ends_line(&iter))
4032 clen = g_unichar_to_utf8(wc, ch);
4036 if (!strchr("*-+", ch[0]))
4039 gtk_text_iter_forward_char(&iter);
4040 if (gtk_text_iter_ends_line(&iter))
4042 wc = gtk_text_iter_get_char(&iter);
4043 if (g_unichar_isspace(wc)) {
4049 /* return the string at the start of the itemization */
4050 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4051 const GtkTextIter *start)
4053 GtkTextIter iter = *start;
4056 GString *item_chars = g_string_new("");
4059 if (gtk_text_iter_ends_line(&iter))
4064 wc = gtk_text_iter_get_char(&iter);
4065 if (!g_unichar_isspace(wc))
4067 gtk_text_iter_forward_char(&iter);
4068 if (gtk_text_iter_ends_line(&iter))
4070 g_string_append_unichar(item_chars, wc);
4073 str = item_chars->str;
4074 g_string_free(item_chars, FALSE);
4078 /* return the number of spaces at a line's start */
4079 static int compose_left_offset_length(GtkTextBuffer *buffer,
4080 const GtkTextIter *start)
4082 GtkTextIter iter = *start;
4085 if (gtk_text_iter_ends_line(&iter))
4089 wc = gtk_text_iter_get_char(&iter);
4090 if (!g_unichar_isspace(wc))
4093 gtk_text_iter_forward_char(&iter);
4094 if (gtk_text_iter_ends_line(&iter))
4098 gtk_text_iter_forward_char(&iter);
4099 if (gtk_text_iter_ends_line(&iter))
4104 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4105 const GtkTextIter *start,
4106 GtkTextIter *break_pos,
4110 GtkTextIter iter = *start, line_end = *start;
4111 PangoLogAttr *attrs;
4118 gboolean can_break = FALSE;
4119 gboolean do_break = FALSE;
4120 gboolean was_white = FALSE;
4121 gboolean prev_dont_break = FALSE;
4123 gtk_text_iter_forward_to_line_end(&line_end);
4124 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4125 len = g_utf8_strlen(str, -1);
4129 g_warning("compose_get_line_break_pos: len = 0!\n");
4133 /* g_print("breaking line: %d: %s (len = %d)\n",
4134 gtk_text_iter_get_line(&iter), str, len); */
4136 attrs = g_new(PangoLogAttr, len + 1);
4138 pango_default_break(str, -1, NULL, attrs, len + 1);
4142 /* skip quote and leading spaces */
4143 for (i = 0; *p != '\0' && i < len; i++) {
4146 wc = g_utf8_get_char(p);
4147 if (i >= quote_len && !g_unichar_isspace(wc))
4149 if (g_unichar_iswide(wc))
4151 else if (*p == '\t')
4155 p = g_utf8_next_char(p);
4158 for (; *p != '\0' && i < len; i++) {
4159 PangoLogAttr *attr = attrs + i;
4163 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4166 was_white = attr->is_white;
4168 /* don't wrap URI */
4169 if ((uri_len = get_uri_len(p)) > 0) {
4171 if (pos > 0 && col > max_col) {
4181 wc = g_utf8_get_char(p);
4182 if (g_unichar_iswide(wc)) {
4184 if (prev_dont_break && can_break && attr->is_line_break)
4186 } else if (*p == '\t')
4190 if (pos > 0 && col > max_col) {
4195 if (*p == '-' || *p == '/')
4196 prev_dont_break = TRUE;
4198 prev_dont_break = FALSE;
4200 p = g_utf8_next_char(p);
4204 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4209 *break_pos = *start;
4210 gtk_text_iter_set_line_offset(break_pos, pos);
4215 static gboolean compose_join_next_line(Compose *compose,
4216 GtkTextBuffer *buffer,
4218 const gchar *quote_str)
4220 GtkTextIter iter_ = *iter, cur, prev, next, end;
4221 PangoLogAttr attrs[3];
4223 gchar *next_quote_str;
4226 gboolean keep_cursor = FALSE;
4228 if (!gtk_text_iter_forward_line(&iter_) ||
4229 gtk_text_iter_ends_line(&iter_)) {
4232 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4234 if ((quote_str || next_quote_str) &&
4235 strcmp2(quote_str, next_quote_str) != 0) {
4236 g_free(next_quote_str);
4239 g_free(next_quote_str);
4242 if (quote_len > 0) {
4243 gtk_text_iter_forward_chars(&end, quote_len);
4244 if (gtk_text_iter_ends_line(&end)) {
4249 /* don't join itemized lines */
4250 if (compose_itemized_length(buffer, &end) > 0) {
4254 /* don't join signature separator */
4255 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4258 /* delete quote str */
4260 gtk_text_buffer_delete(buffer, &iter_, &end);
4262 /* don't join line breaks put by the user */
4264 gtk_text_iter_backward_char(&cur);
4265 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4266 gtk_text_iter_forward_char(&cur);
4270 gtk_text_iter_forward_char(&cur);
4271 /* delete linebreak and extra spaces */
4272 while (gtk_text_iter_backward_char(&cur)) {
4273 wc1 = gtk_text_iter_get_char(&cur);
4274 if (!g_unichar_isspace(wc1))
4279 while (!gtk_text_iter_ends_line(&cur)) {
4280 wc1 = gtk_text_iter_get_char(&cur);
4281 if (!g_unichar_isspace(wc1))
4283 gtk_text_iter_forward_char(&cur);
4286 if (!gtk_text_iter_equal(&prev, &next)) {
4289 mark = gtk_text_buffer_get_insert(buffer);
4290 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4291 if (gtk_text_iter_equal(&prev, &cur))
4293 gtk_text_buffer_delete(buffer, &prev, &next);
4297 /* insert space if required */
4298 gtk_text_iter_backward_char(&prev);
4299 wc1 = gtk_text_iter_get_char(&prev);
4300 wc2 = gtk_text_iter_get_char(&next);
4301 gtk_text_iter_forward_char(&next);
4302 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4303 pango_default_break(str, -1, NULL, attrs, 3);
4304 if (!attrs[1].is_line_break ||
4305 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4306 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4308 gtk_text_iter_backward_char(&iter_);
4309 gtk_text_buffer_place_cursor(buffer, &iter_);
4318 #define ADD_TXT_POS(bp_, ep_, pti_) \
4319 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4320 last = last->next; \
4321 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4322 last->next = NULL; \
4324 g_warning("alloc error scanning URIs\n"); \
4327 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4329 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4330 GtkTextBuffer *buffer;
4331 GtkTextIter iter, break_pos, end_of_line;
4332 gchar *quote_str = NULL;
4334 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4335 gboolean prev_autowrap = compose->autowrap;
4336 gint startq_offset = -1, noq_offset = -1;
4337 gint uri_start = -1, uri_stop = -1;
4338 gint nouri_start = -1, nouri_stop = -1;
4339 gint num_blocks = 0;
4340 gint quotelevel = -1;
4341 gboolean modified = force;
4342 gboolean removed = FALSE;
4343 gboolean modified_before_remove = FALSE;
4345 gboolean start = TRUE;
4346 gint itemized_len = 0, rem_item_len = 0;
4347 gchar *itemized_chars = NULL;
4348 gboolean item_continuation = FALSE;
4353 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4357 compose->autowrap = FALSE;
4359 buffer = gtk_text_view_get_buffer(text);
4360 undo_wrapping(compose->undostruct, TRUE);
4365 mark = gtk_text_buffer_get_insert(buffer);
4366 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4370 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4371 if (gtk_text_iter_ends_line(&iter)) {
4372 while (gtk_text_iter_ends_line(&iter) &&
4373 gtk_text_iter_forward_line(&iter))
4376 while (gtk_text_iter_backward_line(&iter)) {
4377 if (gtk_text_iter_ends_line(&iter)) {
4378 gtk_text_iter_forward_line(&iter);
4384 /* move to line start */
4385 gtk_text_iter_set_line_offset(&iter, 0);
4388 itemized_len = compose_itemized_length(buffer, &iter);
4390 if (!itemized_len) {
4391 itemized_len = compose_left_offset_length(buffer, &iter);
4392 item_continuation = TRUE;
4396 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4398 /* go until paragraph end (empty line) */
4399 while (start || !gtk_text_iter_ends_line(&iter)) {
4400 gchar *scanpos = NULL;
4401 /* parse table - in order of priority */
4403 const gchar *needle; /* token */
4405 /* token search function */
4406 gchar *(*search) (const gchar *haystack,
4407 const gchar *needle);
4408 /* part parsing function */
4409 gboolean (*parse) (const gchar *start,
4410 const gchar *scanpos,
4414 /* part to URI function */
4415 gchar *(*build_uri) (const gchar *bp,
4419 static struct table parser[] = {
4420 {"http://", strcasestr, get_uri_part, make_uri_string},
4421 {"https://", strcasestr, get_uri_part, make_uri_string},
4422 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4423 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4424 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4425 {"www.", strcasestr, get_uri_part, make_http_string},
4426 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4427 {"@", strcasestr, get_email_part, make_email_string}
4429 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4430 gint last_index = PARSE_ELEMS;
4432 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4436 if (!prev_autowrap && num_blocks == 0) {
4438 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4439 G_CALLBACK(text_inserted),
4442 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4445 uri_start = uri_stop = -1;
4447 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4450 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4451 if (startq_offset == -1)
4452 startq_offset = gtk_text_iter_get_offset(&iter);
4453 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4454 if (quotelevel > 2) {
4455 /* recycle colors */
4456 if (prefs_common.recycle_quote_colors)
4465 if (startq_offset == -1)
4466 noq_offset = gtk_text_iter_get_offset(&iter);
4470 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4473 if (gtk_text_iter_ends_line(&iter)) {
4475 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4476 prefs_common.linewrap_len,
4478 GtkTextIter prev, next, cur;
4479 if (prev_autowrap != FALSE || force) {
4480 compose->automatic_break = TRUE;
4482 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4483 compose->automatic_break = FALSE;
4484 if (itemized_len && compose->autoindent) {
4485 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4486 if (!item_continuation)
4487 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4489 } else if (quote_str && wrap_quote) {
4490 compose->automatic_break = TRUE;
4492 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4493 compose->automatic_break = FALSE;
4494 if (itemized_len && compose->autoindent) {
4495 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4496 if (!item_continuation)
4497 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4501 /* remove trailing spaces */
4503 rem_item_len = itemized_len;
4504 while (compose->autoindent && rem_item_len-- > 0)
4505 gtk_text_iter_backward_char(&cur);
4506 gtk_text_iter_backward_char(&cur);
4509 while (!gtk_text_iter_starts_line(&cur)) {
4512 gtk_text_iter_backward_char(&cur);
4513 wc = gtk_text_iter_get_char(&cur);
4514 if (!g_unichar_isspace(wc))
4518 if (!gtk_text_iter_equal(&prev, &next)) {
4519 gtk_text_buffer_delete(buffer, &prev, &next);
4521 gtk_text_iter_forward_char(&break_pos);
4525 gtk_text_buffer_insert(buffer, &break_pos,
4529 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4531 /* move iter to current line start */
4532 gtk_text_iter_set_line_offset(&iter, 0);
4539 /* move iter to next line start */
4545 if (!prev_autowrap && num_blocks > 0) {
4547 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4548 G_CALLBACK(text_inserted),
4552 while (!gtk_text_iter_ends_line(&end_of_line)) {
4553 gtk_text_iter_forward_char(&end_of_line);
4555 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4557 nouri_start = gtk_text_iter_get_offset(&iter);
4558 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4560 walk_pos = gtk_text_iter_get_offset(&iter);
4561 /* FIXME: this looks phony. scanning for anything in the parse table */
4562 for (n = 0; n < PARSE_ELEMS; n++) {
4565 tmp = parser[n].search(walk, parser[n].needle);
4567 if (scanpos == NULL || tmp < scanpos) {
4576 /* check if URI can be parsed */
4577 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4578 (const gchar **)&ep, FALSE)
4579 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4583 strlen(parser[last_index].needle);
4586 uri_start = walk_pos + (bp - o_walk);
4587 uri_stop = walk_pos + (ep - o_walk);
4591 gtk_text_iter_forward_line(&iter);
4594 if (startq_offset != -1) {
4595 GtkTextIter startquote, endquote;
4596 gtk_text_buffer_get_iter_at_offset(
4597 buffer, &startquote, startq_offset);
4600 switch (quotelevel) {
4602 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4603 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4604 gtk_text_buffer_apply_tag_by_name(
4605 buffer, "quote0", &startquote, &endquote);
4606 gtk_text_buffer_remove_tag_by_name(
4607 buffer, "quote1", &startquote, &endquote);
4608 gtk_text_buffer_remove_tag_by_name(
4609 buffer, "quote2", &startquote, &endquote);
4614 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4615 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4616 gtk_text_buffer_apply_tag_by_name(
4617 buffer, "quote1", &startquote, &endquote);
4618 gtk_text_buffer_remove_tag_by_name(
4619 buffer, "quote0", &startquote, &endquote);
4620 gtk_text_buffer_remove_tag_by_name(
4621 buffer, "quote2", &startquote, &endquote);
4626 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4627 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4628 gtk_text_buffer_apply_tag_by_name(
4629 buffer, "quote2", &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, "quote1", &startquote, &endquote);
4639 } else if (noq_offset != -1) {
4640 GtkTextIter startnoquote, endnoquote;
4641 gtk_text_buffer_get_iter_at_offset(
4642 buffer, &startnoquote, noq_offset);
4645 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4646 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4647 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4648 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4649 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4650 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4651 gtk_text_buffer_remove_tag_by_name(
4652 buffer, "quote0", &startnoquote, &endnoquote);
4653 gtk_text_buffer_remove_tag_by_name(
4654 buffer, "quote1", &startnoquote, &endnoquote);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote2", &startnoquote, &endnoquote);
4662 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4663 GtkTextIter nouri_start_iter, nouri_end_iter;
4664 gtk_text_buffer_get_iter_at_offset(
4665 buffer, &nouri_start_iter, nouri_start);
4666 gtk_text_buffer_get_iter_at_offset(
4667 buffer, &nouri_end_iter, nouri_stop);
4668 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4669 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4670 gtk_text_buffer_remove_tag_by_name(
4671 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4672 modified_before_remove = modified;
4677 if (uri_start >= 0 && uri_stop > 0) {
4678 GtkTextIter uri_start_iter, uri_end_iter, back;
4679 gtk_text_buffer_get_iter_at_offset(
4680 buffer, &uri_start_iter, uri_start);
4681 gtk_text_buffer_get_iter_at_offset(
4682 buffer, &uri_end_iter, uri_stop);
4683 back = uri_end_iter;
4684 gtk_text_iter_backward_char(&back);
4685 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4686 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4687 gtk_text_buffer_apply_tag_by_name(
4688 buffer, "link", &uri_start_iter, &uri_end_iter);
4690 if (removed && !modified_before_remove) {
4696 // debug_print("not modified, out after %d lines\n", lines);
4700 // debug_print("modified, out after %d lines\n", lines);
4702 g_free(itemized_chars);
4705 undo_wrapping(compose->undostruct, FALSE);
4706 compose->autowrap = prev_autowrap;
4711 void compose_action_cb(void *data)
4713 Compose *compose = (Compose *)data;
4714 compose_wrap_all(compose);
4717 static void compose_wrap_all(Compose *compose)
4719 compose_wrap_all_full(compose, FALSE);
4722 static void compose_wrap_all_full(Compose *compose, gboolean force)
4724 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4725 GtkTextBuffer *buffer;
4727 gboolean modified = TRUE;
4729 buffer = gtk_text_view_get_buffer(text);
4731 gtk_text_buffer_get_start_iter(buffer, &iter);
4733 undo_wrapping(compose->undostruct, TRUE);
4735 while (!gtk_text_iter_is_end(&iter) && modified)
4736 modified = compose_beautify_paragraph(compose, &iter, force);
4738 undo_wrapping(compose->undostruct, FALSE);
4742 static void compose_set_title(Compose *compose)
4748 edited = compose->modified ? _(" [Edited]") : "";
4750 subject = gtk_editable_get_chars(
4751 GTK_EDITABLE(compose->subject_entry), 0, -1);
4753 #ifndef GENERIC_UMPC
4754 if (subject && strlen(subject))
4755 str = g_strdup_printf(_("%s - Compose message%s"),
4758 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4760 str = g_strdup(_("Compose message"));
4763 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4769 * compose_current_mail_account:
4771 * Find a current mail account (the currently selected account, or the
4772 * default account, if a news account is currently selected). If a
4773 * mail account cannot be found, display an error message.
4775 * Return value: Mail account, or NULL if not found.
4777 static PrefsAccount *
4778 compose_current_mail_account(void)
4782 if (cur_account && cur_account->protocol != A_NNTP)
4785 ac = account_get_default();
4786 if (!ac || ac->protocol == A_NNTP) {
4787 alertpanel_error(_("Account for sending mail is not specified.\n"
4788 "Please select a mail account before sending."));
4795 #define QUOTE_IF_REQUIRED(out, str) \
4797 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4801 len = strlen(str) + 3; \
4802 if ((__tmp = alloca(len)) == NULL) { \
4803 g_warning("can't allocate memory\n"); \
4804 g_string_free(header, TRUE); \
4807 g_snprintf(__tmp, len, "\"%s\"", str); \
4812 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4813 g_warning("can't allocate memory\n"); \
4814 g_string_free(header, TRUE); \
4817 strcpy(__tmp, str); \
4823 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4825 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4829 len = strlen(str) + 3; \
4830 if ((__tmp = alloca(len)) == NULL) { \
4831 g_warning("can't allocate memory\n"); \
4834 g_snprintf(__tmp, len, "\"%s\"", str); \
4839 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4840 g_warning("can't allocate memory\n"); \
4843 strcpy(__tmp, str); \
4849 static void compose_select_account(Compose *compose, PrefsAccount *account,
4852 gchar *from = NULL, *header = NULL;
4853 ComposeHeaderEntry *header_entry;
4854 #if GTK_CHECK_VERSION(2, 24, 0)
4858 cm_return_if_fail(account != NULL);
4860 compose->account = account;
4861 if (account->name && *account->name) {
4863 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4864 qbuf = escape_internal_quotes(buf, '"');
4865 from = g_strdup_printf("%s <%s>",
4866 qbuf, account->address);
4869 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4871 from = g_strdup_printf("<%s>",
4873 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4878 compose_set_title(compose);
4880 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4881 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4883 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4884 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4885 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4887 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4889 activate_privacy_system(compose, account, FALSE);
4891 if (!init && compose->mode != COMPOSE_REDIRECT) {
4892 undo_block(compose->undostruct);
4893 compose_insert_sig(compose, TRUE);
4894 undo_unblock(compose->undostruct);
4897 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4898 #if !GTK_CHECK_VERSION(2, 24, 0)
4899 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4901 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4902 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4903 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4906 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4907 if (account->protocol == A_NNTP) {
4908 if (!strcmp(header, _("To:")))
4909 combobox_select_by_text(
4910 GTK_COMBO_BOX(header_entry->combo),
4913 if (!strcmp(header, _("Newsgroups:")))
4914 combobox_select_by_text(
4915 GTK_COMBO_BOX(header_entry->combo),
4923 /* use account's dict info if set */
4924 if (compose->gtkaspell) {
4925 if (account->enable_default_dictionary)
4926 gtkaspell_change_dict(compose->gtkaspell,
4927 account->default_dictionary, FALSE);
4928 if (account->enable_default_alt_dictionary)
4929 gtkaspell_change_alt_dict(compose->gtkaspell,
4930 account->default_alt_dictionary);
4931 if (account->enable_default_dictionary
4932 || account->enable_default_alt_dictionary)
4933 compose_spell_menu_changed(compose);
4938 gboolean compose_check_for_valid_recipient(Compose *compose) {
4939 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4940 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4941 gboolean recipient_found = FALSE;
4945 /* free to and newsgroup list */
4946 slist_free_strings_full(compose->to_list);
4947 compose->to_list = NULL;
4949 slist_free_strings_full(compose->newsgroup_list);
4950 compose->newsgroup_list = NULL;
4952 /* search header entries for to and newsgroup entries */
4953 for (list = compose->header_list; list; list = list->next) {
4956 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4957 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4960 if (entry[0] != '\0') {
4961 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4962 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4963 compose->to_list = address_list_append(compose->to_list, entry);
4964 recipient_found = TRUE;
4967 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4968 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4969 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4970 recipient_found = TRUE;
4977 return recipient_found;
4980 static gboolean compose_check_for_set_recipients(Compose *compose)
4982 if (compose->account->set_autocc && compose->account->auto_cc) {
4983 gboolean found_other = FALSE;
4985 /* search header entries for to and newsgroup entries */
4986 for (list = compose->header_list; list; list = list->next) {
4989 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4990 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4993 if (strcmp(entry, compose->account->auto_cc)
4994 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5004 if (compose->batch) {
5005 gtk_widget_show_all(compose->window);
5007 aval = alertpanel(_("Send"),
5008 _("The only recipient is the default CC address. Send anyway?"),
5009 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5010 if (aval != G_ALERTALTERNATE)
5014 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5015 gboolean found_other = FALSE;
5017 /* search header entries for to and newsgroup entries */
5018 for (list = compose->header_list; list; list = list->next) {
5021 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5022 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5025 if (strcmp(entry, compose->account->auto_bcc)
5026 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5036 if (compose->batch) {
5037 gtk_widget_show_all(compose->window);
5039 aval = alertpanel(_("Send"),
5040 _("The only recipient is the default BCC address. Send anyway?"),
5041 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5042 if (aval != G_ALERTALTERNATE)
5049 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5053 if (compose_check_for_valid_recipient(compose) == FALSE) {
5054 if (compose->batch) {
5055 gtk_widget_show_all(compose->window);
5057 alertpanel_error(_("Recipient is not specified."));
5061 if (compose_check_for_set_recipients(compose) == FALSE) {
5065 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5066 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5067 if (*str == '\0' && check_everything == TRUE &&
5068 compose->mode != COMPOSE_REDIRECT) {
5070 gchar *button_label;
5073 if (compose->sending)
5074 button_label = _("+_Send");
5076 button_label = _("+_Queue");
5077 message = g_strdup_printf(_("Subject is empty. %s"),
5078 compose->sending?_("Send it anyway?"):
5079 _("Queue it anyway?"));
5081 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5082 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5083 ALERT_QUESTION, G_ALERTDEFAULT);
5085 if (aval & G_ALERTDISABLE) {
5086 aval &= ~G_ALERTDISABLE;
5087 prefs_common.warn_empty_subj = FALSE;
5089 if (aval != G_ALERTALTERNATE)
5094 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5100 gint compose_send(Compose *compose)
5103 FolderItem *folder = NULL;
5105 gchar *msgpath = NULL;
5106 gboolean discard_window = FALSE;
5107 gchar *errstr = NULL;
5108 gchar *tmsgid = NULL;
5109 MainWindow *mainwin = mainwindow_get_mainwindow();
5110 gboolean queued_removed = FALSE;
5112 if (prefs_common.send_dialog_invisible
5113 || compose->batch == TRUE)
5114 discard_window = TRUE;
5116 compose_allow_user_actions (compose, FALSE);
5117 compose->sending = TRUE;
5119 if (compose_check_entries(compose, TRUE) == FALSE) {
5120 if (compose->batch) {
5121 gtk_widget_show_all(compose->window);
5127 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5130 if (compose->batch) {
5131 gtk_widget_show_all(compose->window);
5134 alertpanel_error(_("Could not queue message for sending:\n\n"
5135 "Charset conversion failed."));
5136 } else if (val == -5) {
5137 alertpanel_error(_("Could not queue message for sending:\n\n"
5138 "Couldn't get recipient encryption key."));
5139 } else if (val == -6) {
5141 } else if (val == -3) {
5142 if (privacy_peek_error())
5143 alertpanel_error(_("Could not queue message for sending:\n\n"
5144 "Signature failed: %s"), privacy_get_error());
5145 } else if (val == -2 && errno != 0) {
5146 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5148 alertpanel_error(_("Could not queue message for sending."));
5153 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5154 if (discard_window) {
5155 compose->sending = FALSE;
5156 compose_close(compose);
5157 /* No more compose access in the normal codepath
5158 * after this point! */
5163 alertpanel_error(_("The message was queued but could not be "
5164 "sent.\nUse \"Send queued messages\" from "
5165 "the main window to retry."));
5166 if (!discard_window) {
5173 if (msgpath == NULL) {
5174 msgpath = folder_item_fetch_msg(folder, msgnum);
5175 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5178 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5179 claws_unlink(msgpath);
5182 if (!discard_window) {
5184 if (!queued_removed)
5185 folder_item_remove_msg(folder, msgnum);
5186 folder_item_scan(folder);
5188 /* make sure we delete that */
5189 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5191 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5192 folder_item_remove_msg(folder, tmp->msgnum);
5193 procmsg_msginfo_free(tmp);
5200 if (!queued_removed)
5201 folder_item_remove_msg(folder, msgnum);
5202 folder_item_scan(folder);
5204 /* make sure we delete that */
5205 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5207 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5208 folder_item_remove_msg(folder, tmp->msgnum);
5209 procmsg_msginfo_free(tmp);
5212 if (!discard_window) {
5213 compose->sending = FALSE;
5214 compose_allow_user_actions (compose, TRUE);
5215 compose_close(compose);
5219 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5220 "the main window to retry."), errstr);
5223 alertpanel_error_log(_("The message was queued but could not be "
5224 "sent.\nUse \"Send queued messages\" from "
5225 "the main window to retry."));
5227 if (!discard_window) {
5236 toolbar_main_set_sensitive(mainwin);
5237 main_window_set_menu_sensitive(mainwin);
5243 compose_allow_user_actions (compose, TRUE);
5244 compose->sending = FALSE;
5245 compose->modified = TRUE;
5246 toolbar_main_set_sensitive(mainwin);
5247 main_window_set_menu_sensitive(mainwin);
5252 static gboolean compose_use_attach(Compose *compose)
5254 GtkTreeModel *model = gtk_tree_view_get_model
5255 (GTK_TREE_VIEW(compose->attach_clist));
5256 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5259 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5262 gchar buf[BUFFSIZE];
5264 gboolean first_to_address;
5265 gboolean first_cc_address;
5267 ComposeHeaderEntry *headerentry;
5268 const gchar *headerentryname;
5269 const gchar *cc_hdr;
5270 const gchar *to_hdr;
5271 gboolean err = FALSE;
5273 debug_print("Writing redirect header\n");
5275 cc_hdr = prefs_common_translated_header_name("Cc:");
5276 to_hdr = prefs_common_translated_header_name("To:");
5278 first_to_address = TRUE;
5279 for (list = compose->header_list; list; list = list->next) {
5280 headerentry = ((ComposeHeaderEntry *)list->data);
5281 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5283 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5284 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5285 Xstrdup_a(str, entstr, return -1);
5287 if (str[0] != '\0') {
5288 compose_convert_header
5289 (compose, buf, sizeof(buf), str,
5290 strlen("Resent-To") + 2, TRUE);
5292 if (first_to_address) {
5293 err |= (fprintf(fp, "Resent-To: ") < 0);
5294 first_to_address = FALSE;
5296 err |= (fprintf(fp, ",") < 0);
5298 err |= (fprintf(fp, "%s", buf) < 0);
5302 if (!first_to_address) {
5303 err |= (fprintf(fp, "\n") < 0);
5306 first_cc_address = TRUE;
5307 for (list = compose->header_list; list; list = list->next) {
5308 headerentry = ((ComposeHeaderEntry *)list->data);
5309 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5311 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5312 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5313 Xstrdup_a(str, strg, return -1);
5315 if (str[0] != '\0') {
5316 compose_convert_header
5317 (compose, buf, sizeof(buf), str,
5318 strlen("Resent-Cc") + 2, TRUE);
5320 if (first_cc_address) {
5321 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5322 first_cc_address = FALSE;
5324 err |= (fprintf(fp, ",") < 0);
5326 err |= (fprintf(fp, "%s", buf) < 0);
5330 if (!first_cc_address) {
5331 err |= (fprintf(fp, "\n") < 0);
5334 return (err ? -1:0);
5337 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5339 gchar buf[BUFFSIZE];
5341 const gchar *entstr;
5342 /* struct utsname utsbuf; */
5343 gboolean err = FALSE;
5345 cm_return_val_if_fail(fp != NULL, -1);
5346 cm_return_val_if_fail(compose->account != NULL, -1);
5347 cm_return_val_if_fail(compose->account->address != NULL, -1);
5350 get_rfc822_date(buf, sizeof(buf));
5351 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5354 if (compose->account->name && *compose->account->name) {
5355 compose_convert_header
5356 (compose, buf, sizeof(buf), compose->account->name,
5357 strlen("From: "), TRUE);
5358 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5359 buf, compose->account->address) < 0);
5361 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5364 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5365 if (*entstr != '\0') {
5366 Xstrdup_a(str, entstr, return -1);
5369 compose_convert_header(compose, buf, sizeof(buf), str,
5370 strlen("Subject: "), FALSE);
5371 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5375 /* Resent-Message-ID */
5376 if (compose->account->set_domain && compose->account->domain) {
5377 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5378 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5379 g_snprintf(buf, sizeof(buf), "%s",
5380 strchr(compose->account->address, '@') ?
5381 strchr(compose->account->address, '@')+1 :
5382 compose->account->address);
5384 g_snprintf(buf, sizeof(buf), "%s", "");
5387 if (compose->account->gen_msgid) {
5389 if (compose->account->msgid_with_addr) {
5390 addr = compose->account->address;
5392 generate_msgid(buf, sizeof(buf), addr);
5393 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5395 g_free(compose->msgid);
5396 compose->msgid = g_strdup(buf);
5398 compose->msgid = NULL;
5401 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5404 /* separator between header and body */
5405 err |= (fputs("\n", fp) == EOF);
5407 return (err ? -1:0);
5410 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5414 gchar buf[BUFFSIZE];
5416 gboolean skip = FALSE;
5417 gboolean err = FALSE;
5418 gchar *not_included[]={
5419 "Return-Path:", "Delivered-To:", "Received:",
5420 "Subject:", "X-UIDL:", "AF:",
5421 "NF:", "PS:", "SRH:",
5422 "SFN:", "DSR:", "MID:",
5423 "CFG:", "PT:", "S:",
5424 "RQ:", "SSV:", "NSV:",
5425 "SSH:", "R:", "MAID:",
5426 "NAID:", "RMID:", "FMID:",
5427 "SCF:", "RRCPT:", "NG:",
5428 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5429 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5430 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5431 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5432 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5435 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5436 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5440 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5442 for (i = 0; not_included[i] != NULL; i++) {
5443 if (g_ascii_strncasecmp(buf, not_included[i],
5444 strlen(not_included[i])) == 0) {
5451 if (fputs(buf, fdest) == -1)
5454 if (!prefs_common.redirect_keep_from) {
5455 if (g_ascii_strncasecmp(buf, "From:",
5456 strlen("From:")) == 0) {
5457 err |= (fputs(" (by way of ", fdest) == EOF);
5458 if (compose->account->name
5459 && *compose->account->name) {
5460 compose_convert_header
5461 (compose, buf, sizeof(buf),
5462 compose->account->name,
5465 err |= (fprintf(fdest, "%s <%s>",
5467 compose->account->address) < 0);
5469 err |= (fprintf(fdest, "%s",
5470 compose->account->address) < 0);
5471 err |= (fputs(")", fdest) == EOF);
5475 if (fputs("\n", fdest) == -1)
5482 if (compose_redirect_write_headers(compose, fdest))
5485 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5486 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5499 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5501 GtkTextBuffer *buffer;
5502 GtkTextIter start, end;
5505 const gchar *out_codeset;
5506 EncodingType encoding = ENC_UNKNOWN;
5507 MimeInfo *mimemsg, *mimetext;
5509 const gchar *src_codeset = CS_INTERNAL;
5510 gchar *from_addr = NULL;
5511 gchar *from_name = NULL;
5513 if (action == COMPOSE_WRITE_FOR_SEND)
5514 attach_parts = TRUE;
5516 /* create message MimeInfo */
5517 mimemsg = procmime_mimeinfo_new();
5518 mimemsg->type = MIMETYPE_MESSAGE;
5519 mimemsg->subtype = g_strdup("rfc822");
5520 mimemsg->content = MIMECONTENT_MEM;
5521 mimemsg->tmp = TRUE; /* must free content later */
5522 mimemsg->data.mem = compose_get_header(compose);
5524 /* Create text part MimeInfo */
5525 /* get all composed text */
5526 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5527 gtk_text_buffer_get_start_iter(buffer, &start);
5528 gtk_text_buffer_get_end_iter(buffer, &end);
5529 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5531 out_codeset = conv_get_charset_str(compose->out_encoding);
5533 if (!out_codeset && is_ascii_str(chars)) {
5534 out_codeset = CS_US_ASCII;
5535 } else if (prefs_common.outgoing_fallback_to_ascii &&
5536 is_ascii_str(chars)) {
5537 out_codeset = CS_US_ASCII;
5538 encoding = ENC_7BIT;
5542 gchar *test_conv_global_out = NULL;
5543 gchar *test_conv_reply = NULL;
5545 /* automatic mode. be automatic. */
5546 codeconv_set_strict(TRUE);
5548 out_codeset = conv_get_outgoing_charset_str();
5550 debug_print("trying to convert to %s\n", out_codeset);
5551 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5554 if (!test_conv_global_out && compose->orig_charset
5555 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5556 out_codeset = compose->orig_charset;
5557 debug_print("failure; trying to convert to %s\n", out_codeset);
5558 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5561 if (!test_conv_global_out && !test_conv_reply) {
5563 out_codeset = CS_INTERNAL;
5564 debug_print("failure; finally using %s\n", out_codeset);
5566 g_free(test_conv_global_out);
5567 g_free(test_conv_reply);
5568 codeconv_set_strict(FALSE);
5571 if (encoding == ENC_UNKNOWN) {
5572 if (prefs_common.encoding_method == CTE_BASE64)
5573 encoding = ENC_BASE64;
5574 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5575 encoding = ENC_QUOTED_PRINTABLE;
5576 else if (prefs_common.encoding_method == CTE_8BIT)
5577 encoding = ENC_8BIT;
5579 encoding = procmime_get_encoding_for_charset(out_codeset);
5582 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5583 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5585 if (action == COMPOSE_WRITE_FOR_SEND) {
5586 codeconv_set_strict(TRUE);
5587 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5588 codeconv_set_strict(FALSE);
5594 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5595 "to the specified %s charset.\n"
5596 "Send it as %s?"), out_codeset, src_codeset);
5597 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5598 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5601 if (aval != G_ALERTALTERNATE) {
5606 out_codeset = src_codeset;
5612 out_codeset = src_codeset;
5617 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5618 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5619 strstr(buf, "\nFrom ") != NULL) {
5620 encoding = ENC_QUOTED_PRINTABLE;
5624 mimetext = procmime_mimeinfo_new();
5625 mimetext->content = MIMECONTENT_MEM;
5626 mimetext->tmp = TRUE; /* must free content later */
5627 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5628 * and free the data, which we need later. */
5629 mimetext->data.mem = g_strdup(buf);
5630 mimetext->type = MIMETYPE_TEXT;
5631 mimetext->subtype = g_strdup("plain");
5632 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5633 g_strdup(out_codeset));
5635 /* protect trailing spaces when signing message */
5636 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5637 privacy_system_can_sign(compose->privacy_system)) {
5638 encoding = ENC_QUOTED_PRINTABLE;
5641 debug_print("main text: %zd bytes encoded as %s in %d\n",
5642 strlen(buf), out_codeset, encoding);
5644 /* check for line length limit */
5645 if (action == COMPOSE_WRITE_FOR_SEND &&
5646 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5647 check_line_length(buf, 1000, &line) < 0) {
5651 msg = g_strdup_printf
5652 (_("Line %d exceeds the line length limit (998 bytes).\n"
5653 "The contents of the message might be broken on the way to the delivery.\n"
5655 "Send it anyway?"), line + 1);
5656 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5658 if (aval != G_ALERTALTERNATE) {
5664 if (encoding != ENC_UNKNOWN)
5665 procmime_encode_content(mimetext, encoding);
5667 /* append attachment parts */
5668 if (compose_use_attach(compose) && attach_parts) {
5669 MimeInfo *mimempart;
5670 gchar *boundary = NULL;
5671 mimempart = procmime_mimeinfo_new();
5672 mimempart->content = MIMECONTENT_EMPTY;
5673 mimempart->type = MIMETYPE_MULTIPART;
5674 mimempart->subtype = g_strdup("mixed");
5678 boundary = generate_mime_boundary(NULL);
5679 } while (strstr(buf, boundary) != NULL);
5681 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5684 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5686 g_node_append(mimempart->node, mimetext->node);
5687 g_node_append(mimemsg->node, mimempart->node);
5689 if (compose_add_attachments(compose, mimempart) < 0)
5692 g_node_append(mimemsg->node, mimetext->node);
5696 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5697 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5698 /* extract name and address */
5699 if (strstr(spec, " <") && strstr(spec, ">")) {
5700 from_addr = g_strdup(strrchr(spec, '<')+1);
5701 *(strrchr(from_addr, '>')) = '\0';
5702 from_name = g_strdup(spec);
5703 *(strrchr(from_name, '<')) = '\0';
5710 /* sign message if sending */
5711 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5712 privacy_system_can_sign(compose->privacy_system))
5713 if (!privacy_sign(compose->privacy_system, mimemsg,
5714 compose->account, from_addr)) {
5721 procmime_write_mimeinfo(mimemsg, fp);
5723 procmime_mimeinfo_free_all(mimemsg);
5728 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5730 GtkTextBuffer *buffer;
5731 GtkTextIter start, end;
5736 if ((fp = g_fopen(file, "wb")) == NULL) {
5737 FILE_OP_ERROR(file, "fopen");
5741 /* chmod for security */
5742 if (change_file_mode_rw(fp, file) < 0) {
5743 FILE_OP_ERROR(file, "chmod");
5744 g_warning("can't change file mode\n");
5747 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5748 gtk_text_buffer_get_start_iter(buffer, &start);
5749 gtk_text_buffer_get_end_iter(buffer, &end);
5750 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5752 chars = conv_codeset_strdup
5753 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5762 len = strlen(chars);
5763 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5764 FILE_OP_ERROR(file, "fwrite");
5773 if (fclose(fp) == EOF) {
5774 FILE_OP_ERROR(file, "fclose");
5781 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5784 MsgInfo *msginfo = compose->targetinfo;
5786 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5787 if (!msginfo) return -1;
5789 if (!force && MSG_IS_LOCKED(msginfo->flags))
5792 item = msginfo->folder;
5793 cm_return_val_if_fail(item != NULL, -1);
5795 if (procmsg_msg_exist(msginfo) &&
5796 (folder_has_parent_of_type(item, F_QUEUE) ||
5797 folder_has_parent_of_type(item, F_DRAFT)
5798 || msginfo == compose->autosaved_draft)) {
5799 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5800 g_warning("can't remove the old message\n");
5803 debug_print("removed reedit target %d\n", msginfo->msgnum);
5810 static void compose_remove_draft(Compose *compose)
5813 MsgInfo *msginfo = compose->targetinfo;
5814 drafts = account_get_special_folder(compose->account, F_DRAFT);
5816 if (procmsg_msg_exist(msginfo)) {
5817 folder_item_remove_msg(drafts, msginfo->msgnum);
5822 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5823 gboolean remove_reedit_target)
5825 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5828 static gboolean compose_warn_encryption(Compose *compose)
5830 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5831 AlertValue val = G_ALERTALTERNATE;
5833 if (warning == NULL)
5836 val = alertpanel_full(_("Encryption warning"), warning,
5837 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5838 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5839 if (val & G_ALERTDISABLE) {
5840 val &= ~G_ALERTDISABLE;
5841 if (val == G_ALERTALTERNATE)
5842 privacy_inhibit_encrypt_warning(compose->privacy_system,
5846 if (val == G_ALERTALTERNATE) {
5853 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5854 gchar **msgpath, gboolean check_subject,
5855 gboolean remove_reedit_target)
5862 PrefsAccount *mailac = NULL, *newsac = NULL;
5863 gboolean err = FALSE;
5865 debug_print("queueing message...\n");
5866 cm_return_val_if_fail(compose->account != NULL, -1);
5868 if (compose_check_entries(compose, check_subject) == FALSE) {
5869 if (compose->batch) {
5870 gtk_widget_show_all(compose->window);
5875 if (!compose->to_list && !compose->newsgroup_list) {
5876 g_warning("can't get recipient list.");
5880 if (compose->to_list) {
5881 if (compose->account->protocol != A_NNTP)
5882 mailac = compose->account;
5883 else if (cur_account && cur_account->protocol != A_NNTP)
5884 mailac = cur_account;
5885 else if (!(mailac = compose_current_mail_account())) {
5886 alertpanel_error(_("No account for sending mails available!"));
5891 if (compose->newsgroup_list) {
5892 if (compose->account->protocol == A_NNTP)
5893 newsac = compose->account;
5895 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5900 /* write queue header */
5901 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5902 G_DIR_SEPARATOR, compose, (guint) rand());
5903 debug_print("queuing to %s\n", tmp);
5904 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5905 FILE_OP_ERROR(tmp, "fopen");
5910 if (change_file_mode_rw(fp, tmp) < 0) {
5911 FILE_OP_ERROR(tmp, "chmod");
5912 g_warning("can't change file mode\n");
5915 /* queueing variables */
5916 err |= (fprintf(fp, "AF:\n") < 0);
5917 err |= (fprintf(fp, "NF:0\n") < 0);
5918 err |= (fprintf(fp, "PS:10\n") < 0);
5919 err |= (fprintf(fp, "SRH:1\n") < 0);
5920 err |= (fprintf(fp, "SFN:\n") < 0);
5921 err |= (fprintf(fp, "DSR:\n") < 0);
5923 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5925 err |= (fprintf(fp, "MID:\n") < 0);
5926 err |= (fprintf(fp, "CFG:\n") < 0);
5927 err |= (fprintf(fp, "PT:0\n") < 0);
5928 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5929 err |= (fprintf(fp, "RQ:\n") < 0);
5931 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5933 err |= (fprintf(fp, "SSV:\n") < 0);
5935 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5937 err |= (fprintf(fp, "NSV:\n") < 0);
5938 err |= (fprintf(fp, "SSH:\n") < 0);
5939 /* write recepient list */
5940 if (compose->to_list) {
5941 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5942 for (cur = compose->to_list->next; cur != NULL;
5944 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5945 err |= (fprintf(fp, "\n") < 0);
5947 /* write newsgroup list */
5948 if (compose->newsgroup_list) {
5949 err |= (fprintf(fp, "NG:") < 0);
5950 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5951 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5952 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5953 err |= (fprintf(fp, "\n") < 0);
5955 /* Sylpheed account IDs */
5957 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5959 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5962 if (compose->privacy_system != NULL) {
5963 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5964 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5965 if (compose->use_encryption) {
5967 if (!compose_warn_encryption(compose)) {
5973 if (mailac && mailac->encrypt_to_self) {
5974 GSList *tmp_list = g_slist_copy(compose->to_list);
5975 tmp_list = g_slist_append(tmp_list, compose->account->address);
5976 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5977 g_slist_free(tmp_list);
5979 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5981 if (encdata != NULL) {
5982 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5983 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5984 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5986 } /* else we finally dont want to encrypt */
5988 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5989 /* and if encdata was null, it means there's been a problem in
5992 g_warning("failed to write queue message");
6002 /* Save copy folder */
6003 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6004 gchar *savefolderid;
6006 savefolderid = compose_get_save_to(compose);
6007 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6008 g_free(savefolderid);
6010 /* Save copy folder */
6011 if (compose->return_receipt) {
6012 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6014 /* Message-ID of message replying to */
6015 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6016 gchar *folderid = NULL;
6018 if (compose->replyinfo->folder)
6019 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6020 if (folderid == NULL)
6021 folderid = g_strdup("NULL");
6023 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6026 /* Message-ID of message forwarding to */
6027 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6028 gchar *folderid = NULL;
6030 if (compose->fwdinfo->folder)
6031 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6032 if (folderid == NULL)
6033 folderid = g_strdup("NULL");
6035 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6039 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6040 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6042 /* end of headers */
6043 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6045 if (compose->redirect_filename != NULL) {
6046 if (compose_redirect_write_to_file(compose, fp) < 0) {
6054 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6058 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6062 g_warning("failed to write queue message\n");
6068 if (fclose(fp) == EOF) {
6069 FILE_OP_ERROR(tmp, "fclose");
6075 if (item && *item) {
6078 queue = account_get_special_folder(compose->account, F_QUEUE);
6081 g_warning("can't find queue folder\n");
6086 folder_item_scan(queue);
6087 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6088 g_warning("can't queue the message\n");
6094 if (msgpath == NULL) {
6100 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6101 compose_remove_reedit_target(compose, FALSE);
6104 if ((msgnum != NULL) && (item != NULL)) {
6112 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6115 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6117 struct stat statbuf;
6118 gchar *type, *subtype;
6119 GtkTreeModel *model;
6122 model = gtk_tree_view_get_model(tree_view);
6124 if (!gtk_tree_model_get_iter_first(model, &iter))
6127 gtk_tree_model_get(model, &iter,
6131 if (!is_file_exist(ainfo->file)) {
6132 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6133 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6134 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6136 if (val == G_ALERTDEFAULT) {
6141 if (g_stat(ainfo->file, &statbuf) < 0)
6144 mimepart = procmime_mimeinfo_new();
6145 mimepart->content = MIMECONTENT_FILE;
6146 mimepart->data.filename = g_strdup(ainfo->file);
6147 mimepart->tmp = FALSE; /* or we destroy our attachment */
6148 mimepart->offset = 0;
6149 mimepart->length = statbuf.st_size;
6151 type = g_strdup(ainfo->content_type);
6153 if (!strchr(type, '/')) {
6155 type = g_strdup("application/octet-stream");
6158 subtype = strchr(type, '/') + 1;
6159 *(subtype - 1) = '\0';
6160 mimepart->type = procmime_get_media_type(type);
6161 mimepart->subtype = g_strdup(subtype);
6164 if (mimepart->type == MIMETYPE_MESSAGE &&
6165 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6166 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6167 } else if (mimepart->type == MIMETYPE_TEXT) {
6168 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6169 /* Text parts with no name come from multipart/alternative
6170 * forwards. Make sure the recipient won't look at the
6171 * original HTML part by mistake. */
6172 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6173 ainfo->name = g_strdup_printf(_("Original %s part"),
6177 g_hash_table_insert(mimepart->typeparameters,
6178 g_strdup("charset"), g_strdup(ainfo->charset));
6180 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6181 if (mimepart->type == MIMETYPE_APPLICATION &&
6182 !strcmp2(mimepart->subtype, "octet-stream"))
6183 g_hash_table_insert(mimepart->typeparameters,
6184 g_strdup("name"), g_strdup(ainfo->name));
6185 g_hash_table_insert(mimepart->dispositionparameters,
6186 g_strdup("filename"), g_strdup(ainfo->name));
6187 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6190 if (mimepart->type == MIMETYPE_MESSAGE
6191 || mimepart->type == MIMETYPE_MULTIPART)
6192 ainfo->encoding = ENC_BINARY;
6193 else if (compose->use_signing) {
6194 if (ainfo->encoding == ENC_7BIT)
6195 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6196 else if (ainfo->encoding == ENC_8BIT)
6197 ainfo->encoding = ENC_BASE64;
6202 procmime_encode_content(mimepart, ainfo->encoding);
6204 g_node_append(parent->node, mimepart->node);
6205 } while (gtk_tree_model_iter_next(model, &iter));
6210 static gchar *compose_quote_list_of_addresses(gchar *str)
6212 GSList *list = NULL, *item = NULL;
6213 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6215 list = address_list_append_with_comments(list, str);
6216 for (item = list; item != NULL; item = item->next) {
6217 gchar *spec = item->data;
6218 gchar *endofname = strstr(spec, " <");
6219 if (endofname != NULL) {
6222 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6223 qqname = escape_internal_quotes(qname, '"');
6225 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6226 gchar *addr = g_strdup(endofname);
6227 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6228 faddr = g_strconcat(name, addr, NULL);
6231 debug_print("new auto-quoted address: '%s'", faddr);
6235 result = g_strdup((faddr != NULL)? faddr: spec);
6237 result = g_strconcat(result,
6239 (faddr != NULL)? faddr: spec,
6242 if (faddr != NULL) {
6247 slist_free_strings_full(list);
6252 #define IS_IN_CUSTOM_HEADER(header) \
6253 (compose->account->add_customhdr && \
6254 custom_header_find(compose->account->customhdr_list, header) != NULL)
6256 static void compose_add_headerfield_from_headerlist(Compose *compose,
6258 const gchar *fieldname,
6259 const gchar *seperator)
6261 gchar *str, *fieldname_w_colon;
6262 gboolean add_field = FALSE;
6264 ComposeHeaderEntry *headerentry;
6265 const gchar *headerentryname;
6266 const gchar *trans_fieldname;
6269 if (IS_IN_CUSTOM_HEADER(fieldname))
6272 debug_print("Adding %s-fields\n", fieldname);
6274 fieldstr = g_string_sized_new(64);
6276 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6277 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6279 for (list = compose->header_list; list; list = list->next) {
6280 headerentry = ((ComposeHeaderEntry *)list->data);
6281 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6283 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6284 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6286 str = compose_quote_list_of_addresses(ustr);
6288 if (str != NULL && str[0] != '\0') {
6290 g_string_append(fieldstr, seperator);
6291 g_string_append(fieldstr, str);
6300 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6301 compose_convert_header
6302 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6303 strlen(fieldname) + 2, TRUE);
6304 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6308 g_free(fieldname_w_colon);
6309 g_string_free(fieldstr, TRUE);
6314 static gchar *compose_get_manual_headers_info(Compose *compose)
6316 GString *sh_header = g_string_new(" ");
6318 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6320 for (list = compose->header_list; list; list = list->next) {
6321 ComposeHeaderEntry *headerentry;
6324 gchar *headername_wcolon;
6325 const gchar *headername_trans;
6327 gboolean standard_header = FALSE;
6329 headerentry = ((ComposeHeaderEntry *)list->data);
6331 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6333 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6338 if (!strstr(tmp, ":")) {
6339 headername_wcolon = g_strconcat(tmp, ":", NULL);
6340 headername = g_strdup(tmp);
6342 headername_wcolon = g_strdup(tmp);
6343 headername = g_strdup(strtok(tmp, ":"));
6347 string = std_headers;
6348 while (*string != NULL) {
6349 headername_trans = prefs_common_translated_header_name(*string);
6350 if (!strcmp(headername_trans, headername_wcolon))
6351 standard_header = TRUE;
6354 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6355 g_string_append_printf(sh_header, "%s ", headername);
6357 g_free(headername_wcolon);
6359 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6360 return g_string_free(sh_header, FALSE);
6363 static gchar *compose_get_header(Compose *compose)
6365 gchar buf[BUFFSIZE];
6366 const gchar *entry_str;
6370 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6372 gchar *from_name = NULL, *from_address = NULL;
6375 cm_return_val_if_fail(compose->account != NULL, NULL);
6376 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6378 header = g_string_sized_new(64);
6381 get_rfc822_date(buf, sizeof(buf));
6382 g_string_append_printf(header, "Date: %s\n", buf);
6386 if (compose->account->name && *compose->account->name) {
6388 QUOTE_IF_REQUIRED(buf, compose->account->name);
6389 tmp = g_strdup_printf("%s <%s>",
6390 buf, compose->account->address);
6392 tmp = g_strdup_printf("%s",
6393 compose->account->address);
6395 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6396 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6398 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6399 from_address = g_strdup(compose->account->address);
6401 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6402 /* extract name and address */
6403 if (strstr(spec, " <") && strstr(spec, ">")) {
6404 from_address = g_strdup(strrchr(spec, '<')+1);
6405 *(strrchr(from_address, '>')) = '\0';
6406 from_name = g_strdup(spec);
6407 *(strrchr(from_name, '<')) = '\0';
6410 from_address = g_strdup(spec);
6417 if (from_name && *from_name) {
6419 compose_convert_header
6420 (compose, buf, sizeof(buf), from_name,
6421 strlen("From: "), TRUE);
6422 QUOTE_IF_REQUIRED(name, buf);
6423 qname = escape_internal_quotes(name, '"');
6425 g_string_append_printf(header, "From: %s <%s>\n",
6426 qname, from_address);
6430 g_string_append_printf(header, "From: %s\n", from_address);
6433 g_free(from_address);
6436 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6439 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6442 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6446 * If this account is a NNTP account remove Bcc header from
6447 * message body since it otherwise will be publicly shown
6449 if (compose->account->protocol != A_NNTP)
6450 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6453 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6455 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6458 compose_convert_header(compose, buf, sizeof(buf), str,
6459 strlen("Subject: "), FALSE);
6460 g_string_append_printf(header, "Subject: %s\n", buf);
6466 if (compose->account->set_domain && compose->account->domain) {
6467 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6468 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6469 g_snprintf(buf, sizeof(buf), "%s",
6470 strchr(compose->account->address, '@') ?
6471 strchr(compose->account->address, '@')+1 :
6472 compose->account->address);
6474 g_snprintf(buf, sizeof(buf), "%s", "");
6477 if (compose->account->gen_msgid) {
6479 if (compose->account->msgid_with_addr) {
6480 addr = compose->account->address;
6482 generate_msgid(buf, sizeof(buf), addr);
6483 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6485 g_free(compose->msgid);
6486 compose->msgid = g_strdup(buf);
6488 compose->msgid = NULL;
6491 if (compose->remove_references == FALSE) {
6493 if (compose->inreplyto && compose->to_list)
6494 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6497 if (compose->references)
6498 g_string_append_printf(header, "References: %s\n", compose->references);
6502 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6505 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6508 if (compose->account->organization &&
6509 strlen(compose->account->organization) &&
6510 !IS_IN_CUSTOM_HEADER("Organization")) {
6511 compose_convert_header(compose, buf, sizeof(buf),
6512 compose->account->organization,
6513 strlen("Organization: "), FALSE);
6514 g_string_append_printf(header, "Organization: %s\n", buf);
6517 /* Program version and system info */
6518 if (compose->account->gen_xmailer &&
6519 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6520 !compose->newsgroup_list) {
6521 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6523 gtk_major_version, gtk_minor_version, gtk_micro_version,
6526 if (compose->account->gen_xmailer &&
6527 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6528 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6530 gtk_major_version, gtk_minor_version, gtk_micro_version,
6534 /* custom headers */
6535 if (compose->account->add_customhdr) {
6538 for (cur = compose->account->customhdr_list; cur != NULL;
6540 CustomHeader *chdr = (CustomHeader *)cur->data;
6542 if (custom_header_is_allowed(chdr->name)
6543 && chdr->value != NULL
6544 && *(chdr->value) != '\0') {
6545 compose_convert_header
6546 (compose, buf, sizeof(buf),
6548 strlen(chdr->name) + 2, FALSE);
6549 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6554 /* Automatic Faces and X-Faces */
6555 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6556 g_string_append_printf(header, "X-Face: %s\n", buf);
6558 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6559 g_string_append_printf(header, "X-Face: %s\n", buf);
6561 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6562 g_string_append_printf(header, "Face: %s\n", buf);
6564 else if (get_default_face (buf, sizeof(buf)) == 0) {
6565 g_string_append_printf(header, "Face: %s\n", buf);
6569 switch (compose->priority) {
6570 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6571 "X-Priority: 1 (Highest)\n");
6573 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6574 "X-Priority: 2 (High)\n");
6576 case PRIORITY_NORMAL: break;
6577 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6578 "X-Priority: 4 (Low)\n");
6580 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6581 "X-Priority: 5 (Lowest)\n");
6583 default: debug_print("compose: priority unknown : %d\n",
6587 /* Request Return Receipt */
6588 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6589 if (compose->return_receipt) {
6590 if (compose->account->name
6591 && *compose->account->name) {
6592 compose_convert_header(compose, buf, sizeof(buf),
6593 compose->account->name,
6594 strlen("Disposition-Notification-To: "),
6596 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6598 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6602 /* get special headers */
6603 for (list = compose->header_list; list; list = list->next) {
6604 ComposeHeaderEntry *headerentry;
6607 gchar *headername_wcolon;
6608 const gchar *headername_trans;
6611 gboolean standard_header = FALSE;
6613 headerentry = ((ComposeHeaderEntry *)list->data);
6615 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6617 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6622 if (!strstr(tmp, ":")) {
6623 headername_wcolon = g_strconcat(tmp, ":", NULL);
6624 headername = g_strdup(tmp);
6626 headername_wcolon = g_strdup(tmp);
6627 headername = g_strdup(strtok(tmp, ":"));
6631 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6632 Xstrdup_a(headervalue, entry_str, return NULL);
6633 subst_char(headervalue, '\r', ' ');
6634 subst_char(headervalue, '\n', ' ');
6635 string = std_headers;
6636 while (*string != NULL) {
6637 headername_trans = prefs_common_translated_header_name(*string);
6638 if (!strcmp(headername_trans, headername_wcolon))
6639 standard_header = TRUE;
6642 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6643 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6646 g_free(headername_wcolon);
6650 g_string_free(header, FALSE);
6655 #undef IS_IN_CUSTOM_HEADER
6657 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6658 gint header_len, gboolean addr_field)
6660 gchar *tmpstr = NULL;
6661 const gchar *out_codeset = NULL;
6663 cm_return_if_fail(src != NULL);
6664 cm_return_if_fail(dest != NULL);
6666 if (len < 1) return;
6668 tmpstr = g_strdup(src);
6670 subst_char(tmpstr, '\n', ' ');
6671 subst_char(tmpstr, '\r', ' ');
6674 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6675 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6676 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6681 codeconv_set_strict(TRUE);
6682 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6683 conv_get_charset_str(compose->out_encoding));
6684 codeconv_set_strict(FALSE);
6686 if (!dest || *dest == '\0') {
6687 gchar *test_conv_global_out = NULL;
6688 gchar *test_conv_reply = NULL;
6690 /* automatic mode. be automatic. */
6691 codeconv_set_strict(TRUE);
6693 out_codeset = conv_get_outgoing_charset_str();
6695 debug_print("trying to convert to %s\n", out_codeset);
6696 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6699 if (!test_conv_global_out && compose->orig_charset
6700 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6701 out_codeset = compose->orig_charset;
6702 debug_print("failure; trying to convert to %s\n", out_codeset);
6703 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6706 if (!test_conv_global_out && !test_conv_reply) {
6708 out_codeset = CS_INTERNAL;
6709 debug_print("finally using %s\n", out_codeset);
6711 g_free(test_conv_global_out);
6712 g_free(test_conv_reply);
6713 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6715 codeconv_set_strict(FALSE);
6720 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6724 cm_return_if_fail(user_data != NULL);
6726 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6727 g_strstrip(address);
6728 if (*address != '\0') {
6729 gchar *name = procheader_get_fromname(address);
6730 extract_address(address);
6731 #ifndef USE_NEW_ADDRBOOK
6732 addressbook_add_contact(name, address, NULL, NULL);
6734 debug_print("%s: %s\n", name, address);
6735 if (addressadd_selection(name, address, NULL, NULL)) {
6736 debug_print( "addressbook_add_contact - added\n" );
6743 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6745 GtkWidget *menuitem;
6748 cm_return_if_fail(menu != NULL);
6749 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6751 menuitem = gtk_separator_menu_item_new();
6752 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6753 gtk_widget_show(menuitem);
6755 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6756 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6758 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6759 g_strstrip(address);
6760 if (*address == '\0') {
6761 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6764 g_signal_connect(G_OBJECT(menuitem), "activate",
6765 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6766 gtk_widget_show(menuitem);
6769 void compose_add_extra_header(gchar *header, GtkListStore *model)
6772 if (strcmp(header, "")) {
6773 COMBOBOX_ADD(model, header, COMPOSE_TO);
6777 void compose_add_extra_header_entries(GtkListStore *model)
6781 gchar buf[BUFFSIZE];
6784 if (extra_headers == NULL) {
6785 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6786 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6787 debug_print("extra headers file not found\n");
6788 goto extra_headers_done;
6790 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6791 lastc = strlen(buf) - 1; /* remove trailing control chars */
6792 while (lastc >= 0 && buf[lastc] != ':')
6793 buf[lastc--] = '\0';
6794 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6795 buf[lastc] = '\0'; /* remove trailing : for comparison */
6796 if (custom_header_is_allowed(buf)) {
6798 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6801 g_message("disallowed extra header line: %s\n", buf);
6805 g_message("invalid extra header line: %s\n", buf);
6811 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6812 extra_headers = g_slist_reverse(extra_headers);
6814 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6817 static void compose_create_header_entry(Compose *compose)
6819 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6826 const gchar *header = NULL;
6827 ComposeHeaderEntry *headerentry;
6828 gboolean standard_header = FALSE;
6829 GtkListStore *model;
6832 headerentry = g_new0(ComposeHeaderEntry, 1);
6834 /* Combo box model */
6835 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6836 #if !GTK_CHECK_VERSION(2, 24, 0)
6837 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6839 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6841 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6843 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6845 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6846 COMPOSE_NEWSGROUPS);
6847 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6849 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6850 COMPOSE_FOLLOWUPTO);
6851 compose_add_extra_header_entries(model);
6854 #if GTK_CHECK_VERSION(2, 24, 0)
6855 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6856 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6857 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6858 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6859 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6861 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6862 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6863 G_CALLBACK(compose_grab_focus_cb), compose);
6864 gtk_widget_show(combo);
6866 /* Putting only the combobox child into focus chain of its parent causes
6867 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6868 * This eliminates need to pres Tab twice in order to really get from the
6869 * combobox to next widget. */
6871 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6872 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6875 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6876 compose->header_nextrow, compose->header_nextrow+1,
6877 GTK_SHRINK, GTK_FILL, 0, 0);
6878 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6879 const gchar *last_header_entry = gtk_entry_get_text(
6880 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6882 while (*string != NULL) {
6883 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6884 standard_header = TRUE;
6887 if (standard_header)
6888 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6890 if (!compose->header_last || !standard_header) {
6891 switch(compose->account->protocol) {
6893 header = prefs_common_translated_header_name("Newsgroups:");
6896 header = prefs_common_translated_header_name("To:");
6901 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6903 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6904 G_CALLBACK(compose_grab_focus_cb), compose);
6906 /* Entry field with cleanup button */
6907 button = gtk_button_new();
6908 gtk_button_set_image(GTK_BUTTON(button),
6909 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6910 gtk_widget_show(button);
6911 CLAWS_SET_TIP(button,
6912 _("Delete entry contents"));
6913 entry = gtk_entry_new();
6914 gtk_widget_show(entry);
6915 CLAWS_SET_TIP(entry,
6916 _("Use <tab> to autocomplete from addressbook"));
6917 hbox = gtk_hbox_new (FALSE, 0);
6918 gtk_widget_show(hbox);
6919 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6920 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6921 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6922 compose->header_nextrow, compose->header_nextrow+1,
6923 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6925 g_signal_connect(G_OBJECT(entry), "key-press-event",
6926 G_CALLBACK(compose_headerentry_key_press_event_cb),
6928 g_signal_connect(G_OBJECT(entry), "changed",
6929 G_CALLBACK(compose_headerentry_changed_cb),
6931 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6932 G_CALLBACK(compose_grab_focus_cb), compose);
6934 g_signal_connect(G_OBJECT(button), "clicked",
6935 G_CALLBACK(compose_headerentry_button_clicked_cb),
6939 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6940 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6941 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6942 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6943 G_CALLBACK(compose_header_drag_received_cb),
6945 g_signal_connect(G_OBJECT(entry), "drag-drop",
6946 G_CALLBACK(compose_drag_drop),
6948 g_signal_connect(G_OBJECT(entry), "populate-popup",
6949 G_CALLBACK(compose_entry_popup_extend),
6952 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6954 headerentry->compose = compose;
6955 headerentry->combo = combo;
6956 headerentry->entry = entry;
6957 headerentry->button = button;
6958 headerentry->hbox = hbox;
6959 headerentry->headernum = compose->header_nextrow;
6960 headerentry->type = PREF_NONE;
6962 compose->header_nextrow++;
6963 compose->header_last = headerentry;
6964 compose->header_list =
6965 g_slist_append(compose->header_list,
6969 static void compose_add_header_entry(Compose *compose, const gchar *header,
6970 gchar *text, ComposePrefType pref_type)
6972 ComposeHeaderEntry *last_header = compose->header_last;
6973 gchar *tmp = g_strdup(text), *email;
6974 gboolean replyto_hdr;
6976 replyto_hdr = (!strcasecmp(header,
6977 prefs_common_translated_header_name("Reply-To:")) ||
6979 prefs_common_translated_header_name("Followup-To:")) ||
6981 prefs_common_translated_header_name("In-Reply-To:")));
6983 extract_address(tmp);
6984 email = g_utf8_strdown(tmp, -1);
6986 if (replyto_hdr == FALSE &&
6987 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6989 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6990 header, text, (gint) pref_type);
6996 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6997 gtk_entry_set_text(GTK_ENTRY(
6998 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7000 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7001 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7002 last_header->type = pref_type;
7004 if (replyto_hdr == FALSE)
7005 g_hash_table_insert(compose->email_hashtable, email,
7006 GUINT_TO_POINTER(1));
7013 static void compose_destroy_headerentry(Compose *compose,
7014 ComposeHeaderEntry *headerentry)
7016 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7019 extract_address(text);
7020 email = g_utf8_strdown(text, -1);
7021 g_hash_table_remove(compose->email_hashtable, email);
7025 gtk_widget_destroy(headerentry->combo);
7026 gtk_widget_destroy(headerentry->entry);
7027 gtk_widget_destroy(headerentry->button);
7028 gtk_widget_destroy(headerentry->hbox);
7029 g_free(headerentry);
7032 static void compose_remove_header_entries(Compose *compose)
7035 for (list = compose->header_list; list; list = list->next)
7036 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7038 compose->header_last = NULL;
7039 g_slist_free(compose->header_list);
7040 compose->header_list = NULL;
7041 compose->header_nextrow = 1;
7042 compose_create_header_entry(compose);
7045 static GtkWidget *compose_create_header(Compose *compose)
7047 GtkWidget *from_optmenu_hbox;
7048 GtkWidget *header_scrolledwin_main;
7049 GtkWidget *header_table_main;
7050 GtkWidget *header_scrolledwin;
7051 GtkWidget *header_table;
7053 /* parent with account selection and from header */
7054 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7055 gtk_widget_show(header_scrolledwin_main);
7056 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7058 header_table_main = gtk_table_new(2, 2, FALSE);
7059 gtk_widget_show(header_table_main);
7060 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7061 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7062 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7064 from_optmenu_hbox = compose_account_option_menu_create(compose);
7065 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7066 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7068 /* child with header labels and entries */
7069 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7070 gtk_widget_show(header_scrolledwin);
7071 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7073 header_table = gtk_table_new(2, 2, FALSE);
7074 gtk_widget_show(header_table);
7075 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7076 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7077 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7079 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7080 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7082 compose->header_table = header_table;
7083 compose->header_list = NULL;
7084 compose->header_nextrow = 0;
7086 compose_create_header_entry(compose);
7088 compose->table = NULL;
7090 return header_scrolledwin_main;
7093 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7095 Compose *compose = (Compose *)data;
7096 GdkEventButton event;
7099 event.time = gtk_get_current_event_time();
7101 return attach_button_pressed(compose->attach_clist, &event, compose);
7104 static GtkWidget *compose_create_attach(Compose *compose)
7106 GtkWidget *attach_scrwin;
7107 GtkWidget *attach_clist;
7109 GtkListStore *store;
7110 GtkCellRenderer *renderer;
7111 GtkTreeViewColumn *column;
7112 GtkTreeSelection *selection;
7114 /* attachment list */
7115 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7116 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7117 GTK_POLICY_AUTOMATIC,
7118 GTK_POLICY_AUTOMATIC);
7119 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7121 store = gtk_list_store_new(N_ATTACH_COLS,
7127 G_TYPE_AUTO_POINTER,
7129 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7130 (GTK_TREE_MODEL(store)));
7131 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7132 g_object_unref(store);
7134 renderer = gtk_cell_renderer_text_new();
7135 column = gtk_tree_view_column_new_with_attributes
7136 (_("Mime type"), renderer, "text",
7137 COL_MIMETYPE, NULL);
7138 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7140 renderer = gtk_cell_renderer_text_new();
7141 column = gtk_tree_view_column_new_with_attributes
7142 (_("Size"), renderer, "text",
7144 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7146 renderer = gtk_cell_renderer_text_new();
7147 column = gtk_tree_view_column_new_with_attributes
7148 (_("Name"), renderer, "text",
7150 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7152 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7153 prefs_common.use_stripes_everywhere);
7154 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7155 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7157 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7158 G_CALLBACK(attach_selected), compose);
7159 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7160 G_CALLBACK(attach_button_pressed), compose);
7161 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7162 G_CALLBACK(popup_attach_button_pressed), compose);
7163 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7164 G_CALLBACK(attach_key_pressed), compose);
7167 gtk_drag_dest_set(attach_clist,
7168 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7169 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7170 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7171 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7172 G_CALLBACK(compose_attach_drag_received_cb),
7174 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7175 G_CALLBACK(compose_drag_drop),
7178 compose->attach_scrwin = attach_scrwin;
7179 compose->attach_clist = attach_clist;
7181 return attach_scrwin;
7184 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7185 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7187 static GtkWidget *compose_create_others(Compose *compose)
7190 GtkWidget *savemsg_checkbtn;
7191 GtkWidget *savemsg_combo;
7192 GtkWidget *savemsg_select;
7195 gchar *folderidentifier;
7197 /* Table for settings */
7198 table = gtk_table_new(3, 1, FALSE);
7199 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7200 gtk_widget_show(table);
7201 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7204 /* Save Message to folder */
7205 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7206 gtk_widget_show(savemsg_checkbtn);
7207 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7208 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7209 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7211 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7212 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7214 #if !GTK_CHECK_VERSION(2, 24, 0)
7215 savemsg_combo = gtk_combo_box_entry_new_text();
7217 savemsg_combo = gtk_combo_box_text_new_with_entry();
7219 compose->savemsg_checkbtn = savemsg_checkbtn;
7220 compose->savemsg_combo = savemsg_combo;
7221 gtk_widget_show(savemsg_combo);
7223 if (prefs_common.compose_save_to_history)
7224 #if !GTK_CHECK_VERSION(2, 24, 0)
7225 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7226 prefs_common.compose_save_to_history);
7228 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7229 prefs_common.compose_save_to_history);
7231 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7232 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7233 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7234 G_CALLBACK(compose_grab_focus_cb), compose);
7235 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7236 folderidentifier = folder_item_get_identifier(account_get_special_folder
7237 (compose->account, F_OUTBOX));
7238 compose_set_save_to(compose, folderidentifier);
7239 g_free(folderidentifier);
7242 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7243 gtk_widget_show(savemsg_select);
7244 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7245 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7246 G_CALLBACK(compose_savemsg_select_cb),
7252 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7254 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7255 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7258 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7263 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7266 path = folder_item_get_identifier(dest);
7268 compose_set_save_to(compose, path);
7272 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7273 GdkAtom clip, GtkTextIter *insert_place);
7276 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7280 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7282 if (event->button == 3) {
7284 GtkTextIter sel_start, sel_end;
7285 gboolean stuff_selected;
7287 /* move the cursor to allow GtkAspell to check the word
7288 * under the mouse */
7289 if (event->x && event->y) {
7290 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7291 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7293 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7296 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7297 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7300 stuff_selected = gtk_text_buffer_get_selection_bounds(
7302 &sel_start, &sel_end);
7304 gtk_text_buffer_place_cursor (buffer, &iter);
7305 /* reselect stuff */
7307 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7308 gtk_text_buffer_select_range(buffer,
7309 &sel_start, &sel_end);
7311 return FALSE; /* pass the event so that the right-click goes through */
7314 if (event->button == 2) {
7319 /* get the middle-click position to paste at the correct place */
7320 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7321 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7323 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7326 entry_paste_clipboard(compose, text,
7327 prefs_common.linewrap_pastes,
7328 GDK_SELECTION_PRIMARY, &iter);
7336 static void compose_spell_menu_changed(void *data)
7338 Compose *compose = (Compose *)data;
7340 GtkWidget *menuitem;
7341 GtkWidget *parent_item;
7342 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7345 if (compose->gtkaspell == NULL)
7348 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7349 "/Menu/Spelling/Options");
7351 /* setting the submenu removes /Spelling/Options from the factory
7352 * so we need to save it */
7354 if (parent_item == NULL) {
7355 parent_item = compose->aspell_options_menu;
7356 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7358 compose->aspell_options_menu = parent_item;
7360 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7362 spell_menu = g_slist_reverse(spell_menu);
7363 for (items = spell_menu;
7364 items; items = items->next) {
7365 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7366 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7367 gtk_widget_show(GTK_WIDGET(menuitem));
7369 g_slist_free(spell_menu);
7371 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7372 gtk_widget_show(parent_item);
7375 static void compose_dict_changed(void *data)
7377 Compose *compose = (Compose *) data;
7379 if(!compose->gtkaspell)
7381 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7384 gtkaspell_highlight_all(compose->gtkaspell);
7385 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7389 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7391 Compose *compose = (Compose *)data;
7392 GdkEventButton event;
7395 event.time = gtk_get_current_event_time();
7399 return text_clicked(compose->text, &event, compose);
7402 static gboolean compose_force_window_origin = TRUE;
7403 static Compose *compose_create(PrefsAccount *account,
7412 GtkWidget *handlebox;
7414 GtkWidget *notebook;
7416 GtkWidget *attach_hbox;
7417 GtkWidget *attach_lab1;
7418 GtkWidget *attach_lab2;
7423 GtkWidget *subject_hbox;
7424 GtkWidget *subject_frame;
7425 GtkWidget *subject_entry;
7429 GtkWidget *edit_vbox;
7430 GtkWidget *ruler_hbox;
7432 GtkWidget *scrolledwin;
7434 GtkTextBuffer *buffer;
7435 GtkClipboard *clipboard;
7437 UndoMain *undostruct;
7439 GtkWidget *popupmenu;
7440 GtkWidget *tmpl_menu;
7441 GtkActionGroup *action_group = NULL;
7444 GtkAspell * gtkaspell = NULL;
7447 static GdkGeometry geometry;
7449 cm_return_val_if_fail(account != NULL, NULL);
7451 debug_print("Creating compose window...\n");
7452 compose = g_new0(Compose, 1);
7454 compose->batch = batch;
7455 compose->account = account;
7456 compose->folder = folder;
7458 compose->mutex = cm_mutex_new();
7459 compose->set_cursor_pos = -1;
7461 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7463 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7464 gtk_widget_set_size_request(window, prefs_common.compose_width,
7465 prefs_common.compose_height);
7467 if (!geometry.max_width) {
7468 geometry.max_width = gdk_screen_width();
7469 geometry.max_height = gdk_screen_height();
7472 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7473 &geometry, GDK_HINT_MAX_SIZE);
7474 if (!geometry.min_width) {
7475 geometry.min_width = 600;
7476 geometry.min_height = 440;
7478 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7479 &geometry, GDK_HINT_MIN_SIZE);
7481 #ifndef GENERIC_UMPC
7482 if (compose_force_window_origin)
7483 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7484 prefs_common.compose_y);
7486 g_signal_connect(G_OBJECT(window), "delete_event",
7487 G_CALLBACK(compose_delete_cb), compose);
7488 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7489 gtk_widget_realize(window);
7491 gtkut_widget_set_composer_icon(window);
7493 vbox = gtk_vbox_new(FALSE, 0);
7494 gtk_container_add(GTK_CONTAINER(window), vbox);
7496 compose->ui_manager = gtk_ui_manager_new();
7497 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7498 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7499 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7500 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7501 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7502 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7503 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7504 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7505 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7506 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7625 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)
7626 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)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7632 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)
7633 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)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7638 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)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7642 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)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7648 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)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7654 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)
7655 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)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7668 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)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7686 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7687 gtk_widget_show_all(menubar);
7689 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7690 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7692 if (prefs_common.toolbar_detachable) {
7693 handlebox = gtk_handle_box_new();
7695 handlebox = gtk_hbox_new(FALSE, 0);
7697 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7699 gtk_widget_realize(handlebox);
7700 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7703 vbox2 = gtk_vbox_new(FALSE, 2);
7704 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7705 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7708 notebook = gtk_notebook_new();
7709 gtk_widget_show(notebook);
7711 /* header labels and entries */
7712 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7713 compose_create_header(compose),
7714 gtk_label_new_with_mnemonic(_("Hea_der")));
7715 /* attachment list */
7716 attach_hbox = gtk_hbox_new(FALSE, 0);
7717 gtk_widget_show(attach_hbox);
7719 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7720 gtk_widget_show(attach_lab1);
7721 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7723 attach_lab2 = gtk_label_new("");
7724 gtk_widget_show(attach_lab2);
7725 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7727 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7728 compose_create_attach(compose),
7731 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7732 compose_create_others(compose),
7733 gtk_label_new_with_mnemonic(_("Othe_rs")));
7736 subject_hbox = gtk_hbox_new(FALSE, 0);
7737 gtk_widget_show(subject_hbox);
7739 subject_frame = gtk_frame_new(NULL);
7740 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7741 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7742 gtk_widget_show(subject_frame);
7744 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7745 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7746 gtk_widget_show(subject);
7748 label = gtk_label_new_with_mnemonic(_("_Subject:"));
7749 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7750 gtk_widget_show(label);
7753 subject_entry = claws_spell_entry_new();
7755 subject_entry = gtk_entry_new();
7757 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7758 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7759 G_CALLBACK(compose_grab_focus_cb), compose);
7760 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7761 gtk_widget_show(subject_entry);
7762 compose->subject_entry = subject_entry;
7763 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7765 edit_vbox = gtk_vbox_new(FALSE, 0);
7767 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7770 ruler_hbox = gtk_hbox_new(FALSE, 0);
7771 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7773 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7774 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7775 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7779 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7780 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7781 GTK_POLICY_AUTOMATIC,
7782 GTK_POLICY_AUTOMATIC);
7783 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7785 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7787 text = gtk_text_view_new();
7788 if (prefs_common.show_compose_margin) {
7789 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7790 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7792 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7793 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7794 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7795 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7796 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7798 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7799 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7800 G_CALLBACK(compose_edit_size_alloc),
7802 g_signal_connect(G_OBJECT(buffer), "changed",
7803 G_CALLBACK(compose_changed_cb), compose);
7804 g_signal_connect(G_OBJECT(text), "grab_focus",
7805 G_CALLBACK(compose_grab_focus_cb), compose);
7806 g_signal_connect(G_OBJECT(buffer), "insert_text",
7807 G_CALLBACK(text_inserted), compose);
7808 g_signal_connect(G_OBJECT(text), "button_press_event",
7809 G_CALLBACK(text_clicked), compose);
7810 g_signal_connect(G_OBJECT(text), "popup-menu",
7811 G_CALLBACK(compose_popup_menu), compose);
7812 g_signal_connect(G_OBJECT(subject_entry), "changed",
7813 G_CALLBACK(compose_changed_cb), compose);
7814 g_signal_connect(G_OBJECT(subject_entry), "activate",
7815 G_CALLBACK(compose_subject_entry_activated), compose);
7818 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7819 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7820 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7821 g_signal_connect(G_OBJECT(text), "drag_data_received",
7822 G_CALLBACK(compose_insert_drag_received_cb),
7824 g_signal_connect(G_OBJECT(text), "drag-drop",
7825 G_CALLBACK(compose_drag_drop),
7827 g_signal_connect(G_OBJECT(text), "key-press-event",
7828 G_CALLBACK(completion_set_focus_to_subject),
7830 gtk_widget_show_all(vbox);
7832 /* pane between attach clist and text */
7833 paned = gtk_vpaned_new();
7834 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7835 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7836 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7837 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7838 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7839 G_CALLBACK(compose_notebook_size_alloc), paned);
7841 gtk_widget_show_all(paned);
7844 if (prefs_common.textfont) {
7845 PangoFontDescription *font_desc;
7847 font_desc = pango_font_description_from_string
7848 (prefs_common.textfont);
7850 gtk_widget_modify_font(text, font_desc);
7851 pango_font_description_free(font_desc);
7855 gtk_action_group_add_actions(action_group, compose_popup_entries,
7856 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7864 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7866 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7867 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7868 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7870 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7872 undostruct = undo_init(text);
7873 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7876 address_completion_start(window);
7878 compose->window = window;
7879 compose->vbox = vbox;
7880 compose->menubar = menubar;
7881 compose->handlebox = handlebox;
7883 compose->vbox2 = vbox2;
7885 compose->paned = paned;
7887 compose->attach_label = attach_lab2;
7889 compose->notebook = notebook;
7890 compose->edit_vbox = edit_vbox;
7891 compose->ruler_hbox = ruler_hbox;
7892 compose->ruler = ruler;
7893 compose->scrolledwin = scrolledwin;
7894 compose->text = text;
7896 compose->focused_editable = NULL;
7898 compose->popupmenu = popupmenu;
7900 compose->tmpl_menu = tmpl_menu;
7902 compose->mode = mode;
7903 compose->rmode = mode;
7905 compose->targetinfo = NULL;
7906 compose->replyinfo = NULL;
7907 compose->fwdinfo = NULL;
7909 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7910 g_str_equal, (GDestroyNotify) g_free, NULL);
7912 compose->replyto = NULL;
7914 compose->bcc = NULL;
7915 compose->followup_to = NULL;
7917 compose->ml_post = NULL;
7919 compose->inreplyto = NULL;
7920 compose->references = NULL;
7921 compose->msgid = NULL;
7922 compose->boundary = NULL;
7924 compose->autowrap = prefs_common.autowrap;
7925 compose->autoindent = prefs_common.auto_indent;
7926 compose->use_signing = FALSE;
7927 compose->use_encryption = FALSE;
7928 compose->privacy_system = NULL;
7930 compose->modified = FALSE;
7932 compose->return_receipt = FALSE;
7934 compose->to_list = NULL;
7935 compose->newsgroup_list = NULL;
7937 compose->undostruct = undostruct;
7939 compose->sig_str = NULL;
7941 compose->exteditor_file = NULL;
7942 compose->exteditor_pid = -1;
7943 compose->exteditor_tag = -1;
7944 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7946 compose->folder_update_callback_id =
7947 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
7948 compose_update_folder_hook,
7949 (gpointer) compose);
7952 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7953 if (mode != COMPOSE_REDIRECT) {
7954 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7955 strcmp(prefs_common.dictionary, "")) {
7956 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7957 prefs_common.alt_dictionary,
7958 conv_get_locale_charset_str(),
7959 prefs_common.misspelled_col,
7960 prefs_common.check_while_typing,
7961 prefs_common.recheck_when_changing_dict,
7962 prefs_common.use_alternate,
7963 prefs_common.use_both_dicts,
7964 GTK_TEXT_VIEW(text),
7965 GTK_WINDOW(compose->window),
7966 compose_dict_changed,
7967 compose_spell_menu_changed,
7970 alertpanel_error(_("Spell checker could not "
7972 gtkaspell_checkers_strerror());
7973 gtkaspell_checkers_reset_error();
7975 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7979 compose->gtkaspell = gtkaspell;
7980 compose_spell_menu_changed(compose);
7981 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7984 compose_select_account(compose, account, TRUE);
7986 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7987 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7989 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7990 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7992 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7993 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7995 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7996 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7998 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7999 if (account->protocol != A_NNTP)
8000 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8001 prefs_common_translated_header_name("To:"));
8003 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8004 prefs_common_translated_header_name("Newsgroups:"));
8006 #ifndef USE_NEW_ADDRBOOK
8007 addressbook_set_target_compose(compose);
8009 if (mode != COMPOSE_REDIRECT)
8010 compose_set_template_menu(compose);
8012 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8015 compose_list = g_list_append(compose_list, compose);
8017 if (!prefs_common.show_ruler)
8018 gtk_widget_hide(ruler_hbox);
8020 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8023 compose->priority = PRIORITY_NORMAL;
8024 compose_update_priority_menu_item(compose);
8026 compose_set_out_encoding(compose);
8029 compose_update_actions_menu(compose);
8031 /* Privacy Systems menu */
8032 compose_update_privacy_systems_menu(compose);
8034 activate_privacy_system(compose, account, TRUE);
8035 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8037 gtk_widget_realize(window);
8039 gtk_widget_show(window);
8045 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8050 GtkWidget *optmenubox;
8053 GtkWidget *from_name = NULL;
8055 gint num = 0, def_menu = 0;
8057 accounts = account_get_list();
8058 cm_return_val_if_fail(accounts != NULL, NULL);
8060 optmenubox = gtk_event_box_new();
8061 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8062 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8064 hbox = gtk_hbox_new(FALSE, 6);
8065 from_name = gtk_entry_new();
8067 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8068 G_CALLBACK(compose_grab_focus_cb), compose);
8069 g_signal_connect_after(G_OBJECT(from_name), "activate",
8070 G_CALLBACK(from_name_activate_cb), optmenu);
8072 for (; accounts != NULL; accounts = accounts->next, num++) {
8073 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8074 gchar *name, *from = NULL;
8076 if (ac == compose->account) def_menu = num;
8078 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8081 if (ac == compose->account) {
8082 if (ac->name && *ac->name) {
8084 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8085 from = g_strdup_printf("%s <%s>",
8087 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8089 from = g_strdup_printf("%s",
8091 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8094 COMBOBOX_ADD(menu, name, ac->account_id);
8099 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8101 g_signal_connect(G_OBJECT(optmenu), "changed",
8102 G_CALLBACK(account_activated),
8104 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8105 G_CALLBACK(compose_entry_popup_extend),
8108 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8109 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8111 /* Putting only the GtkEntry into focus chain of parent hbox causes
8112 * the account selector combobox next to it to be unreachable when
8113 * navigating widgets in GtkTable with up/down arrow keys.
8114 * Note: gtk_widget_set_can_focus() was not enough. */
8116 l = g_list_prepend(l, from_name);
8117 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8120 CLAWS_SET_TIP(optmenubox,
8121 _("Account to use for this email"));
8122 CLAWS_SET_TIP(from_name,
8123 _("Sender address to be used"));
8125 compose->account_combo = optmenu;
8126 compose->from_name = from_name;
8131 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8133 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8134 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8135 Compose *compose = (Compose *) data;
8137 compose->priority = value;
8141 static void compose_reply_change_mode(Compose *compose,
8144 gboolean was_modified = compose->modified;
8146 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8148 cm_return_if_fail(compose->replyinfo != NULL);
8150 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8152 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8154 if (action == COMPOSE_REPLY_TO_ALL)
8156 if (action == COMPOSE_REPLY_TO_SENDER)
8158 if (action == COMPOSE_REPLY_TO_LIST)
8161 compose_remove_header_entries(compose);
8162 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8163 if (compose->account->set_autocc && compose->account->auto_cc)
8164 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8166 if (compose->account->set_autobcc && compose->account->auto_bcc)
8167 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8169 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8170 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8171 compose_show_first_last_header(compose, TRUE);
8172 compose->modified = was_modified;
8173 compose_set_title(compose);
8176 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8178 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8179 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8180 Compose *compose = (Compose *) data;
8183 compose_reply_change_mode(compose, value);
8186 static void compose_update_priority_menu_item(Compose * compose)
8188 GtkWidget *menuitem = NULL;
8189 switch (compose->priority) {
8190 case PRIORITY_HIGHEST:
8191 menuitem = gtk_ui_manager_get_widget
8192 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8195 menuitem = gtk_ui_manager_get_widget
8196 (compose->ui_manager, "/Menu/Options/Priority/High");
8198 case PRIORITY_NORMAL:
8199 menuitem = gtk_ui_manager_get_widget
8200 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8203 menuitem = gtk_ui_manager_get_widget
8204 (compose->ui_manager, "/Menu/Options/Priority/Low");
8206 case PRIORITY_LOWEST:
8207 menuitem = gtk_ui_manager_get_widget
8208 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8211 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8214 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8216 Compose *compose = (Compose *) data;
8218 gboolean can_sign = FALSE, can_encrypt = FALSE;
8220 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8222 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8225 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8226 g_free(compose->privacy_system);
8227 compose->privacy_system = NULL;
8228 if (systemid != NULL) {
8229 compose->privacy_system = g_strdup(systemid);
8231 can_sign = privacy_system_can_sign(systemid);
8232 can_encrypt = privacy_system_can_encrypt(systemid);
8235 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8237 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8238 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8241 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8243 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8244 GtkWidget *menuitem = NULL;
8245 GList *children, *amenu;
8246 gboolean can_sign = FALSE, can_encrypt = FALSE;
8247 gboolean found = FALSE;
8249 if (compose->privacy_system != NULL) {
8251 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8252 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8253 cm_return_if_fail(menuitem != NULL);
8255 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8258 while (amenu != NULL) {
8259 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8260 if (systemid != NULL) {
8261 if (strcmp(systemid, compose->privacy_system) == 0 &&
8262 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8263 menuitem = GTK_WIDGET(amenu->data);
8265 can_sign = privacy_system_can_sign(systemid);
8266 can_encrypt = privacy_system_can_encrypt(systemid);
8270 } else if (strlen(compose->privacy_system) == 0 &&
8271 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8272 menuitem = GTK_WIDGET(amenu->data);
8275 can_encrypt = FALSE;
8280 amenu = amenu->next;
8282 g_list_free(children);
8283 if (menuitem != NULL)
8284 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8286 if (warn && !found && strlen(compose->privacy_system)) {
8287 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8288 "will not be able to sign or encrypt this message."),
8289 compose->privacy_system);
8293 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8294 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8297 static void compose_set_out_encoding(Compose *compose)
8299 CharSet out_encoding;
8300 const gchar *branch = NULL;
8301 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8303 switch(out_encoding) {
8304 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8305 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8306 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8307 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8308 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8309 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8310 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8311 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8312 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8313 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8314 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8315 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8316 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8317 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8318 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8319 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8320 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8321 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8322 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8323 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8324 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8325 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8326 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8327 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8328 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8329 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8330 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8331 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8332 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8333 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8334 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8335 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8336 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8338 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8341 static void compose_set_template_menu(Compose *compose)
8343 GSList *tmpl_list, *cur;
8347 tmpl_list = template_get_config();
8349 menu = gtk_menu_new();
8351 gtk_menu_set_accel_group (GTK_MENU (menu),
8352 gtk_ui_manager_get_accel_group(compose->ui_manager));
8353 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8354 Template *tmpl = (Template *)cur->data;
8355 gchar *accel_path = NULL;
8356 item = gtk_menu_item_new_with_label(tmpl->name);
8357 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8358 g_signal_connect(G_OBJECT(item), "activate",
8359 G_CALLBACK(compose_template_activate_cb),
8361 g_object_set_data(G_OBJECT(item), "template", tmpl);
8362 gtk_widget_show(item);
8363 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8364 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8368 gtk_widget_show(menu);
8369 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8372 void compose_update_actions_menu(Compose *compose)
8374 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8377 static void compose_update_privacy_systems_menu(Compose *compose)
8379 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8380 GSList *systems, *cur;
8382 GtkWidget *system_none;
8384 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8385 GtkWidget *privacy_menu = gtk_menu_new();
8387 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8388 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8390 g_signal_connect(G_OBJECT(system_none), "activate",
8391 G_CALLBACK(compose_set_privacy_system_cb), compose);
8393 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8394 gtk_widget_show(system_none);
8396 systems = privacy_get_system_ids();
8397 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8398 gchar *systemid = cur->data;
8400 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8401 widget = gtk_radio_menu_item_new_with_label(group,
8402 privacy_system_get_name(systemid));
8403 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8404 g_strdup(systemid), g_free);
8405 g_signal_connect(G_OBJECT(widget), "activate",
8406 G_CALLBACK(compose_set_privacy_system_cb), compose);
8408 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8409 gtk_widget_show(widget);
8412 g_slist_free(systems);
8413 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8414 gtk_widget_show_all(privacy_menu);
8415 gtk_widget_show_all(privacy_menuitem);
8418 void compose_reflect_prefs_all(void)
8423 for (cur = compose_list; cur != NULL; cur = cur->next) {
8424 compose = (Compose *)cur->data;
8425 compose_set_template_menu(compose);
8429 void compose_reflect_prefs_pixmap_theme(void)
8434 for (cur = compose_list; cur != NULL; cur = cur->next) {
8435 compose = (Compose *)cur->data;
8436 toolbar_update(TOOLBAR_COMPOSE, compose);
8440 static const gchar *compose_quote_char_from_context(Compose *compose)
8442 const gchar *qmark = NULL;
8444 cm_return_val_if_fail(compose != NULL, NULL);
8446 switch (compose->mode) {
8447 /* use forward-specific quote char */
8448 case COMPOSE_FORWARD:
8449 case COMPOSE_FORWARD_AS_ATTACH:
8450 case COMPOSE_FORWARD_INLINE:
8451 if (compose->folder && compose->folder->prefs &&
8452 compose->folder->prefs->forward_with_format)
8453 qmark = compose->folder->prefs->forward_quotemark;
8454 else if (compose->account->forward_with_format)
8455 qmark = compose->account->forward_quotemark;
8457 qmark = prefs_common.fw_quotemark;
8460 /* use reply-specific quote char in all other modes */
8462 if (compose->folder && compose->folder->prefs &&
8463 compose->folder->prefs->reply_with_format)
8464 qmark = compose->folder->prefs->reply_quotemark;
8465 else if (compose->account->reply_with_format)
8466 qmark = compose->account->reply_quotemark;
8468 qmark = prefs_common.quotemark;
8472 if (qmark == NULL || *qmark == '\0')
8478 static void compose_template_apply(Compose *compose, Template *tmpl,
8482 GtkTextBuffer *buffer;
8486 gchar *parsed_str = NULL;
8487 gint cursor_pos = 0;
8488 const gchar *err_msg = _("The body of the template has an error at line %d.");
8491 /* process the body */
8493 text = GTK_TEXT_VIEW(compose->text);
8494 buffer = gtk_text_view_get_buffer(text);
8497 qmark = compose_quote_char_from_context(compose);
8499 if (compose->replyinfo != NULL) {
8502 gtk_text_buffer_set_text(buffer, "", -1);
8503 mark = gtk_text_buffer_get_insert(buffer);
8504 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8506 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8507 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8509 } else if (compose->fwdinfo != NULL) {
8512 gtk_text_buffer_set_text(buffer, "", -1);
8513 mark = gtk_text_buffer_get_insert(buffer);
8514 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8516 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8517 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8520 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8522 GtkTextIter start, end;
8525 gtk_text_buffer_get_start_iter(buffer, &start);
8526 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8527 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8529 /* clear the buffer now */
8531 gtk_text_buffer_set_text(buffer, "", -1);
8533 parsed_str = compose_quote_fmt(compose, dummyinfo,
8534 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8535 procmsg_msginfo_free( dummyinfo );
8541 gtk_text_buffer_set_text(buffer, "", -1);
8542 mark = gtk_text_buffer_get_insert(buffer);
8543 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8546 if (replace && parsed_str && compose->account->auto_sig)
8547 compose_insert_sig(compose, FALSE);
8549 if (replace && parsed_str) {
8550 gtk_text_buffer_get_start_iter(buffer, &iter);
8551 gtk_text_buffer_place_cursor(buffer, &iter);
8555 cursor_pos = quote_fmt_get_cursor_pos();
8556 compose->set_cursor_pos = cursor_pos;
8557 if (cursor_pos == -1)
8559 gtk_text_buffer_get_start_iter(buffer, &iter);
8560 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8561 gtk_text_buffer_place_cursor(buffer, &iter);
8564 /* process the other fields */
8566 compose_template_apply_fields(compose, tmpl);
8567 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8568 quote_fmt_reset_vartable();
8569 compose_changed_cb(NULL, compose);
8572 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8573 gtkaspell_highlight_all(compose->gtkaspell);
8577 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8579 MsgInfo* dummyinfo = NULL;
8580 MsgInfo *msginfo = NULL;
8583 if (compose->replyinfo != NULL)
8584 msginfo = compose->replyinfo;
8585 else if (compose->fwdinfo != NULL)
8586 msginfo = compose->fwdinfo;
8588 dummyinfo = compose_msginfo_new_from_compose(compose);
8589 msginfo = dummyinfo;
8592 if (tmpl->from && *tmpl->from != '\0') {
8594 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8595 compose->gtkaspell);
8597 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8599 quote_fmt_scan_string(tmpl->from);
8602 buf = quote_fmt_get_buffer();
8604 alertpanel_error(_("Template From format error."));
8606 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8610 if (tmpl->to && *tmpl->to != '\0') {
8612 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8613 compose->gtkaspell);
8615 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8617 quote_fmt_scan_string(tmpl->to);
8620 buf = quote_fmt_get_buffer();
8622 alertpanel_error(_("Template To format error."));
8624 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8628 if (tmpl->cc && *tmpl->cc != '\0') {
8630 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8631 compose->gtkaspell);
8633 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8635 quote_fmt_scan_string(tmpl->cc);
8638 buf = quote_fmt_get_buffer();
8640 alertpanel_error(_("Template Cc format error."));
8642 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8646 if (tmpl->bcc && *tmpl->bcc != '\0') {
8648 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8649 compose->gtkaspell);
8651 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8653 quote_fmt_scan_string(tmpl->bcc);
8656 buf = quote_fmt_get_buffer();
8658 alertpanel_error(_("Template Bcc format error."));
8660 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8664 if (tmpl->replyto && *tmpl->replyto != '\0') {
8666 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8667 compose->gtkaspell);
8669 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8671 quote_fmt_scan_string(tmpl->replyto);
8674 buf = quote_fmt_get_buffer();
8676 alertpanel_error(_("Template Reply-To format error."));
8678 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8682 /* process the subject */
8683 if (tmpl->subject && *tmpl->subject != '\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->subject);
8693 buf = quote_fmt_get_buffer();
8695 alertpanel_error(_("Template subject format error."));
8697 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8701 procmsg_msginfo_free( dummyinfo );
8704 static void compose_destroy(Compose *compose)
8706 GtkAllocation allocation;
8707 GtkTextBuffer *buffer;
8708 GtkClipboard *clipboard;
8710 compose_list = g_list_remove(compose_list, compose);
8712 if (compose->updating) {
8713 debug_print("danger, not destroying anything now\n");
8714 compose->deferred_destroy = TRUE;
8718 /* NOTE: address_completion_end() does nothing with the window
8719 * however this may change. */
8720 address_completion_end(compose->window);
8722 slist_free_strings_full(compose->to_list);
8723 slist_free_strings_full(compose->newsgroup_list);
8724 slist_free_strings_full(compose->header_list);
8726 slist_free_strings_full(extra_headers);
8727 extra_headers = NULL;
8729 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8731 g_hash_table_destroy(compose->email_hashtable);
8733 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8734 compose->folder_update_callback_id);
8736 procmsg_msginfo_free(compose->targetinfo);
8737 procmsg_msginfo_free(compose->replyinfo);
8738 procmsg_msginfo_free(compose->fwdinfo);
8740 g_free(compose->replyto);
8741 g_free(compose->cc);
8742 g_free(compose->bcc);
8743 g_free(compose->newsgroups);
8744 g_free(compose->followup_to);
8746 g_free(compose->ml_post);
8748 g_free(compose->inreplyto);
8749 g_free(compose->references);
8750 g_free(compose->msgid);
8751 g_free(compose->boundary);
8753 g_free(compose->redirect_filename);
8754 if (compose->undostruct)
8755 undo_destroy(compose->undostruct);
8757 g_free(compose->sig_str);
8759 g_free(compose->exteditor_file);
8761 g_free(compose->orig_charset);
8763 g_free(compose->privacy_system);
8765 #ifndef USE_NEW_ADDRBOOK
8766 if (addressbook_get_target_compose() == compose)
8767 addressbook_set_target_compose(NULL);
8770 if (compose->gtkaspell) {
8771 gtkaspell_delete(compose->gtkaspell);
8772 compose->gtkaspell = NULL;
8776 if (!compose->batch) {
8777 gtk_widget_get_allocation(compose->window, &allocation);
8778 prefs_common.compose_width = allocation.width;
8779 prefs_common.compose_height = allocation.height;
8782 if (!gtk_widget_get_parent(compose->paned))
8783 gtk_widget_destroy(compose->paned);
8784 gtk_widget_destroy(compose->popupmenu);
8786 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8787 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8788 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8790 gtk_widget_destroy(compose->window);
8791 toolbar_destroy(compose->toolbar);
8792 g_free(compose->toolbar);
8793 cm_mutex_free(compose->mutex);
8797 static void compose_attach_info_free(AttachInfo *ainfo)
8799 g_free(ainfo->file);
8800 g_free(ainfo->content_type);
8801 g_free(ainfo->name);
8802 g_free(ainfo->charset);
8806 static void compose_attach_update_label(Compose *compose)
8811 GtkTreeModel *model;
8816 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8817 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8818 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8822 while(gtk_tree_model_iter_next(model, &iter))
8825 text = g_strdup_printf("(%d)", i);
8826 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8830 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8832 Compose *compose = (Compose *)data;
8833 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8834 GtkTreeSelection *selection;
8836 GtkTreeModel *model;
8838 selection = gtk_tree_view_get_selection(tree_view);
8839 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8844 for (cur = sel; cur != NULL; cur = cur->next) {
8845 GtkTreePath *path = cur->data;
8846 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8849 gtk_tree_path_free(path);
8852 for (cur = sel; cur != NULL; cur = cur->next) {
8853 GtkTreeRowReference *ref = cur->data;
8854 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8857 if (gtk_tree_model_get_iter(model, &iter, path))
8858 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8860 gtk_tree_path_free(path);
8861 gtk_tree_row_reference_free(ref);
8865 compose_attach_update_label(compose);
8868 static struct _AttachProperty
8871 GtkWidget *mimetype_entry;
8872 GtkWidget *encoding_optmenu;
8873 GtkWidget *path_entry;
8874 GtkWidget *filename_entry;
8876 GtkWidget *cancel_btn;
8879 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8881 gtk_tree_path_free((GtkTreePath *)ptr);
8884 static void compose_attach_property(GtkAction *action, gpointer data)
8886 Compose *compose = (Compose *)data;
8887 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8889 GtkComboBox *optmenu;
8890 GtkTreeSelection *selection;
8892 GtkTreeModel *model;
8895 static gboolean cancelled;
8897 /* only if one selected */
8898 selection = gtk_tree_view_get_selection(tree_view);
8899 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8902 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8906 path = (GtkTreePath *) sel->data;
8907 gtk_tree_model_get_iter(model, &iter, path);
8908 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8911 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8917 if (!attach_prop.window)
8918 compose_attach_property_create(&cancelled);
8919 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8920 gtk_widget_grab_focus(attach_prop.ok_btn);
8921 gtk_widget_show(attach_prop.window);
8922 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8923 GTK_WINDOW(compose->window));
8925 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8926 if (ainfo->encoding == ENC_UNKNOWN)
8927 combobox_select_by_data(optmenu, ENC_BASE64);
8929 combobox_select_by_data(optmenu, ainfo->encoding);
8931 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8932 ainfo->content_type ? ainfo->content_type : "");
8933 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8934 ainfo->file ? ainfo->file : "");
8935 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8936 ainfo->name ? ainfo->name : "");
8939 const gchar *entry_text;
8941 gchar *cnttype = NULL;
8948 gtk_widget_hide(attach_prop.window);
8949 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8954 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8955 if (*entry_text != '\0') {
8958 text = g_strstrip(g_strdup(entry_text));
8959 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8960 cnttype = g_strdup(text);
8963 alertpanel_error(_("Invalid MIME type."));
8969 ainfo->encoding = combobox_get_active_data(optmenu);
8971 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8972 if (*entry_text != '\0') {
8973 if (is_file_exist(entry_text) &&
8974 (size = get_file_size(entry_text)) > 0)
8975 file = g_strdup(entry_text);
8978 (_("File doesn't exist or is empty."));
8984 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8985 if (*entry_text != '\0') {
8986 g_free(ainfo->name);
8987 ainfo->name = g_strdup(entry_text);
8991 g_free(ainfo->content_type);
8992 ainfo->content_type = cnttype;
8995 g_free(ainfo->file);
8999 ainfo->size = (goffset)size;
9001 /* update tree store */
9002 text = to_human_readable(ainfo->size);
9003 gtk_tree_model_get_iter(model, &iter, path);
9004 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9005 COL_MIMETYPE, ainfo->content_type,
9007 COL_NAME, ainfo->name,
9008 COL_CHARSET, ainfo->charset,
9014 gtk_tree_path_free(path);
9017 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9019 label = gtk_label_new(str); \
9020 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9021 GTK_FILL, 0, 0, 0); \
9022 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9024 entry = gtk_entry_new(); \
9025 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9026 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9029 static void compose_attach_property_create(gboolean *cancelled)
9035 GtkWidget *mimetype_entry;
9038 GtkListStore *optmenu_menu;
9039 GtkWidget *path_entry;
9040 GtkWidget *filename_entry;
9043 GtkWidget *cancel_btn;
9044 GList *mime_type_list, *strlist;
9047 debug_print("Creating attach_property window...\n");
9049 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9050 gtk_widget_set_size_request(window, 480, -1);
9051 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9052 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9053 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9054 g_signal_connect(G_OBJECT(window), "delete_event",
9055 G_CALLBACK(attach_property_delete_event),
9057 g_signal_connect(G_OBJECT(window), "key_press_event",
9058 G_CALLBACK(attach_property_key_pressed),
9061 vbox = gtk_vbox_new(FALSE, 8);
9062 gtk_container_add(GTK_CONTAINER(window), vbox);
9064 table = gtk_table_new(4, 2, FALSE);
9065 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9066 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9067 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9069 label = gtk_label_new(_("MIME type"));
9070 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9072 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9073 #if !GTK_CHECK_VERSION(2, 24, 0)
9074 mimetype_entry = gtk_combo_box_entry_new_text();
9076 mimetype_entry = gtk_combo_box_text_new_with_entry();
9078 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9079 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9081 /* stuff with list */
9082 mime_type_list = procmime_get_mime_type_list();
9084 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9085 MimeType *type = (MimeType *) mime_type_list->data;
9088 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9090 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9093 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9094 (GCompareFunc)strcmp2);
9097 for (mime_type_list = strlist; mime_type_list != NULL;
9098 mime_type_list = mime_type_list->next) {
9099 #if !GTK_CHECK_VERSION(2, 24, 0)
9100 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9102 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9104 g_free(mime_type_list->data);
9106 g_list_free(strlist);
9107 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9108 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9110 label = gtk_label_new(_("Encoding"));
9111 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9113 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9115 hbox = gtk_hbox_new(FALSE, 0);
9116 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9117 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9119 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9120 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9122 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9123 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9124 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9125 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9126 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9128 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9130 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9131 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9133 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9134 &ok_btn, GTK_STOCK_OK,
9136 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9137 gtk_widget_grab_default(ok_btn);
9139 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9140 G_CALLBACK(attach_property_ok),
9142 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9143 G_CALLBACK(attach_property_cancel),
9146 gtk_widget_show_all(vbox);
9148 attach_prop.window = window;
9149 attach_prop.mimetype_entry = mimetype_entry;
9150 attach_prop.encoding_optmenu = optmenu;
9151 attach_prop.path_entry = path_entry;
9152 attach_prop.filename_entry = filename_entry;
9153 attach_prop.ok_btn = ok_btn;
9154 attach_prop.cancel_btn = cancel_btn;
9157 #undef SET_LABEL_AND_ENTRY
9159 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9165 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9171 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9172 gboolean *cancelled)
9180 static gboolean attach_property_key_pressed(GtkWidget *widget,
9182 gboolean *cancelled)
9184 if (event && event->keyval == GDK_KEY_Escape) {
9188 if (event && event->keyval == GDK_KEY_Return) {
9196 static void compose_exec_ext_editor(Compose *compose)
9203 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9204 G_DIR_SEPARATOR, compose);
9206 if (pipe(pipe_fds) < 0) {
9212 if ((pid = fork()) < 0) {
9219 /* close the write side of the pipe */
9222 compose->exteditor_file = g_strdup(tmp);
9223 compose->exteditor_pid = pid;
9225 compose_set_ext_editor_sensitive(compose, FALSE);
9228 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9230 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9232 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9236 } else { /* process-monitoring process */
9242 /* close the read side of the pipe */
9245 if (compose_write_body_to_file(compose, tmp) < 0) {
9246 fd_write_all(pipe_fds[1], "2\n", 2);
9250 pid_ed = compose_exec_ext_editor_real(tmp);
9252 fd_write_all(pipe_fds[1], "1\n", 2);
9256 /* wait until editor is terminated */
9257 waitpid(pid_ed, NULL, 0);
9259 fd_write_all(pipe_fds[1], "0\n", 2);
9266 #endif /* G_OS_UNIX */
9270 static gint compose_exec_ext_editor_real(const gchar *file)
9277 cm_return_val_if_fail(file != NULL, -1);
9279 if ((pid = fork()) < 0) {
9284 if (pid != 0) return pid;
9286 /* grandchild process */
9288 if (setpgid(0, getppid()))
9291 if (prefs_common_get_ext_editor_cmd() &&
9292 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9293 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9294 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9296 if (prefs_common_get_ext_editor_cmd())
9297 g_warning("External editor command-line is invalid: '%s'\n",
9298 prefs_common_get_ext_editor_cmd());
9299 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9302 cmdline = strsplit_with_quote(buf, " ", 1024);
9303 execvp(cmdline[0], cmdline);
9306 g_strfreev(cmdline);
9311 static gboolean compose_ext_editor_kill(Compose *compose)
9313 pid_t pgid = compose->exteditor_pid * -1;
9316 ret = kill(pgid, 0);
9318 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9322 msg = g_strdup_printf
9323 (_("The external editor is still working.\n"
9324 "Force terminating the process?\n"
9325 "process group id: %d"), -pgid);
9326 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9327 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9331 if (val == G_ALERTALTERNATE) {
9332 g_source_remove(compose->exteditor_tag);
9333 g_io_channel_shutdown(compose->exteditor_ch,
9335 g_io_channel_unref(compose->exteditor_ch);
9337 if (kill(pgid, SIGTERM) < 0) perror("kill");
9338 waitpid(compose->exteditor_pid, NULL, 0);
9340 g_warning("Terminated process group id: %d", -pgid);
9341 g_warning("Temporary file: %s",
9342 compose->exteditor_file);
9344 compose_set_ext_editor_sensitive(compose, TRUE);
9346 g_free(compose->exteditor_file);
9347 compose->exteditor_file = NULL;
9348 compose->exteditor_pid = -1;
9349 compose->exteditor_ch = NULL;
9350 compose->exteditor_tag = -1;
9358 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9362 Compose *compose = (Compose *)data;
9365 debug_print("Compose: input from monitoring process\n");
9367 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9369 g_io_channel_shutdown(source, FALSE, NULL);
9370 g_io_channel_unref(source);
9372 waitpid(compose->exteditor_pid, NULL, 0);
9374 if (buf[0] == '0') { /* success */
9375 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9376 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9378 gtk_text_buffer_set_text(buffer, "", -1);
9379 compose_insert_file(compose, compose->exteditor_file);
9380 compose_changed_cb(NULL, compose);
9381 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9383 if (claws_unlink(compose->exteditor_file) < 0)
9384 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9385 } else if (buf[0] == '1') { /* failed */
9386 g_warning("Couldn't exec external editor\n");
9387 if (claws_unlink(compose->exteditor_file) < 0)
9388 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9389 } else if (buf[0] == '2') {
9390 g_warning("Couldn't write to file\n");
9391 } else if (buf[0] == '3') {
9392 g_warning("Pipe read failed\n");
9395 compose_set_ext_editor_sensitive(compose, TRUE);
9397 g_free(compose->exteditor_file);
9398 compose->exteditor_file = NULL;
9399 compose->exteditor_pid = -1;
9400 compose->exteditor_ch = NULL;
9401 compose->exteditor_tag = -1;
9406 static void compose_set_ext_editor_sensitive(Compose *compose,
9409 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9410 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9411 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9412 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9413 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9414 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9415 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9416 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9418 gtk_widget_set_sensitive(compose->text, sensitive);
9419 if (compose->toolbar->send_btn)
9420 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9421 if (compose->toolbar->sendl_btn)
9422 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9423 if (compose->toolbar->draft_btn)
9424 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9425 if (compose->toolbar->insert_btn)
9426 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9427 if (compose->toolbar->sig_btn)
9428 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9429 if (compose->toolbar->exteditor_btn)
9430 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9431 if (compose->toolbar->linewrap_current_btn)
9432 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9433 if (compose->toolbar->linewrap_all_btn)
9434 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9436 #endif /* G_OS_UNIX */
9439 * compose_undo_state_changed:
9441 * Change the sensivity of the menuentries undo and redo
9443 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9444 gint redo_state, gpointer data)
9446 Compose *compose = (Compose *)data;
9448 switch (undo_state) {
9449 case UNDO_STATE_TRUE:
9450 if (!undostruct->undo_state) {
9451 undostruct->undo_state = TRUE;
9452 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9455 case UNDO_STATE_FALSE:
9456 if (undostruct->undo_state) {
9457 undostruct->undo_state = FALSE;
9458 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9461 case UNDO_STATE_UNCHANGED:
9463 case UNDO_STATE_REFRESH:
9464 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9467 g_warning("Undo state not recognized");
9471 switch (redo_state) {
9472 case UNDO_STATE_TRUE:
9473 if (!undostruct->redo_state) {
9474 undostruct->redo_state = TRUE;
9475 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9478 case UNDO_STATE_FALSE:
9479 if (undostruct->redo_state) {
9480 undostruct->redo_state = FALSE;
9481 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9484 case UNDO_STATE_UNCHANGED:
9486 case UNDO_STATE_REFRESH:
9487 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9490 g_warning("Redo state not recognized");
9495 /* callback functions */
9497 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9498 GtkAllocation *allocation,
9501 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9504 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9505 * includes "non-client" (windows-izm) in calculation, so this calculation
9506 * may not be accurate.
9508 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9509 GtkAllocation *allocation,
9510 GtkSHRuler *shruler)
9512 if (prefs_common.show_ruler) {
9513 gint char_width = 0, char_height = 0;
9514 gint line_width_in_chars;
9516 gtkut_get_font_size(GTK_WIDGET(widget),
9517 &char_width, &char_height);
9518 line_width_in_chars =
9519 (allocation->width - allocation->x) / char_width;
9521 /* got the maximum */
9522 gtk_shruler_set_range(GTK_SHRULER(shruler),
9523 0.0, line_width_in_chars, 0);
9532 ComposePrefType type;
9533 gboolean entry_marked;
9536 static void account_activated(GtkComboBox *optmenu, gpointer data)
9538 Compose *compose = (Compose *)data;
9541 gchar *folderidentifier;
9542 gint account_id = 0;
9545 GSList *list, *saved_list = NULL;
9546 HeaderEntryState *state;
9547 GtkRcStyle *style = NULL;
9548 #if !GTK_CHECK_VERSION(3, 0, 0)
9549 static GdkColor yellow;
9550 static gboolean color_set = FALSE;
9552 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9555 /* Get ID of active account in the combo box */
9556 menu = gtk_combo_box_get_model(optmenu);
9557 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9558 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9560 ac = account_find_from_id(account_id);
9561 cm_return_if_fail(ac != NULL);
9563 if (ac != compose->account) {
9564 compose_select_account(compose, ac, FALSE);
9566 for (list = compose->header_list; list; list = list->next) {
9567 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9569 if (hentry->type == PREF_ACCOUNT || !list->next) {
9570 compose_destroy_headerentry(compose, hentry);
9574 state = g_malloc0(sizeof(HeaderEntryState));
9575 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9576 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9577 state->entry = gtk_editable_get_chars(
9578 GTK_EDITABLE(hentry->entry), 0, -1);
9579 state->type = hentry->type;
9581 #if !GTK_CHECK_VERSION(3, 0, 0)
9583 gdk_color_parse("#f5f6be", &yellow);
9584 color_set = gdk_colormap_alloc_color(
9585 gdk_colormap_get_system(),
9586 &yellow, FALSE, TRUE);
9590 style = gtk_widget_get_modifier_style(hentry->entry);
9591 state->entry_marked = gdk_color_equal(&yellow,
9592 &style->base[GTK_STATE_NORMAL]);
9594 saved_list = g_slist_append(saved_list, state);
9595 compose_destroy_headerentry(compose, hentry);
9598 compose->header_last = NULL;
9599 g_slist_free(compose->header_list);
9600 compose->header_list = NULL;
9601 compose->header_nextrow = 1;
9602 compose_create_header_entry(compose);
9604 if (ac->set_autocc && ac->auto_cc)
9605 compose_entry_append(compose, ac->auto_cc,
9606 COMPOSE_CC, PREF_ACCOUNT);
9608 if (ac->set_autobcc && ac->auto_bcc)
9609 compose_entry_append(compose, ac->auto_bcc,
9610 COMPOSE_BCC, PREF_ACCOUNT);
9612 if (ac->set_autoreplyto && ac->auto_replyto)
9613 compose_entry_append(compose, ac->auto_replyto,
9614 COMPOSE_REPLYTO, PREF_ACCOUNT);
9616 for (list = saved_list; list; list = list->next) {
9617 state = (HeaderEntryState *) list->data;
9619 compose_add_header_entry(compose, state->header,
9620 state->entry, state->type);
9621 if (state->entry_marked)
9622 compose_entry_mark_default_to(compose, state->entry);
9624 g_free(state->header);
9625 g_free(state->entry);
9628 g_slist_free(saved_list);
9630 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9631 (ac->protocol == A_NNTP) ?
9632 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9635 /* Set message save folder */
9636 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9637 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9639 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9640 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9642 compose_set_save_to(compose, NULL);
9643 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9644 folderidentifier = folder_item_get_identifier(account_get_special_folder
9645 (compose->account, F_OUTBOX));
9646 compose_set_save_to(compose, folderidentifier);
9647 g_free(folderidentifier);
9651 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9652 GtkTreeViewColumn *column, Compose *compose)
9654 compose_attach_property(NULL, compose);
9657 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9660 Compose *compose = (Compose *)data;
9661 GtkTreeSelection *attach_selection;
9662 gint attach_nr_selected;
9664 if (!event) return FALSE;
9666 if (event->button == 3) {
9667 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9668 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9670 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9671 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9673 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9674 NULL, NULL, event->button, event->time);
9681 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9684 Compose *compose = (Compose *)data;
9686 if (!event) return FALSE;
9688 switch (event->keyval) {
9689 case GDK_KEY_Delete:
9690 compose_attach_remove_selected(NULL, compose);
9696 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9698 toolbar_comp_set_sensitive(compose, allow);
9699 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9700 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9702 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9704 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9705 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9706 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9708 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9712 static void compose_send_cb(GtkAction *action, gpointer data)
9714 Compose *compose = (Compose *)data;
9716 if (prefs_common.work_offline &&
9717 !inc_offline_should_override(TRUE,
9718 _("Claws Mail needs network access in order "
9719 "to send this email.")))
9722 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9723 g_source_remove(compose->draft_timeout_tag);
9724 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9727 compose_send(compose);
9730 static void compose_send_later_cb(GtkAction *action, gpointer data)
9732 Compose *compose = (Compose *)data;
9736 compose_allow_user_actions(compose, FALSE);
9737 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9738 compose_allow_user_actions(compose, TRUE);
9742 compose_close(compose);
9743 } else if (val == -1) {
9744 alertpanel_error(_("Could not queue message."));
9745 } else if (val == -2) {
9746 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9747 } else if (val == -3) {
9748 if (privacy_peek_error())
9749 alertpanel_error(_("Could not queue message for sending:\n\n"
9750 "Signature failed: %s"), privacy_get_error());
9751 } else if (val == -4) {
9752 alertpanel_error(_("Could not queue message for sending:\n\n"
9753 "Charset conversion failed."));
9754 } else if (val == -5) {
9755 alertpanel_error(_("Could not queue message for sending:\n\n"
9756 "Couldn't get recipient encryption key."));
9757 } else if (val == -6) {
9760 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9763 #define DRAFTED_AT_EXIT "drafted_at_exit"
9764 static void compose_register_draft(MsgInfo *info)
9766 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9767 DRAFTED_AT_EXIT, NULL);
9768 FILE *fp = g_fopen(filepath, "ab");
9771 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9779 gboolean compose_draft (gpointer data, guint action)
9781 Compose *compose = (Compose *)data;
9786 MsgFlags flag = {0, 0};
9787 static gboolean lock = FALSE;
9788 MsgInfo *newmsginfo;
9790 gboolean target_locked = FALSE;
9791 gboolean err = FALSE;
9793 if (lock) return FALSE;
9795 if (compose->sending)
9798 draft = account_get_special_folder(compose->account, F_DRAFT);
9799 cm_return_val_if_fail(draft != NULL, FALSE);
9801 if (!g_mutex_trylock(compose->mutex)) {
9802 /* we don't want to lock the mutex once it's available,
9803 * because as the only other part of compose.c locking
9804 * it is compose_close - which means once unlocked,
9805 * the compose struct will be freed */
9806 debug_print("couldn't lock mutex, probably sending\n");
9812 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9813 G_DIR_SEPARATOR, compose);
9814 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9815 FILE_OP_ERROR(tmp, "fopen");
9819 /* chmod for security */
9820 if (change_file_mode_rw(fp, tmp) < 0) {
9821 FILE_OP_ERROR(tmp, "chmod");
9822 g_warning("can't change file mode\n");
9825 /* Save draft infos */
9826 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9827 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9829 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9830 gchar *savefolderid;
9832 savefolderid = compose_get_save_to(compose);
9833 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9834 g_free(savefolderid);
9836 if (compose->return_receipt) {
9837 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9839 if (compose->privacy_system) {
9840 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9841 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9842 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9845 /* Message-ID of message replying to */
9846 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9847 gchar *folderid = NULL;
9849 if (compose->replyinfo->folder)
9850 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9851 if (folderid == NULL)
9852 folderid = g_strdup("NULL");
9854 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9857 /* Message-ID of message forwarding to */
9858 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9859 gchar *folderid = NULL;
9861 if (compose->fwdinfo->folder)
9862 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9863 if (folderid == NULL)
9864 folderid = g_strdup("NULL");
9866 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9870 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9871 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9873 sheaders = compose_get_manual_headers_info(compose);
9874 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9877 /* end of headers */
9878 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9885 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9889 if (fclose(fp) == EOF) {
9893 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9894 if (compose->targetinfo) {
9895 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9897 flag.perm_flags |= MSG_LOCKED;
9899 flag.tmp_flags = MSG_DRAFT;
9901 folder_item_scan(draft);
9902 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9903 MsgInfo *tmpinfo = NULL;
9904 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9905 if (compose->msgid) {
9906 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9909 msgnum = tmpinfo->msgnum;
9910 procmsg_msginfo_free(tmpinfo);
9911 debug_print("got draft msgnum %d from scanning\n", msgnum);
9913 debug_print("didn't get draft msgnum after scanning\n");
9916 debug_print("got draft msgnum %d from adding\n", msgnum);
9922 if (action != COMPOSE_AUTO_SAVE) {
9923 if (action != COMPOSE_DRAFT_FOR_EXIT)
9924 alertpanel_error(_("Could not save draft."));
9927 gtkut_window_popup(compose->window);
9928 val = alertpanel_full(_("Could not save draft"),
9929 _("Could not save draft.\n"
9930 "Do you want to cancel exit or discard this email?"),
9931 _("_Cancel exit"), _("_Discard email"), NULL,
9932 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9933 if (val == G_ALERTALTERNATE) {
9935 g_mutex_unlock(compose->mutex); /* must be done before closing */
9936 compose_close(compose);
9940 g_mutex_unlock(compose->mutex); /* must be done before closing */
9949 if (compose->mode == COMPOSE_REEDIT) {
9950 compose_remove_reedit_target(compose, TRUE);
9953 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9956 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9958 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9960 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9961 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9962 procmsg_msginfo_set_flags(newmsginfo, 0,
9963 MSG_HAS_ATTACHMENT);
9965 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9966 compose_register_draft(newmsginfo);
9968 procmsg_msginfo_free(newmsginfo);
9971 folder_item_scan(draft);
9973 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9975 g_mutex_unlock(compose->mutex); /* must be done before closing */
9976 compose_close(compose);
9982 path = folder_item_fetch_msg(draft, msgnum);
9984 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9987 if (g_stat(path, &s) < 0) {
9988 FILE_OP_ERROR(path, "stat");
9994 procmsg_msginfo_free(compose->targetinfo);
9995 compose->targetinfo = procmsg_msginfo_new();
9996 compose->targetinfo->msgnum = msgnum;
9997 compose->targetinfo->size = (goffset)s.st_size;
9998 compose->targetinfo->mtime = s.st_mtime;
9999 compose->targetinfo->folder = draft;
10001 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10002 compose->mode = COMPOSE_REEDIT;
10004 if (action == COMPOSE_AUTO_SAVE) {
10005 compose->autosaved_draft = compose->targetinfo;
10007 compose->modified = FALSE;
10008 compose_set_title(compose);
10012 g_mutex_unlock(compose->mutex);
10016 void compose_clear_exit_drafts(void)
10018 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10019 DRAFTED_AT_EXIT, NULL);
10020 if (is_file_exist(filepath))
10021 claws_unlink(filepath);
10026 void compose_reopen_exit_drafts(void)
10028 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10029 DRAFTED_AT_EXIT, NULL);
10030 FILE *fp = g_fopen(filepath, "rb");
10034 while (fgets(buf, sizeof(buf), fp)) {
10035 gchar **parts = g_strsplit(buf, "\t", 2);
10036 const gchar *folder = parts[0];
10037 int msgnum = parts[1] ? atoi(parts[1]):-1;
10039 if (folder && *folder && msgnum > -1) {
10040 FolderItem *item = folder_find_item_from_identifier(folder);
10041 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10043 compose_reedit(info, FALSE);
10050 compose_clear_exit_drafts();
10053 static void compose_save_cb(GtkAction *action, gpointer data)
10055 Compose *compose = (Compose *)data;
10056 compose_draft(compose, COMPOSE_KEEP_EDITING);
10057 compose->rmode = COMPOSE_REEDIT;
10060 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10062 if (compose && file_list) {
10065 for ( tmp = file_list; tmp; tmp = tmp->next) {
10066 gchar *file = (gchar *) tmp->data;
10067 gchar *utf8_filename = conv_filename_to_utf8(file);
10068 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10069 compose_changed_cb(NULL, compose);
10074 g_free(utf8_filename);
10079 static void compose_attach_cb(GtkAction *action, gpointer data)
10081 Compose *compose = (Compose *)data;
10084 if (compose->redirect_filename != NULL)
10087 /* Set focus_window properly, in case we were called via popup menu,
10088 * which unsets it (via focus_out_event callback on compose window). */
10089 manage_window_focus_in(compose->window, NULL, NULL);
10091 file_list = filesel_select_multiple_files_open(_("Select file"));
10094 compose_attach_from_list(compose, file_list, TRUE);
10095 g_list_free(file_list);
10099 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10101 Compose *compose = (Compose *)data;
10103 gint files_inserted = 0;
10105 file_list = filesel_select_multiple_files_open(_("Select file"));
10110 for ( tmp = file_list; tmp; tmp = tmp->next) {
10111 gchar *file = (gchar *) tmp->data;
10112 gchar *filedup = g_strdup(file);
10113 gchar *shortfile = g_path_get_basename(filedup);
10114 ComposeInsertResult res;
10115 /* insert the file if the file is short or if the user confirmed that
10116 he/she wants to insert the large file */
10117 res = compose_insert_file(compose, file);
10118 if (res == COMPOSE_INSERT_READ_ERROR) {
10119 alertpanel_error(_("File '%s' could not be read."), shortfile);
10120 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10121 alertpanel_error(_("File '%s' contained invalid characters\n"
10122 "for the current encoding, insertion may be incorrect."),
10124 } else if (res == COMPOSE_INSERT_SUCCESS)
10131 g_list_free(file_list);
10135 if (files_inserted > 0 && compose->gtkaspell &&
10136 compose->gtkaspell->check_while_typing)
10137 gtkaspell_highlight_all(compose->gtkaspell);
10141 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10143 Compose *compose = (Compose *)data;
10145 compose_insert_sig(compose, FALSE);
10148 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10150 Compose *compose = (Compose *)data;
10152 compose_insert_sig(compose, TRUE);
10155 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10159 Compose *compose = (Compose *)data;
10161 gtkut_widget_get_uposition(widget, &x, &y);
10162 if (!compose->batch) {
10163 prefs_common.compose_x = x;
10164 prefs_common.compose_y = y;
10166 if (compose->sending || compose->updating)
10168 compose_close_cb(NULL, compose);
10172 void compose_close_toolbar(Compose *compose)
10174 compose_close_cb(NULL, compose);
10177 static gboolean compose_can_autosave(Compose *compose)
10179 if (compose->privacy_system && compose->use_encryption)
10180 return prefs_common.autosave && prefs_common.autosave_encrypted;
10182 return prefs_common.autosave;
10185 static void compose_close_cb(GtkAction *action, gpointer data)
10187 Compose *compose = (Compose *)data;
10191 if (compose->exteditor_tag != -1) {
10192 if (!compose_ext_editor_kill(compose))
10197 if (compose->modified) {
10198 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10199 if (!g_mutex_trylock(compose->mutex)) {
10200 /* we don't want to lock the mutex once it's available,
10201 * because as the only other part of compose.c locking
10202 * it is compose_close - which means once unlocked,
10203 * the compose struct will be freed */
10204 debug_print("couldn't lock mutex, probably sending\n");
10208 val = alertpanel(_("Discard message"),
10209 _("This message has been modified. Discard it?"),
10210 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10212 val = alertpanel(_("Save changes"),
10213 _("This message has been modified. Save the latest changes?"),
10214 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10216 g_mutex_unlock(compose->mutex);
10218 case G_ALERTDEFAULT:
10219 if (compose_can_autosave(compose) && !reedit)
10220 compose_remove_draft(compose);
10222 case G_ALERTALTERNATE:
10223 compose_draft(data, COMPOSE_QUIT_EDITING);
10230 compose_close(compose);
10233 static void compose_print_cb(GtkAction *action, gpointer data)
10235 Compose *compose = (Compose *) data;
10237 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10238 if (compose->targetinfo)
10239 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10242 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10244 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10245 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10246 Compose *compose = (Compose *) data;
10249 compose->out_encoding = (CharSet)value;
10252 static void compose_address_cb(GtkAction *action, gpointer data)
10254 Compose *compose = (Compose *)data;
10256 #ifndef USE_NEW_ADDRBOOK
10257 addressbook_open(compose);
10259 GError* error = NULL;
10260 addressbook_connect_signals(compose);
10261 addressbook_dbus_open(TRUE, &error);
10263 g_warning("%s", error->message);
10264 g_error_free(error);
10269 static void about_show_cb(GtkAction *action, gpointer data)
10274 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10276 Compose *compose = (Compose *)data;
10281 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10282 cm_return_if_fail(tmpl != NULL);
10284 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10286 val = alertpanel(_("Apply template"), msg,
10287 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10290 if (val == G_ALERTDEFAULT)
10291 compose_template_apply(compose, tmpl, TRUE);
10292 else if (val == G_ALERTALTERNATE)
10293 compose_template_apply(compose, tmpl, FALSE);
10296 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10298 Compose *compose = (Compose *)data;
10300 compose_exec_ext_editor(compose);
10303 static void compose_undo_cb(GtkAction *action, gpointer data)
10305 Compose *compose = (Compose *)data;
10306 gboolean prev_autowrap = compose->autowrap;
10308 compose->autowrap = FALSE;
10309 undo_undo(compose->undostruct);
10310 compose->autowrap = prev_autowrap;
10313 static void compose_redo_cb(GtkAction *action, gpointer data)
10315 Compose *compose = (Compose *)data;
10316 gboolean prev_autowrap = compose->autowrap;
10318 compose->autowrap = FALSE;
10319 undo_redo(compose->undostruct);
10320 compose->autowrap = prev_autowrap;
10323 static void entry_cut_clipboard(GtkWidget *entry)
10325 if (GTK_IS_EDITABLE(entry))
10326 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10327 else if (GTK_IS_TEXT_VIEW(entry))
10328 gtk_text_buffer_cut_clipboard(
10329 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10330 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10334 static void entry_copy_clipboard(GtkWidget *entry)
10336 if (GTK_IS_EDITABLE(entry))
10337 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10338 else if (GTK_IS_TEXT_VIEW(entry))
10339 gtk_text_buffer_copy_clipboard(
10340 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10341 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10344 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10345 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10347 if (GTK_IS_TEXT_VIEW(entry)) {
10348 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10349 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10350 GtkTextIter start_iter, end_iter;
10352 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10354 if (contents == NULL)
10357 /* we shouldn't delete the selection when middle-click-pasting, or we
10358 * can't mid-click-paste our own selection */
10359 if (clip != GDK_SELECTION_PRIMARY) {
10360 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10361 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10364 if (insert_place == NULL) {
10365 /* if insert_place isn't specified, insert at the cursor.
10366 * used for Ctrl-V pasting */
10367 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10368 start = gtk_text_iter_get_offset(&start_iter);
10369 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10371 /* if insert_place is specified, paste here.
10372 * used for mid-click-pasting */
10373 start = gtk_text_iter_get_offset(insert_place);
10374 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10375 if (prefs_common.primary_paste_unselects)
10376 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10380 /* paste unwrapped: mark the paste so it's not wrapped later */
10381 end = start + strlen(contents);
10382 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10383 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10384 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10385 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10386 /* rewrap paragraph now (after a mid-click-paste) */
10387 mark_start = gtk_text_buffer_get_insert(buffer);
10388 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10389 gtk_text_iter_backward_char(&start_iter);
10390 compose_beautify_paragraph(compose, &start_iter, TRUE);
10392 } else if (GTK_IS_EDITABLE(entry))
10393 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10395 compose->modified = TRUE;
10398 static void entry_allsel(GtkWidget *entry)
10400 if (GTK_IS_EDITABLE(entry))
10401 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10402 else if (GTK_IS_TEXT_VIEW(entry)) {
10403 GtkTextIter startiter, enditer;
10404 GtkTextBuffer *textbuf;
10406 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10407 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10408 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10410 gtk_text_buffer_move_mark_by_name(textbuf,
10411 "selection_bound", &startiter);
10412 gtk_text_buffer_move_mark_by_name(textbuf,
10413 "insert", &enditer);
10417 static void compose_cut_cb(GtkAction *action, gpointer data)
10419 Compose *compose = (Compose *)data;
10420 if (compose->focused_editable
10421 #ifndef GENERIC_UMPC
10422 && gtk_widget_has_focus(compose->focused_editable)
10425 entry_cut_clipboard(compose->focused_editable);
10428 static void compose_copy_cb(GtkAction *action, gpointer data)
10430 Compose *compose = (Compose *)data;
10431 if (compose->focused_editable
10432 #ifndef GENERIC_UMPC
10433 && gtk_widget_has_focus(compose->focused_editable)
10436 entry_copy_clipboard(compose->focused_editable);
10439 static void compose_paste_cb(GtkAction *action, gpointer data)
10441 Compose *compose = (Compose *)data;
10442 gint prev_autowrap;
10443 GtkTextBuffer *buffer;
10445 if (compose->focused_editable &&
10446 #ifndef GENERIC_UMPC
10447 gtk_widget_has_focus(compose->focused_editable)
10450 entry_paste_clipboard(compose, compose->focused_editable,
10451 prefs_common.linewrap_pastes,
10452 GDK_SELECTION_CLIPBOARD, NULL);
10457 #ifndef GENERIC_UMPC
10458 gtk_widget_has_focus(compose->text) &&
10460 compose->gtkaspell &&
10461 compose->gtkaspell->check_while_typing)
10462 gtkaspell_highlight_all(compose->gtkaspell);
10466 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10468 Compose *compose = (Compose *)data;
10469 gint wrap_quote = prefs_common.linewrap_quote;
10470 if (compose->focused_editable
10471 #ifndef GENERIC_UMPC
10472 && gtk_widget_has_focus(compose->focused_editable)
10475 /* let text_insert() (called directly or at a later time
10476 * after the gtk_editable_paste_clipboard) know that
10477 * text is to be inserted as a quotation. implemented
10478 * by using a simple refcount... */
10479 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10480 G_OBJECT(compose->focused_editable),
10481 "paste_as_quotation"));
10482 g_object_set_data(G_OBJECT(compose->focused_editable),
10483 "paste_as_quotation",
10484 GINT_TO_POINTER(paste_as_quotation + 1));
10485 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10486 entry_paste_clipboard(compose, compose->focused_editable,
10487 prefs_common.linewrap_pastes,
10488 GDK_SELECTION_CLIPBOARD, NULL);
10489 prefs_common.linewrap_quote = wrap_quote;
10493 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10495 Compose *compose = (Compose *)data;
10496 gint prev_autowrap;
10497 GtkTextBuffer *buffer;
10499 if (compose->focused_editable
10500 #ifndef GENERIC_UMPC
10501 && gtk_widget_has_focus(compose->focused_editable)
10504 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10505 GDK_SELECTION_CLIPBOARD, NULL);
10510 #ifndef GENERIC_UMPC
10511 gtk_widget_has_focus(compose->text) &&
10513 compose->gtkaspell &&
10514 compose->gtkaspell->check_while_typing)
10515 gtkaspell_highlight_all(compose->gtkaspell);
10519 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10521 Compose *compose = (Compose *)data;
10522 gint prev_autowrap;
10523 GtkTextBuffer *buffer;
10525 if (compose->focused_editable
10526 #ifndef GENERIC_UMPC
10527 && gtk_widget_has_focus(compose->focused_editable)
10530 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10531 GDK_SELECTION_CLIPBOARD, NULL);
10536 #ifndef GENERIC_UMPC
10537 gtk_widget_has_focus(compose->text) &&
10539 compose->gtkaspell &&
10540 compose->gtkaspell->check_while_typing)
10541 gtkaspell_highlight_all(compose->gtkaspell);
10545 static void compose_allsel_cb(GtkAction *action, gpointer data)
10547 Compose *compose = (Compose *)data;
10548 if (compose->focused_editable
10549 #ifndef GENERIC_UMPC
10550 && gtk_widget_has_focus(compose->focused_editable)
10553 entry_allsel(compose->focused_editable);
10556 static void textview_move_beginning_of_line (GtkTextView *text)
10558 GtkTextBuffer *buffer;
10562 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10564 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10565 mark = gtk_text_buffer_get_insert(buffer);
10566 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10567 gtk_text_iter_set_line_offset(&ins, 0);
10568 gtk_text_buffer_place_cursor(buffer, &ins);
10571 static void textview_move_forward_character (GtkTextView *text)
10573 GtkTextBuffer *buffer;
10577 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10579 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10580 mark = gtk_text_buffer_get_insert(buffer);
10581 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10582 if (gtk_text_iter_forward_cursor_position(&ins))
10583 gtk_text_buffer_place_cursor(buffer, &ins);
10586 static void textview_move_backward_character (GtkTextView *text)
10588 GtkTextBuffer *buffer;
10592 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10594 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10595 mark = gtk_text_buffer_get_insert(buffer);
10596 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10597 if (gtk_text_iter_backward_cursor_position(&ins))
10598 gtk_text_buffer_place_cursor(buffer, &ins);
10601 static void textview_move_forward_word (GtkTextView *text)
10603 GtkTextBuffer *buffer;
10608 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10610 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10611 mark = gtk_text_buffer_get_insert(buffer);
10612 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10613 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10614 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10615 gtk_text_iter_backward_word_start(&ins);
10616 gtk_text_buffer_place_cursor(buffer, &ins);
10620 static void textview_move_backward_word (GtkTextView *text)
10622 GtkTextBuffer *buffer;
10626 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10628 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10629 mark = gtk_text_buffer_get_insert(buffer);
10630 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10631 if (gtk_text_iter_backward_word_starts(&ins, 1))
10632 gtk_text_buffer_place_cursor(buffer, &ins);
10635 static void textview_move_end_of_line (GtkTextView *text)
10637 GtkTextBuffer *buffer;
10641 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10643 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10644 mark = gtk_text_buffer_get_insert(buffer);
10645 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10646 if (gtk_text_iter_forward_to_line_end(&ins))
10647 gtk_text_buffer_place_cursor(buffer, &ins);
10650 static void textview_move_next_line (GtkTextView *text)
10652 GtkTextBuffer *buffer;
10657 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10659 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10660 mark = gtk_text_buffer_get_insert(buffer);
10661 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10662 offset = gtk_text_iter_get_line_offset(&ins);
10663 if (gtk_text_iter_forward_line(&ins)) {
10664 gtk_text_iter_set_line_offset(&ins, offset);
10665 gtk_text_buffer_place_cursor(buffer, &ins);
10669 static void textview_move_previous_line (GtkTextView *text)
10671 GtkTextBuffer *buffer;
10676 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10678 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10679 mark = gtk_text_buffer_get_insert(buffer);
10680 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10681 offset = gtk_text_iter_get_line_offset(&ins);
10682 if (gtk_text_iter_backward_line(&ins)) {
10683 gtk_text_iter_set_line_offset(&ins, offset);
10684 gtk_text_buffer_place_cursor(buffer, &ins);
10688 static void textview_delete_forward_character (GtkTextView *text)
10690 GtkTextBuffer *buffer;
10692 GtkTextIter ins, end_iter;
10694 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10696 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10697 mark = gtk_text_buffer_get_insert(buffer);
10698 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10700 if (gtk_text_iter_forward_char(&end_iter)) {
10701 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10705 static void textview_delete_backward_character (GtkTextView *text)
10707 GtkTextBuffer *buffer;
10709 GtkTextIter ins, end_iter;
10711 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10713 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10714 mark = gtk_text_buffer_get_insert(buffer);
10715 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10717 if (gtk_text_iter_backward_char(&end_iter)) {
10718 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10722 static void textview_delete_forward_word (GtkTextView *text)
10724 GtkTextBuffer *buffer;
10726 GtkTextIter ins, end_iter;
10728 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10730 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10731 mark = gtk_text_buffer_get_insert(buffer);
10732 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10734 if (gtk_text_iter_forward_word_end(&end_iter)) {
10735 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10739 static void textview_delete_backward_word (GtkTextView *text)
10741 GtkTextBuffer *buffer;
10743 GtkTextIter ins, end_iter;
10745 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10747 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10748 mark = gtk_text_buffer_get_insert(buffer);
10749 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10751 if (gtk_text_iter_backward_word_start(&end_iter)) {
10752 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10756 static void textview_delete_line (GtkTextView *text)
10758 GtkTextBuffer *buffer;
10760 GtkTextIter ins, start_iter, end_iter;
10762 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10764 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10765 mark = gtk_text_buffer_get_insert(buffer);
10766 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10769 gtk_text_iter_set_line_offset(&start_iter, 0);
10772 if (gtk_text_iter_ends_line(&end_iter)){
10773 if (!gtk_text_iter_forward_char(&end_iter))
10774 gtk_text_iter_backward_char(&start_iter);
10777 gtk_text_iter_forward_to_line_end(&end_iter);
10778 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10781 static void textview_delete_to_line_end (GtkTextView *text)
10783 GtkTextBuffer *buffer;
10785 GtkTextIter ins, end_iter;
10787 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10789 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10790 mark = gtk_text_buffer_get_insert(buffer);
10791 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10793 if (gtk_text_iter_ends_line(&end_iter))
10794 gtk_text_iter_forward_char(&end_iter);
10796 gtk_text_iter_forward_to_line_end(&end_iter);
10797 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10800 #define DO_ACTION(name, act) { \
10801 if(!strcmp(name, a_name)) { \
10805 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10807 const gchar *a_name = gtk_action_get_name(action);
10808 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10809 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10810 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10811 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10812 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10813 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10814 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10815 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10816 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10817 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10818 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10819 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10820 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10821 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10825 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10827 Compose *compose = (Compose *)data;
10828 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10829 ComposeCallAdvancedAction action = -1;
10831 action = compose_call_advanced_action_from_path(gaction);
10834 void (*do_action) (GtkTextView *text);
10835 } action_table[] = {
10836 {textview_move_beginning_of_line},
10837 {textview_move_forward_character},
10838 {textview_move_backward_character},
10839 {textview_move_forward_word},
10840 {textview_move_backward_word},
10841 {textview_move_end_of_line},
10842 {textview_move_next_line},
10843 {textview_move_previous_line},
10844 {textview_delete_forward_character},
10845 {textview_delete_backward_character},
10846 {textview_delete_forward_word},
10847 {textview_delete_backward_word},
10848 {textview_delete_line},
10849 {textview_delete_to_line_end}
10852 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10854 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10855 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10856 if (action_table[action].do_action)
10857 action_table[action].do_action(text);
10859 g_warning("Not implemented yet.");
10863 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10865 GtkAllocation allocation;
10869 if (GTK_IS_EDITABLE(widget)) {
10870 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10871 gtk_editable_set_position(GTK_EDITABLE(widget),
10874 if ((parent = gtk_widget_get_parent(widget))
10875 && (parent = gtk_widget_get_parent(parent))
10876 && (parent = gtk_widget_get_parent(parent))) {
10877 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10878 gtk_widget_get_allocation(widget, &allocation);
10879 gint y = allocation.y;
10880 gint height = allocation.height;
10881 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10882 (GTK_SCROLLED_WINDOW(parent));
10884 gfloat value = gtk_adjustment_get_value(shown);
10885 gfloat upper = gtk_adjustment_get_upper(shown);
10886 gfloat page_size = gtk_adjustment_get_page_size(shown);
10887 if (y < (int)value) {
10888 gtk_adjustment_set_value(shown, y - 1);
10890 if ((y + height) > ((int)value + (int)page_size)) {
10891 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10892 gtk_adjustment_set_value(shown,
10893 y + height - (int)page_size - 1);
10895 gtk_adjustment_set_value(shown,
10896 (int)upper - (int)page_size - 1);
10903 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10904 compose->focused_editable = widget;
10906 #ifdef GENERIC_UMPC
10907 if (GTK_IS_TEXT_VIEW(widget)
10908 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10909 g_object_ref(compose->notebook);
10910 g_object_ref(compose->edit_vbox);
10911 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10912 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10913 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10914 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10915 g_object_unref(compose->notebook);
10916 g_object_unref(compose->edit_vbox);
10917 g_signal_handlers_block_by_func(G_OBJECT(widget),
10918 G_CALLBACK(compose_grab_focus_cb),
10920 gtk_widget_grab_focus(widget);
10921 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10922 G_CALLBACK(compose_grab_focus_cb),
10924 } else if (!GTK_IS_TEXT_VIEW(widget)
10925 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10926 g_object_ref(compose->notebook);
10927 g_object_ref(compose->edit_vbox);
10928 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10929 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10930 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10931 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10932 g_object_unref(compose->notebook);
10933 g_object_unref(compose->edit_vbox);
10934 g_signal_handlers_block_by_func(G_OBJECT(widget),
10935 G_CALLBACK(compose_grab_focus_cb),
10937 gtk_widget_grab_focus(widget);
10938 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10939 G_CALLBACK(compose_grab_focus_cb),
10945 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10947 compose->modified = TRUE;
10948 // compose_beautify_paragraph(compose, NULL, TRUE);
10949 #ifndef GENERIC_UMPC
10950 compose_set_title(compose);
10954 static void compose_wrap_cb(GtkAction *action, gpointer data)
10956 Compose *compose = (Compose *)data;
10957 compose_beautify_paragraph(compose, NULL, TRUE);
10960 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10962 Compose *compose = (Compose *)data;
10963 compose_wrap_all_full(compose, TRUE);
10966 static void compose_find_cb(GtkAction *action, gpointer data)
10968 Compose *compose = (Compose *)data;
10970 message_search_compose(compose);
10973 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10976 Compose *compose = (Compose *)data;
10977 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10978 if (compose->autowrap)
10979 compose_wrap_all_full(compose, TRUE);
10980 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10983 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10986 Compose *compose = (Compose *)data;
10987 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10990 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10992 Compose *compose = (Compose *)data;
10994 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10997 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10999 Compose *compose = (Compose *)data;
11001 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11004 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11006 g_free(compose->privacy_system);
11008 compose->privacy_system = g_strdup(account->default_privacy_system);
11009 compose_update_privacy_system_menu_item(compose, warn);
11012 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11014 Compose *compose = (Compose *)data;
11016 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11017 gtk_widget_show(compose->ruler_hbox);
11018 prefs_common.show_ruler = TRUE;
11020 gtk_widget_hide(compose->ruler_hbox);
11021 gtk_widget_queue_resize(compose->edit_vbox);
11022 prefs_common.show_ruler = FALSE;
11026 static void compose_attach_drag_received_cb (GtkWidget *widget,
11027 GdkDragContext *context,
11030 GtkSelectionData *data,
11033 gpointer user_data)
11035 Compose *compose = (Compose *)user_data;
11039 type = gtk_selection_data_get_data_type(data);
11040 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11042 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11044 ) && gtk_drag_get_source_widget(context) !=
11045 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11046 list = uri_list_extract_filenames(
11047 (const gchar *)gtk_selection_data_get_data(data));
11048 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11049 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11050 compose_attach_append
11051 (compose, (const gchar *)tmp->data,
11052 utf8_filename, NULL, NULL);
11053 g_free(utf8_filename);
11055 if (list) compose_changed_cb(NULL, compose);
11056 list_free_strings(list);
11058 } else if (gtk_drag_get_source_widget(context)
11059 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11060 /* comes from our summaryview */
11061 SummaryView * summaryview = NULL;
11062 GSList * list = NULL, *cur = NULL;
11064 if (mainwindow_get_mainwindow())
11065 summaryview = mainwindow_get_mainwindow()->summaryview;
11068 list = summary_get_selected_msg_list(summaryview);
11070 for (cur = list; cur; cur = cur->next) {
11071 MsgInfo *msginfo = (MsgInfo *)cur->data;
11072 gchar *file = NULL;
11074 file = procmsg_get_message_file_full(msginfo,
11077 compose_attach_append(compose, (const gchar *)file,
11078 (const gchar *)file, "message/rfc822", NULL);
11082 g_slist_free(list);
11086 static gboolean compose_drag_drop(GtkWidget *widget,
11087 GdkDragContext *drag_context,
11089 guint time, gpointer user_data)
11091 /* not handling this signal makes compose_insert_drag_received_cb
11096 static gboolean completion_set_focus_to_subject
11097 (GtkWidget *widget,
11098 GdkEventKey *event,
11101 cm_return_val_if_fail(compose != NULL, FALSE);
11103 /* make backtab move to subject field */
11104 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11105 gtk_widget_grab_focus(compose->subject_entry);
11111 static void compose_insert_drag_received_cb (GtkWidget *widget,
11112 GdkDragContext *drag_context,
11115 GtkSelectionData *data,
11118 gpointer user_data)
11120 Compose *compose = (Compose *)user_data;
11124 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11126 type = gtk_selection_data_get_data_type(data);
11128 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11130 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11132 AlertValue val = G_ALERTDEFAULT;
11133 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11135 list = uri_list_extract_filenames(ddata);
11136 if (list == NULL && strstr(ddata, "://")) {
11137 /* Assume a list of no files, and data has ://, is a remote link */
11138 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11139 gchar *tmpfile = get_tmp_file();
11140 str_write_to_file(tmpdata, tmpfile);
11142 compose_insert_file(compose, tmpfile);
11143 claws_unlink(tmpfile);
11145 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11146 compose_beautify_paragraph(compose, NULL, TRUE);
11149 switch (prefs_common.compose_dnd_mode) {
11150 case COMPOSE_DND_ASK:
11151 val = alertpanel_full(_("Insert or attach?"),
11152 _("Do you want to insert the contents of the file(s) "
11153 "into the message body, or attach it to the email?"),
11154 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11155 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11157 case COMPOSE_DND_INSERT:
11158 val = G_ALERTALTERNATE;
11160 case COMPOSE_DND_ATTACH:
11161 val = G_ALERTOTHER;
11164 /* unexpected case */
11165 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11168 if (val & G_ALERTDISABLE) {
11169 val &= ~G_ALERTDISABLE;
11170 /* remember what action to perform by default, only if we don't click Cancel */
11171 if (val == G_ALERTALTERNATE)
11172 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11173 else if (val == G_ALERTOTHER)
11174 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11177 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11178 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11179 list_free_strings(list);
11182 } else if (val == G_ALERTOTHER) {
11183 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11184 list_free_strings(list);
11189 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11190 compose_insert_file(compose, (const gchar *)tmp->data);
11192 list_free_strings(list);
11194 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11199 static void compose_header_drag_received_cb (GtkWidget *widget,
11200 GdkDragContext *drag_context,
11203 GtkSelectionData *data,
11206 gpointer user_data)
11208 GtkEditable *entry = (GtkEditable *)user_data;
11209 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11211 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11214 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11215 gchar *decoded=g_new(gchar, strlen(email));
11218 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11219 gtk_editable_delete_text(entry, 0, -1);
11220 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11221 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11225 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11228 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11230 Compose *compose = (Compose *)data;
11232 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11233 compose->return_receipt = TRUE;
11235 compose->return_receipt = FALSE;
11238 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11240 Compose *compose = (Compose *)data;
11242 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11243 compose->remove_references = TRUE;
11245 compose->remove_references = FALSE;
11248 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11249 ComposeHeaderEntry *headerentry)
11251 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11255 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11256 GdkEventKey *event,
11257 ComposeHeaderEntry *headerentry)
11259 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11260 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11261 !(event->state & GDK_MODIFIER_MASK) &&
11262 (event->keyval == GDK_KEY_BackSpace) &&
11263 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11264 gtk_container_remove
11265 (GTK_CONTAINER(headerentry->compose->header_table),
11266 headerentry->combo);
11267 gtk_container_remove
11268 (GTK_CONTAINER(headerentry->compose->header_table),
11269 headerentry->entry);
11270 headerentry->compose->header_list =
11271 g_slist_remove(headerentry->compose->header_list,
11273 g_free(headerentry);
11274 } else if (event->keyval == GDK_KEY_Tab) {
11275 if (headerentry->compose->header_last == headerentry) {
11276 /* Override default next focus, and give it to subject_entry
11277 * instead of notebook tabs
11279 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11280 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11287 static gboolean scroll_postpone(gpointer data)
11289 Compose *compose = (Compose *)data;
11291 if (compose->batch)
11294 GTK_EVENTS_FLUSH();
11295 compose_show_first_last_header(compose, FALSE);
11299 static void compose_headerentry_changed_cb(GtkWidget *entry,
11300 ComposeHeaderEntry *headerentry)
11302 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11303 compose_create_header_entry(headerentry->compose);
11304 g_signal_handlers_disconnect_matched
11305 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11306 0, 0, NULL, NULL, headerentry);
11308 if (!headerentry->compose->batch)
11309 g_timeout_add(0, scroll_postpone, headerentry->compose);
11313 static gboolean compose_defer_auto_save_draft(Compose *compose)
11315 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11316 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11320 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11322 GtkAdjustment *vadj;
11324 cm_return_if_fail(compose);
11329 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11330 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11331 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11332 gtk_widget_get_parent(compose->header_table)));
11333 gtk_adjustment_set_value(vadj, (show_first ?
11334 gtk_adjustment_get_lower(vadj) :
11335 (gtk_adjustment_get_upper(vadj) -
11336 gtk_adjustment_get_page_size(vadj))));
11337 gtk_adjustment_changed(vadj);
11340 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11341 const gchar *text, gint len, Compose *compose)
11343 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11344 (G_OBJECT(compose->text), "paste_as_quotation"));
11347 cm_return_if_fail(text != NULL);
11349 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11350 G_CALLBACK(text_inserted),
11352 if (paste_as_quotation) {
11354 const gchar *qmark;
11356 GtkTextIter start_iter;
11359 len = strlen(text);
11361 new_text = g_strndup(text, len);
11363 qmark = compose_quote_char_from_context(compose);
11365 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11366 gtk_text_buffer_place_cursor(buffer, iter);
11368 pos = gtk_text_iter_get_offset(iter);
11370 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11371 _("Quote format error at line %d."));
11372 quote_fmt_reset_vartable();
11374 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11375 GINT_TO_POINTER(paste_as_quotation - 1));
11377 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11378 gtk_text_buffer_place_cursor(buffer, iter);
11379 gtk_text_buffer_delete_mark(buffer, mark);
11381 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11382 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11383 compose_beautify_paragraph(compose, &start_iter, FALSE);
11384 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11385 gtk_text_buffer_delete_mark(buffer, mark);
11387 if (strcmp(text, "\n") || compose->automatic_break
11388 || gtk_text_iter_starts_line(iter)) {
11389 GtkTextIter before_ins;
11390 gtk_text_buffer_insert(buffer, iter, text, len);
11391 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11392 before_ins = *iter;
11393 gtk_text_iter_backward_chars(&before_ins, len);
11394 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11397 /* check if the preceding is just whitespace or quote */
11398 GtkTextIter start_line;
11399 gchar *tmp = NULL, *quote = NULL;
11400 gint quote_len = 0, is_normal = 0;
11401 start_line = *iter;
11402 gtk_text_iter_set_line_offset(&start_line, 0);
11403 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11406 if (*tmp == '\0') {
11409 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11417 gtk_text_buffer_insert(buffer, iter, text, len);
11419 gtk_text_buffer_insert_with_tags_by_name(buffer,
11420 iter, text, len, "no_join", NULL);
11425 if (!paste_as_quotation) {
11426 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11427 compose_beautify_paragraph(compose, iter, FALSE);
11428 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11429 gtk_text_buffer_delete_mark(buffer, mark);
11432 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11433 G_CALLBACK(text_inserted),
11435 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11437 if (compose_can_autosave(compose) &&
11438 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11439 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11440 compose->draft_timeout_tag = g_timeout_add
11441 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11445 static void compose_check_all(GtkAction *action, gpointer data)
11447 Compose *compose = (Compose *)data;
11448 if (!compose->gtkaspell)
11451 if (gtk_widget_has_focus(compose->subject_entry))
11452 claws_spell_entry_check_all(
11453 CLAWS_SPELL_ENTRY(compose->subject_entry));
11455 gtkaspell_check_all(compose->gtkaspell);
11458 static void compose_highlight_all(GtkAction *action, gpointer data)
11460 Compose *compose = (Compose *)data;
11461 if (compose->gtkaspell) {
11462 claws_spell_entry_recheck_all(
11463 CLAWS_SPELL_ENTRY(compose->subject_entry));
11464 gtkaspell_highlight_all(compose->gtkaspell);
11468 static void compose_check_backwards(GtkAction *action, gpointer data)
11470 Compose *compose = (Compose *)data;
11471 if (!compose->gtkaspell) {
11472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11476 if (gtk_widget_has_focus(compose->subject_entry))
11477 claws_spell_entry_check_backwards(
11478 CLAWS_SPELL_ENTRY(compose->subject_entry));
11480 gtkaspell_check_backwards(compose->gtkaspell);
11483 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11485 Compose *compose = (Compose *)data;
11486 if (!compose->gtkaspell) {
11487 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11491 if (gtk_widget_has_focus(compose->subject_entry))
11492 claws_spell_entry_check_forwards_go(
11493 CLAWS_SPELL_ENTRY(compose->subject_entry));
11495 gtkaspell_check_forwards_go(compose->gtkaspell);
11500 *\brief Guess originating forward account from MsgInfo and several
11501 * "common preference" settings. Return NULL if no guess.
11503 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11505 PrefsAccount *account = NULL;
11507 cm_return_val_if_fail(msginfo, NULL);
11508 cm_return_val_if_fail(msginfo->folder, NULL);
11509 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11511 if (msginfo->folder->prefs->enable_default_account)
11512 account = account_find_from_id(msginfo->folder->prefs->default_account);
11515 account = msginfo->folder->folder->account;
11517 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11519 Xstrdup_a(to, msginfo->to, return NULL);
11520 extract_address(to);
11521 account = account_find_from_address(to, FALSE);
11524 if (!account && prefs_common.forward_account_autosel) {
11525 gchar cc[BUFFSIZE];
11526 if (!procheader_get_header_from_msginfo
11527 (msginfo, cc,sizeof cc , "Cc:")) {
11528 gchar *buf = cc + strlen("Cc:");
11529 extract_address(buf);
11530 account = account_find_from_address(buf, FALSE);
11534 if (!account && prefs_common.forward_account_autosel) {
11535 gchar deliveredto[BUFFSIZE];
11536 if (!procheader_get_header_from_msginfo
11537 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11538 gchar *buf = deliveredto + strlen("Delivered-To:");
11539 extract_address(buf);
11540 account = account_find_from_address(buf, FALSE);
11547 gboolean compose_close(Compose *compose)
11551 cm_return_val_if_fail(compose, FALSE);
11553 if (!g_mutex_trylock(compose->mutex)) {
11554 /* we have to wait for the (possibly deferred by auto-save)
11555 * drafting to be done, before destroying the compose under
11557 debug_print("waiting for drafting to finish...\n");
11558 compose_allow_user_actions(compose, FALSE);
11559 if (compose->close_timeout_tag == 0) {
11560 compose->close_timeout_tag =
11561 g_timeout_add (500, (GSourceFunc) compose_close,
11567 if (compose->draft_timeout_tag >= 0) {
11568 g_source_remove(compose->draft_timeout_tag);
11569 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11572 gtkut_widget_get_uposition(compose->window, &x, &y);
11573 if (!compose->batch) {
11574 prefs_common.compose_x = x;
11575 prefs_common.compose_y = y;
11577 g_mutex_unlock(compose->mutex);
11578 compose_destroy(compose);
11583 * Add entry field for each address in list.
11584 * \param compose E-Mail composition object.
11585 * \param listAddress List of (formatted) E-Mail addresses.
11587 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11590 node = listAddress;
11592 addr = ( gchar * ) node->data;
11593 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11594 node = g_list_next( node );
11598 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11599 guint action, gboolean opening_multiple)
11601 gchar *body = NULL;
11602 GSList *new_msglist = NULL;
11603 MsgInfo *tmp_msginfo = NULL;
11604 gboolean originally_enc = FALSE;
11605 gboolean originally_sig = FALSE;
11606 Compose *compose = NULL;
11607 gchar *s_system = NULL;
11609 cm_return_if_fail(msgview != NULL);
11611 cm_return_if_fail(msginfo_list != NULL);
11613 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11614 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11615 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11617 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11618 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11619 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11620 orig_msginfo, mimeinfo);
11621 if (tmp_msginfo != NULL) {
11622 new_msglist = g_slist_append(NULL, tmp_msginfo);
11624 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11625 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11626 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11628 tmp_msginfo->folder = orig_msginfo->folder;
11629 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11630 if (orig_msginfo->tags) {
11631 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11632 tmp_msginfo->folder->tags_dirty = TRUE;
11638 if (!opening_multiple)
11639 body = messageview_get_selection(msgview);
11642 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11643 procmsg_msginfo_free(tmp_msginfo);
11644 g_slist_free(new_msglist);
11646 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11648 if (compose && originally_enc) {
11649 compose_force_encryption(compose, compose->account, FALSE, s_system);
11652 if (compose && originally_sig && compose->account->default_sign_reply) {
11653 compose_force_signing(compose, compose->account, s_system);
11657 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11660 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11663 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11664 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11665 GSList *cur = msginfo_list;
11666 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11667 "messages. Opening the windows "
11668 "could take some time. Do you "
11669 "want to continue?"),
11670 g_slist_length(msginfo_list));
11671 if (g_slist_length(msginfo_list) > 9
11672 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11673 != G_ALERTALTERNATE) {
11678 /* We'll open multiple compose windows */
11679 /* let the WM place the next windows */
11680 compose_force_window_origin = FALSE;
11681 for (; cur; cur = cur->next) {
11683 tmplist.data = cur->data;
11684 tmplist.next = NULL;
11685 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11687 compose_force_window_origin = TRUE;
11689 /* forwarding multiple mails as attachments is done via a
11690 * single compose window */
11691 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11695 void compose_check_for_email_account(Compose *compose)
11697 PrefsAccount *ac = NULL, *curr = NULL;
11703 if (compose->account && compose->account->protocol == A_NNTP) {
11704 ac = account_get_cur_account();
11705 if (ac->protocol == A_NNTP) {
11706 list = account_get_list();
11708 for( ; list != NULL ; list = g_list_next(list)) {
11709 curr = (PrefsAccount *) list->data;
11710 if (curr->protocol != A_NNTP) {
11716 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11721 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11722 const gchar *address)
11724 GSList *msginfo_list = NULL;
11725 gchar *body = messageview_get_selection(msgview);
11728 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11730 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11731 compose_check_for_email_account(compose);
11732 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11733 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11734 compose_reply_set_subject(compose, msginfo);
11737 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11740 void compose_set_position(Compose *compose, gint pos)
11742 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11744 gtkut_text_view_set_position(text, pos);
11747 gboolean compose_search_string(Compose *compose,
11748 const gchar *str, gboolean case_sens)
11750 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11752 return gtkut_text_view_search_string(text, str, case_sens);
11755 gboolean compose_search_string_backward(Compose *compose,
11756 const gchar *str, gboolean case_sens)
11758 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11760 return gtkut_text_view_search_string_backward(text, str, case_sens);
11763 /* allocate a msginfo structure and populate its data from a compose data structure */
11764 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11766 MsgInfo *newmsginfo;
11768 gchar buf[BUFFSIZE];
11770 cm_return_val_if_fail( compose != NULL, NULL );
11772 newmsginfo = procmsg_msginfo_new();
11775 get_rfc822_date(buf, sizeof(buf));
11776 newmsginfo->date = g_strdup(buf);
11779 if (compose->from_name) {
11780 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11781 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11785 if (compose->subject_entry)
11786 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11788 /* to, cc, reply-to, newsgroups */
11789 for (list = compose->header_list; list; list = list->next) {
11790 gchar *header = gtk_editable_get_chars(
11792 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11793 gchar *entry = gtk_editable_get_chars(
11794 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11796 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11797 if ( newmsginfo->to == NULL ) {
11798 newmsginfo->to = g_strdup(entry);
11799 } else if (entry && *entry) {
11800 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11801 g_free(newmsginfo->to);
11802 newmsginfo->to = tmp;
11805 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11806 if ( newmsginfo->cc == NULL ) {
11807 newmsginfo->cc = g_strdup(entry);
11808 } else if (entry && *entry) {
11809 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11810 g_free(newmsginfo->cc);
11811 newmsginfo->cc = tmp;
11814 if ( strcasecmp(header,
11815 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11816 if ( newmsginfo->newsgroups == NULL ) {
11817 newmsginfo->newsgroups = g_strdup(entry);
11818 } else if (entry && *entry) {
11819 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11820 g_free(newmsginfo->newsgroups);
11821 newmsginfo->newsgroups = tmp;
11829 /* other data is unset */
11835 /* update compose's dictionaries from folder dict settings */
11836 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11837 FolderItem *folder_item)
11839 cm_return_if_fail(compose != NULL);
11841 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11842 FolderItemPrefs *prefs = folder_item->prefs;
11844 if (prefs->enable_default_dictionary)
11845 gtkaspell_change_dict(compose->gtkaspell,
11846 prefs->default_dictionary, FALSE);
11847 if (folder_item->prefs->enable_default_alt_dictionary)
11848 gtkaspell_change_alt_dict(compose->gtkaspell,
11849 prefs->default_alt_dictionary);
11850 if (prefs->enable_default_dictionary
11851 || prefs->enable_default_alt_dictionary)
11852 compose_spell_menu_changed(compose);
11857 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11859 Compose *compose = (Compose *)data;
11861 cm_return_if_fail(compose != NULL);
11863 gtk_widget_grab_focus(compose->text);
11866 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
11868 gtk_combo_box_popup(GTK_COMBO_BOX(data));