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), 0);
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(_("S_ubject:"));
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;
8051 GtkWidget *fromlabel;
8054 GtkWidget *from_name = NULL;
8056 gint num = 0, def_menu = 0;
8058 accounts = account_get_list();
8059 cm_return_val_if_fail(accounts != NULL, NULL);
8061 optmenubox = gtk_event_box_new();
8062 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8063 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8065 hbox = gtk_hbox_new(FALSE, 4);
8066 from_name = gtk_entry_new();
8068 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8069 G_CALLBACK(compose_grab_focus_cb), compose);
8070 g_signal_connect_after(G_OBJECT(from_name), "activate",
8071 G_CALLBACK(from_name_activate_cb), optmenu);
8073 for (; accounts != NULL; accounts = accounts->next, num++) {
8074 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8075 gchar *name, *from = NULL;
8077 if (ac == compose->account) def_menu = num;
8079 name = g_markup_printf_escaped(_("<i>%s</i>"),
8082 if (ac == compose->account) {
8083 if (ac->name && *ac->name) {
8085 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8086 from = g_strdup_printf("%s <%s>",
8088 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8090 from = g_strdup_printf("%s",
8092 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8095 COMBOBOX_ADD(menu, name, ac->account_id);
8100 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8102 g_signal_connect(G_OBJECT(optmenu), "changed",
8103 G_CALLBACK(account_activated),
8105 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8106 G_CALLBACK(compose_entry_popup_extend),
8109 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8110 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8112 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8113 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8114 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8116 /* Putting only the GtkEntry into focus chain of parent hbox causes
8117 * the account selector combobox next to it to be unreachable when
8118 * navigating widgets in GtkTable with up/down arrow keys.
8119 * Note: gtk_widget_set_can_focus() was not enough. */
8121 l = g_list_prepend(l, from_name);
8122 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8125 CLAWS_SET_TIP(optmenubox,
8126 _("Account to use for this email"));
8127 CLAWS_SET_TIP(from_name,
8128 _("Sender address to be used"));
8130 compose->account_combo = optmenu;
8131 compose->from_name = from_name;
8136 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8138 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8139 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8140 Compose *compose = (Compose *) data;
8142 compose->priority = value;
8146 static void compose_reply_change_mode(Compose *compose,
8149 gboolean was_modified = compose->modified;
8151 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8153 cm_return_if_fail(compose->replyinfo != NULL);
8155 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8157 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8159 if (action == COMPOSE_REPLY_TO_ALL)
8161 if (action == COMPOSE_REPLY_TO_SENDER)
8163 if (action == COMPOSE_REPLY_TO_LIST)
8166 compose_remove_header_entries(compose);
8167 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8168 if (compose->account->set_autocc && compose->account->auto_cc)
8169 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8171 if (compose->account->set_autobcc && compose->account->auto_bcc)
8172 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8174 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8175 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8176 compose_show_first_last_header(compose, TRUE);
8177 compose->modified = was_modified;
8178 compose_set_title(compose);
8181 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8183 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8184 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8185 Compose *compose = (Compose *) data;
8188 compose_reply_change_mode(compose, value);
8191 static void compose_update_priority_menu_item(Compose * compose)
8193 GtkWidget *menuitem = NULL;
8194 switch (compose->priority) {
8195 case PRIORITY_HIGHEST:
8196 menuitem = gtk_ui_manager_get_widget
8197 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8200 menuitem = gtk_ui_manager_get_widget
8201 (compose->ui_manager, "/Menu/Options/Priority/High");
8203 case PRIORITY_NORMAL:
8204 menuitem = gtk_ui_manager_get_widget
8205 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8208 menuitem = gtk_ui_manager_get_widget
8209 (compose->ui_manager, "/Menu/Options/Priority/Low");
8211 case PRIORITY_LOWEST:
8212 menuitem = gtk_ui_manager_get_widget
8213 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8216 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8219 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8221 Compose *compose = (Compose *) data;
8223 gboolean can_sign = FALSE, can_encrypt = FALSE;
8225 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8227 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8230 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8231 g_free(compose->privacy_system);
8232 compose->privacy_system = NULL;
8233 if (systemid != NULL) {
8234 compose->privacy_system = g_strdup(systemid);
8236 can_sign = privacy_system_can_sign(systemid);
8237 can_encrypt = privacy_system_can_encrypt(systemid);
8240 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8242 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8246 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8248 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8249 GtkWidget *menuitem = NULL;
8250 GList *children, *amenu;
8251 gboolean can_sign = FALSE, can_encrypt = FALSE;
8252 gboolean found = FALSE;
8254 if (compose->privacy_system != NULL) {
8256 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8257 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8258 cm_return_if_fail(menuitem != NULL);
8260 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8263 while (amenu != NULL) {
8264 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8265 if (systemid != NULL) {
8266 if (strcmp(systemid, compose->privacy_system) == 0 &&
8267 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8268 menuitem = GTK_WIDGET(amenu->data);
8270 can_sign = privacy_system_can_sign(systemid);
8271 can_encrypt = privacy_system_can_encrypt(systemid);
8275 } else if (strlen(compose->privacy_system) == 0 &&
8276 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8277 menuitem = GTK_WIDGET(amenu->data);
8280 can_encrypt = FALSE;
8285 amenu = amenu->next;
8287 g_list_free(children);
8288 if (menuitem != NULL)
8289 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8291 if (warn && !found && strlen(compose->privacy_system)) {
8292 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8293 "will not be able to sign or encrypt this message."),
8294 compose->privacy_system);
8298 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8299 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8302 static void compose_set_out_encoding(Compose *compose)
8304 CharSet out_encoding;
8305 const gchar *branch = NULL;
8306 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8308 switch(out_encoding) {
8309 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8310 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8311 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8312 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8313 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8314 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8315 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8316 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8317 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8318 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8319 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8320 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8321 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8322 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8323 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8324 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8325 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8326 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8327 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8328 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8329 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8330 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8331 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8332 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8333 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8334 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8335 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8336 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8337 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8338 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8339 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8340 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8341 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8343 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8346 static void compose_set_template_menu(Compose *compose)
8348 GSList *tmpl_list, *cur;
8352 tmpl_list = template_get_config();
8354 menu = gtk_menu_new();
8356 gtk_menu_set_accel_group (GTK_MENU (menu),
8357 gtk_ui_manager_get_accel_group(compose->ui_manager));
8358 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8359 Template *tmpl = (Template *)cur->data;
8360 gchar *accel_path = NULL;
8361 item = gtk_menu_item_new_with_label(tmpl->name);
8362 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8363 g_signal_connect(G_OBJECT(item), "activate",
8364 G_CALLBACK(compose_template_activate_cb),
8366 g_object_set_data(G_OBJECT(item), "template", tmpl);
8367 gtk_widget_show(item);
8368 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8369 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8373 gtk_widget_show(menu);
8374 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8377 void compose_update_actions_menu(Compose *compose)
8379 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8382 static void compose_update_privacy_systems_menu(Compose *compose)
8384 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8385 GSList *systems, *cur;
8387 GtkWidget *system_none;
8389 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8390 GtkWidget *privacy_menu = gtk_menu_new();
8392 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8393 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8395 g_signal_connect(G_OBJECT(system_none), "activate",
8396 G_CALLBACK(compose_set_privacy_system_cb), compose);
8398 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8399 gtk_widget_show(system_none);
8401 systems = privacy_get_system_ids();
8402 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8403 gchar *systemid = cur->data;
8405 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8406 widget = gtk_radio_menu_item_new_with_label(group,
8407 privacy_system_get_name(systemid));
8408 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8409 g_strdup(systemid), g_free);
8410 g_signal_connect(G_OBJECT(widget), "activate",
8411 G_CALLBACK(compose_set_privacy_system_cb), compose);
8413 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8414 gtk_widget_show(widget);
8417 g_slist_free(systems);
8418 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8419 gtk_widget_show_all(privacy_menu);
8420 gtk_widget_show_all(privacy_menuitem);
8423 void compose_reflect_prefs_all(void)
8428 for (cur = compose_list; cur != NULL; cur = cur->next) {
8429 compose = (Compose *)cur->data;
8430 compose_set_template_menu(compose);
8434 void compose_reflect_prefs_pixmap_theme(void)
8439 for (cur = compose_list; cur != NULL; cur = cur->next) {
8440 compose = (Compose *)cur->data;
8441 toolbar_update(TOOLBAR_COMPOSE, compose);
8445 static const gchar *compose_quote_char_from_context(Compose *compose)
8447 const gchar *qmark = NULL;
8449 cm_return_val_if_fail(compose != NULL, NULL);
8451 switch (compose->mode) {
8452 /* use forward-specific quote char */
8453 case COMPOSE_FORWARD:
8454 case COMPOSE_FORWARD_AS_ATTACH:
8455 case COMPOSE_FORWARD_INLINE:
8456 if (compose->folder && compose->folder->prefs &&
8457 compose->folder->prefs->forward_with_format)
8458 qmark = compose->folder->prefs->forward_quotemark;
8459 else if (compose->account->forward_with_format)
8460 qmark = compose->account->forward_quotemark;
8462 qmark = prefs_common.fw_quotemark;
8465 /* use reply-specific quote char in all other modes */
8467 if (compose->folder && compose->folder->prefs &&
8468 compose->folder->prefs->reply_with_format)
8469 qmark = compose->folder->prefs->reply_quotemark;
8470 else if (compose->account->reply_with_format)
8471 qmark = compose->account->reply_quotemark;
8473 qmark = prefs_common.quotemark;
8477 if (qmark == NULL || *qmark == '\0')
8483 static void compose_template_apply(Compose *compose, Template *tmpl,
8487 GtkTextBuffer *buffer;
8491 gchar *parsed_str = NULL;
8492 gint cursor_pos = 0;
8493 const gchar *err_msg = _("The body of the template has an error at line %d.");
8496 /* process the body */
8498 text = GTK_TEXT_VIEW(compose->text);
8499 buffer = gtk_text_view_get_buffer(text);
8502 qmark = compose_quote_char_from_context(compose);
8504 if (compose->replyinfo != NULL) {
8507 gtk_text_buffer_set_text(buffer, "", -1);
8508 mark = gtk_text_buffer_get_insert(buffer);
8509 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8511 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8512 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8514 } else if (compose->fwdinfo != NULL) {
8517 gtk_text_buffer_set_text(buffer, "", -1);
8518 mark = gtk_text_buffer_get_insert(buffer);
8519 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8521 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8522 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8525 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8527 GtkTextIter start, end;
8530 gtk_text_buffer_get_start_iter(buffer, &start);
8531 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8532 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8534 /* clear the buffer now */
8536 gtk_text_buffer_set_text(buffer, "", -1);
8538 parsed_str = compose_quote_fmt(compose, dummyinfo,
8539 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8540 procmsg_msginfo_free( dummyinfo );
8546 gtk_text_buffer_set_text(buffer, "", -1);
8547 mark = gtk_text_buffer_get_insert(buffer);
8548 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8551 if (replace && parsed_str && compose->account->auto_sig)
8552 compose_insert_sig(compose, FALSE);
8554 if (replace && parsed_str) {
8555 gtk_text_buffer_get_start_iter(buffer, &iter);
8556 gtk_text_buffer_place_cursor(buffer, &iter);
8560 cursor_pos = quote_fmt_get_cursor_pos();
8561 compose->set_cursor_pos = cursor_pos;
8562 if (cursor_pos == -1)
8564 gtk_text_buffer_get_start_iter(buffer, &iter);
8565 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8566 gtk_text_buffer_place_cursor(buffer, &iter);
8569 /* process the other fields */
8571 compose_template_apply_fields(compose, tmpl);
8572 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8573 quote_fmt_reset_vartable();
8574 compose_changed_cb(NULL, compose);
8577 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8578 gtkaspell_highlight_all(compose->gtkaspell);
8582 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8584 MsgInfo* dummyinfo = NULL;
8585 MsgInfo *msginfo = NULL;
8588 if (compose->replyinfo != NULL)
8589 msginfo = compose->replyinfo;
8590 else if (compose->fwdinfo != NULL)
8591 msginfo = compose->fwdinfo;
8593 dummyinfo = compose_msginfo_new_from_compose(compose);
8594 msginfo = dummyinfo;
8597 if (tmpl->from && *tmpl->from != '\0') {
8599 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8600 compose->gtkaspell);
8602 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8604 quote_fmt_scan_string(tmpl->from);
8607 buf = quote_fmt_get_buffer();
8609 alertpanel_error(_("Template From format error."));
8611 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8615 if (tmpl->to && *tmpl->to != '\0') {
8617 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8618 compose->gtkaspell);
8620 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8622 quote_fmt_scan_string(tmpl->to);
8625 buf = quote_fmt_get_buffer();
8627 alertpanel_error(_("Template To format error."));
8629 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8633 if (tmpl->cc && *tmpl->cc != '\0') {
8635 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8636 compose->gtkaspell);
8638 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8640 quote_fmt_scan_string(tmpl->cc);
8643 buf = quote_fmt_get_buffer();
8645 alertpanel_error(_("Template Cc format error."));
8647 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8651 if (tmpl->bcc && *tmpl->bcc != '\0') {
8653 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8654 compose->gtkaspell);
8656 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8658 quote_fmt_scan_string(tmpl->bcc);
8661 buf = quote_fmt_get_buffer();
8663 alertpanel_error(_("Template Bcc format error."));
8665 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8669 if (tmpl->replyto && *tmpl->replyto != '\0') {
8671 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8672 compose->gtkaspell);
8674 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8676 quote_fmt_scan_string(tmpl->replyto);
8679 buf = quote_fmt_get_buffer();
8681 alertpanel_error(_("Template Reply-To format error."));
8683 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8687 /* process the subject */
8688 if (tmpl->subject && *tmpl->subject != '\0') {
8690 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8691 compose->gtkaspell);
8693 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8695 quote_fmt_scan_string(tmpl->subject);
8698 buf = quote_fmt_get_buffer();
8700 alertpanel_error(_("Template subject format error."));
8702 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8706 procmsg_msginfo_free( dummyinfo );
8709 static void compose_destroy(Compose *compose)
8711 GtkAllocation allocation;
8712 GtkTextBuffer *buffer;
8713 GtkClipboard *clipboard;
8715 compose_list = g_list_remove(compose_list, compose);
8717 if (compose->updating) {
8718 debug_print("danger, not destroying anything now\n");
8719 compose->deferred_destroy = TRUE;
8723 /* NOTE: address_completion_end() does nothing with the window
8724 * however this may change. */
8725 address_completion_end(compose->window);
8727 slist_free_strings_full(compose->to_list);
8728 slist_free_strings_full(compose->newsgroup_list);
8729 slist_free_strings_full(compose->header_list);
8731 slist_free_strings_full(extra_headers);
8732 extra_headers = NULL;
8734 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8736 g_hash_table_destroy(compose->email_hashtable);
8738 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8739 compose->folder_update_callback_id);
8741 procmsg_msginfo_free(compose->targetinfo);
8742 procmsg_msginfo_free(compose->replyinfo);
8743 procmsg_msginfo_free(compose->fwdinfo);
8745 g_free(compose->replyto);
8746 g_free(compose->cc);
8747 g_free(compose->bcc);
8748 g_free(compose->newsgroups);
8749 g_free(compose->followup_to);
8751 g_free(compose->ml_post);
8753 g_free(compose->inreplyto);
8754 g_free(compose->references);
8755 g_free(compose->msgid);
8756 g_free(compose->boundary);
8758 g_free(compose->redirect_filename);
8759 if (compose->undostruct)
8760 undo_destroy(compose->undostruct);
8762 g_free(compose->sig_str);
8764 g_free(compose->exteditor_file);
8766 g_free(compose->orig_charset);
8768 g_free(compose->privacy_system);
8770 #ifndef USE_NEW_ADDRBOOK
8771 if (addressbook_get_target_compose() == compose)
8772 addressbook_set_target_compose(NULL);
8775 if (compose->gtkaspell) {
8776 gtkaspell_delete(compose->gtkaspell);
8777 compose->gtkaspell = NULL;
8781 if (!compose->batch) {
8782 gtk_widget_get_allocation(compose->window, &allocation);
8783 prefs_common.compose_width = allocation.width;
8784 prefs_common.compose_height = allocation.height;
8787 if (!gtk_widget_get_parent(compose->paned))
8788 gtk_widget_destroy(compose->paned);
8789 gtk_widget_destroy(compose->popupmenu);
8791 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8792 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8793 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8795 gtk_widget_destroy(compose->window);
8796 toolbar_destroy(compose->toolbar);
8797 g_free(compose->toolbar);
8798 cm_mutex_free(compose->mutex);
8802 static void compose_attach_info_free(AttachInfo *ainfo)
8804 g_free(ainfo->file);
8805 g_free(ainfo->content_type);
8806 g_free(ainfo->name);
8807 g_free(ainfo->charset);
8811 static void compose_attach_update_label(Compose *compose)
8816 GtkTreeModel *model;
8821 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8822 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8823 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8827 while(gtk_tree_model_iter_next(model, &iter))
8830 text = g_strdup_printf("(%d)", i);
8831 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8835 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8837 Compose *compose = (Compose *)data;
8838 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8839 GtkTreeSelection *selection;
8841 GtkTreeModel *model;
8843 selection = gtk_tree_view_get_selection(tree_view);
8844 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8849 for (cur = sel; cur != NULL; cur = cur->next) {
8850 GtkTreePath *path = cur->data;
8851 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8854 gtk_tree_path_free(path);
8857 for (cur = sel; cur != NULL; cur = cur->next) {
8858 GtkTreeRowReference *ref = cur->data;
8859 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8862 if (gtk_tree_model_get_iter(model, &iter, path))
8863 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8865 gtk_tree_path_free(path);
8866 gtk_tree_row_reference_free(ref);
8870 compose_attach_update_label(compose);
8873 static struct _AttachProperty
8876 GtkWidget *mimetype_entry;
8877 GtkWidget *encoding_optmenu;
8878 GtkWidget *path_entry;
8879 GtkWidget *filename_entry;
8881 GtkWidget *cancel_btn;
8884 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8886 gtk_tree_path_free((GtkTreePath *)ptr);
8889 static void compose_attach_property(GtkAction *action, gpointer data)
8891 Compose *compose = (Compose *)data;
8892 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8894 GtkComboBox *optmenu;
8895 GtkTreeSelection *selection;
8897 GtkTreeModel *model;
8900 static gboolean cancelled;
8902 /* only if one selected */
8903 selection = gtk_tree_view_get_selection(tree_view);
8904 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8907 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8911 path = (GtkTreePath *) sel->data;
8912 gtk_tree_model_get_iter(model, &iter, path);
8913 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8916 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8922 if (!attach_prop.window)
8923 compose_attach_property_create(&cancelled);
8924 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8925 gtk_widget_grab_focus(attach_prop.ok_btn);
8926 gtk_widget_show(attach_prop.window);
8927 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8928 GTK_WINDOW(compose->window));
8930 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8931 if (ainfo->encoding == ENC_UNKNOWN)
8932 combobox_select_by_data(optmenu, ENC_BASE64);
8934 combobox_select_by_data(optmenu, ainfo->encoding);
8936 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8937 ainfo->content_type ? ainfo->content_type : "");
8938 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8939 ainfo->file ? ainfo->file : "");
8940 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8941 ainfo->name ? ainfo->name : "");
8944 const gchar *entry_text;
8946 gchar *cnttype = NULL;
8953 gtk_widget_hide(attach_prop.window);
8954 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8959 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8960 if (*entry_text != '\0') {
8963 text = g_strstrip(g_strdup(entry_text));
8964 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8965 cnttype = g_strdup(text);
8968 alertpanel_error(_("Invalid MIME type."));
8974 ainfo->encoding = combobox_get_active_data(optmenu);
8976 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8977 if (*entry_text != '\0') {
8978 if (is_file_exist(entry_text) &&
8979 (size = get_file_size(entry_text)) > 0)
8980 file = g_strdup(entry_text);
8983 (_("File doesn't exist or is empty."));
8989 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8990 if (*entry_text != '\0') {
8991 g_free(ainfo->name);
8992 ainfo->name = g_strdup(entry_text);
8996 g_free(ainfo->content_type);
8997 ainfo->content_type = cnttype;
9000 g_free(ainfo->file);
9004 ainfo->size = (goffset)size;
9006 /* update tree store */
9007 text = to_human_readable(ainfo->size);
9008 gtk_tree_model_get_iter(model, &iter, path);
9009 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9010 COL_MIMETYPE, ainfo->content_type,
9012 COL_NAME, ainfo->name,
9013 COL_CHARSET, ainfo->charset,
9019 gtk_tree_path_free(path);
9022 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9024 label = gtk_label_new(str); \
9025 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9026 GTK_FILL, 0, 0, 0); \
9027 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9029 entry = gtk_entry_new(); \
9030 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9031 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9034 static void compose_attach_property_create(gboolean *cancelled)
9040 GtkWidget *mimetype_entry;
9043 GtkListStore *optmenu_menu;
9044 GtkWidget *path_entry;
9045 GtkWidget *filename_entry;
9048 GtkWidget *cancel_btn;
9049 GList *mime_type_list, *strlist;
9052 debug_print("Creating attach_property window...\n");
9054 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9055 gtk_widget_set_size_request(window, 480, -1);
9056 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9057 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9058 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9059 g_signal_connect(G_OBJECT(window), "delete_event",
9060 G_CALLBACK(attach_property_delete_event),
9062 g_signal_connect(G_OBJECT(window), "key_press_event",
9063 G_CALLBACK(attach_property_key_pressed),
9066 vbox = gtk_vbox_new(FALSE, 8);
9067 gtk_container_add(GTK_CONTAINER(window), vbox);
9069 table = gtk_table_new(4, 2, FALSE);
9070 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9071 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9072 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9074 label = gtk_label_new(_("MIME type"));
9075 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9077 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9078 #if !GTK_CHECK_VERSION(2, 24, 0)
9079 mimetype_entry = gtk_combo_box_entry_new_text();
9081 mimetype_entry = gtk_combo_box_text_new_with_entry();
9083 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9084 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9086 /* stuff with list */
9087 mime_type_list = procmime_get_mime_type_list();
9089 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9090 MimeType *type = (MimeType *) mime_type_list->data;
9093 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9095 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9098 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9099 (GCompareFunc)strcmp2);
9102 for (mime_type_list = strlist; mime_type_list != NULL;
9103 mime_type_list = mime_type_list->next) {
9104 #if !GTK_CHECK_VERSION(2, 24, 0)
9105 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9107 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9109 g_free(mime_type_list->data);
9111 g_list_free(strlist);
9112 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9113 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9115 label = gtk_label_new(_("Encoding"));
9116 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9118 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9120 hbox = gtk_hbox_new(FALSE, 0);
9121 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9122 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9124 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9125 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9127 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9128 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9129 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9130 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9131 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9133 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9135 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9136 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9138 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9139 &ok_btn, GTK_STOCK_OK,
9141 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9142 gtk_widget_grab_default(ok_btn);
9144 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9145 G_CALLBACK(attach_property_ok),
9147 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9148 G_CALLBACK(attach_property_cancel),
9151 gtk_widget_show_all(vbox);
9153 attach_prop.window = window;
9154 attach_prop.mimetype_entry = mimetype_entry;
9155 attach_prop.encoding_optmenu = optmenu;
9156 attach_prop.path_entry = path_entry;
9157 attach_prop.filename_entry = filename_entry;
9158 attach_prop.ok_btn = ok_btn;
9159 attach_prop.cancel_btn = cancel_btn;
9162 #undef SET_LABEL_AND_ENTRY
9164 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9170 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9176 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9177 gboolean *cancelled)
9185 static gboolean attach_property_key_pressed(GtkWidget *widget,
9187 gboolean *cancelled)
9189 if (event && event->keyval == GDK_KEY_Escape) {
9193 if (event && event->keyval == GDK_KEY_Return) {
9201 static void compose_exec_ext_editor(Compose *compose)
9208 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9209 G_DIR_SEPARATOR, compose);
9211 if (pipe(pipe_fds) < 0) {
9217 if ((pid = fork()) < 0) {
9224 /* close the write side of the pipe */
9227 compose->exteditor_file = g_strdup(tmp);
9228 compose->exteditor_pid = pid;
9230 compose_set_ext_editor_sensitive(compose, FALSE);
9233 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9235 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9237 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9241 } else { /* process-monitoring process */
9247 /* close the read side of the pipe */
9250 if (compose_write_body_to_file(compose, tmp) < 0) {
9251 fd_write_all(pipe_fds[1], "2\n", 2);
9255 pid_ed = compose_exec_ext_editor_real(tmp);
9257 fd_write_all(pipe_fds[1], "1\n", 2);
9261 /* wait until editor is terminated */
9262 waitpid(pid_ed, NULL, 0);
9264 fd_write_all(pipe_fds[1], "0\n", 2);
9271 #endif /* G_OS_UNIX */
9275 static gint compose_exec_ext_editor_real(const gchar *file)
9282 cm_return_val_if_fail(file != NULL, -1);
9284 if ((pid = fork()) < 0) {
9289 if (pid != 0) return pid;
9291 /* grandchild process */
9293 if (setpgid(0, getppid()))
9296 if (prefs_common_get_ext_editor_cmd() &&
9297 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9298 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9299 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9301 if (prefs_common_get_ext_editor_cmd())
9302 g_warning("External editor command-line is invalid: '%s'\n",
9303 prefs_common_get_ext_editor_cmd());
9304 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9307 cmdline = strsplit_with_quote(buf, " ", 1024);
9308 execvp(cmdline[0], cmdline);
9311 g_strfreev(cmdline);
9316 static gboolean compose_ext_editor_kill(Compose *compose)
9318 pid_t pgid = compose->exteditor_pid * -1;
9321 ret = kill(pgid, 0);
9323 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9327 msg = g_strdup_printf
9328 (_("The external editor is still working.\n"
9329 "Force terminating the process?\n"
9330 "process group id: %d"), -pgid);
9331 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9332 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9336 if (val == G_ALERTALTERNATE) {
9337 g_source_remove(compose->exteditor_tag);
9338 g_io_channel_shutdown(compose->exteditor_ch,
9340 g_io_channel_unref(compose->exteditor_ch);
9342 if (kill(pgid, SIGTERM) < 0) perror("kill");
9343 waitpid(compose->exteditor_pid, NULL, 0);
9345 g_warning("Terminated process group id: %d", -pgid);
9346 g_warning("Temporary file: %s",
9347 compose->exteditor_file);
9349 compose_set_ext_editor_sensitive(compose, TRUE);
9351 g_free(compose->exteditor_file);
9352 compose->exteditor_file = NULL;
9353 compose->exteditor_pid = -1;
9354 compose->exteditor_ch = NULL;
9355 compose->exteditor_tag = -1;
9363 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9367 Compose *compose = (Compose *)data;
9370 debug_print("Compose: input from monitoring process\n");
9372 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9374 g_io_channel_shutdown(source, FALSE, NULL);
9375 g_io_channel_unref(source);
9377 waitpid(compose->exteditor_pid, NULL, 0);
9379 if (buf[0] == '0') { /* success */
9380 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9381 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9382 GtkTextIter start, end;
9385 gtk_text_buffer_set_text(buffer, "", -1);
9386 compose_insert_file(compose, compose->exteditor_file);
9387 compose_changed_cb(NULL, compose);
9388 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9390 if (claws_unlink(compose->exteditor_file) < 0)
9391 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9393 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9394 gtk_text_buffer_get_start_iter(buffer, &start);
9395 gtk_text_buffer_get_end_iter(buffer, &end);
9396 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9397 if (chars && strlen(chars) > 0)
9398 compose->modified = TRUE;
9400 } else if (buf[0] == '1') { /* failed */
9401 g_warning("Couldn't exec external editor\n");
9402 if (claws_unlink(compose->exteditor_file) < 0)
9403 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9404 } else if (buf[0] == '2') {
9405 g_warning("Couldn't write to file\n");
9406 } else if (buf[0] == '3') {
9407 g_warning("Pipe read failed\n");
9410 compose_set_ext_editor_sensitive(compose, TRUE);
9412 g_free(compose->exteditor_file);
9413 compose->exteditor_file = NULL;
9414 compose->exteditor_pid = -1;
9415 compose->exteditor_ch = NULL;
9416 compose->exteditor_tag = -1;
9421 static void compose_set_ext_editor_sensitive(Compose *compose,
9424 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9425 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9426 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9427 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9428 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9429 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9430 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9431 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9433 gtk_widget_set_sensitive(compose->text, sensitive);
9434 if (compose->toolbar->send_btn)
9435 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9436 if (compose->toolbar->sendl_btn)
9437 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9438 if (compose->toolbar->draft_btn)
9439 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9440 if (compose->toolbar->insert_btn)
9441 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9442 if (compose->toolbar->sig_btn)
9443 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9444 if (compose->toolbar->exteditor_btn)
9445 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9446 if (compose->toolbar->linewrap_current_btn)
9447 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9448 if (compose->toolbar->linewrap_all_btn)
9449 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9451 #endif /* G_OS_UNIX */
9454 * compose_undo_state_changed:
9456 * Change the sensivity of the menuentries undo and redo
9458 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9459 gint redo_state, gpointer data)
9461 Compose *compose = (Compose *)data;
9463 switch (undo_state) {
9464 case UNDO_STATE_TRUE:
9465 if (!undostruct->undo_state) {
9466 undostruct->undo_state = TRUE;
9467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9470 case UNDO_STATE_FALSE:
9471 if (undostruct->undo_state) {
9472 undostruct->undo_state = FALSE;
9473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9476 case UNDO_STATE_UNCHANGED:
9478 case UNDO_STATE_REFRESH:
9479 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9482 g_warning("Undo state not recognized");
9486 switch (redo_state) {
9487 case UNDO_STATE_TRUE:
9488 if (!undostruct->redo_state) {
9489 undostruct->redo_state = TRUE;
9490 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9493 case UNDO_STATE_FALSE:
9494 if (undostruct->redo_state) {
9495 undostruct->redo_state = FALSE;
9496 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9499 case UNDO_STATE_UNCHANGED:
9501 case UNDO_STATE_REFRESH:
9502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9505 g_warning("Redo state not recognized");
9510 /* callback functions */
9512 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9513 GtkAllocation *allocation,
9516 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9519 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9520 * includes "non-client" (windows-izm) in calculation, so this calculation
9521 * may not be accurate.
9523 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9524 GtkAllocation *allocation,
9525 GtkSHRuler *shruler)
9527 if (prefs_common.show_ruler) {
9528 gint char_width = 0, char_height = 0;
9529 gint line_width_in_chars;
9531 gtkut_get_font_size(GTK_WIDGET(widget),
9532 &char_width, &char_height);
9533 line_width_in_chars =
9534 (allocation->width - allocation->x) / char_width;
9536 /* got the maximum */
9537 gtk_shruler_set_range(GTK_SHRULER(shruler),
9538 0.0, line_width_in_chars, 0);
9547 ComposePrefType type;
9548 gboolean entry_marked;
9551 static void account_activated(GtkComboBox *optmenu, gpointer data)
9553 Compose *compose = (Compose *)data;
9556 gchar *folderidentifier;
9557 gint account_id = 0;
9560 GSList *list, *saved_list = NULL;
9561 HeaderEntryState *state;
9562 GtkRcStyle *style = NULL;
9563 #if !GTK_CHECK_VERSION(3, 0, 0)
9564 static GdkColor yellow;
9565 static gboolean color_set = FALSE;
9567 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9570 /* Get ID of active account in the combo box */
9571 menu = gtk_combo_box_get_model(optmenu);
9572 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9573 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9575 ac = account_find_from_id(account_id);
9576 cm_return_if_fail(ac != NULL);
9578 if (ac != compose->account) {
9579 compose_select_account(compose, ac, FALSE);
9581 for (list = compose->header_list; list; list = list->next) {
9582 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9584 if (hentry->type == PREF_ACCOUNT || !list->next) {
9585 compose_destroy_headerentry(compose, hentry);
9589 state = g_malloc0(sizeof(HeaderEntryState));
9590 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9591 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9592 state->entry = gtk_editable_get_chars(
9593 GTK_EDITABLE(hentry->entry), 0, -1);
9594 state->type = hentry->type;
9596 #if !GTK_CHECK_VERSION(3, 0, 0)
9598 gdk_color_parse("#f5f6be", &yellow);
9599 color_set = gdk_colormap_alloc_color(
9600 gdk_colormap_get_system(),
9601 &yellow, FALSE, TRUE);
9605 style = gtk_widget_get_modifier_style(hentry->entry);
9606 state->entry_marked = gdk_color_equal(&yellow,
9607 &style->base[GTK_STATE_NORMAL]);
9609 saved_list = g_slist_append(saved_list, state);
9610 compose_destroy_headerentry(compose, hentry);
9613 compose->header_last = NULL;
9614 g_slist_free(compose->header_list);
9615 compose->header_list = NULL;
9616 compose->header_nextrow = 1;
9617 compose_create_header_entry(compose);
9619 if (ac->set_autocc && ac->auto_cc)
9620 compose_entry_append(compose, ac->auto_cc,
9621 COMPOSE_CC, PREF_ACCOUNT);
9623 if (ac->set_autobcc && ac->auto_bcc)
9624 compose_entry_append(compose, ac->auto_bcc,
9625 COMPOSE_BCC, PREF_ACCOUNT);
9627 if (ac->set_autoreplyto && ac->auto_replyto)
9628 compose_entry_append(compose, ac->auto_replyto,
9629 COMPOSE_REPLYTO, PREF_ACCOUNT);
9631 for (list = saved_list; list; list = list->next) {
9632 state = (HeaderEntryState *) list->data;
9634 compose_add_header_entry(compose, state->header,
9635 state->entry, state->type);
9636 if (state->entry_marked)
9637 compose_entry_mark_default_to(compose, state->entry);
9639 g_free(state->header);
9640 g_free(state->entry);
9643 g_slist_free(saved_list);
9645 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9646 (ac->protocol == A_NNTP) ?
9647 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9650 /* Set message save folder */
9651 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9652 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9654 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9655 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9657 compose_set_save_to(compose, NULL);
9658 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9659 folderidentifier = folder_item_get_identifier(account_get_special_folder
9660 (compose->account, F_OUTBOX));
9661 compose_set_save_to(compose, folderidentifier);
9662 g_free(folderidentifier);
9666 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9667 GtkTreeViewColumn *column, Compose *compose)
9669 compose_attach_property(NULL, compose);
9672 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9675 Compose *compose = (Compose *)data;
9676 GtkTreeSelection *attach_selection;
9677 gint attach_nr_selected;
9679 if (!event) return FALSE;
9681 if (event->button == 3) {
9682 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9683 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9685 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9686 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9688 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9689 NULL, NULL, event->button, event->time);
9696 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9699 Compose *compose = (Compose *)data;
9701 if (!event) return FALSE;
9703 switch (event->keyval) {
9704 case GDK_KEY_Delete:
9705 compose_attach_remove_selected(NULL, compose);
9711 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9713 toolbar_comp_set_sensitive(compose, allow);
9714 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9715 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9717 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9719 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9720 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9721 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9723 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9727 static void compose_send_cb(GtkAction *action, gpointer data)
9729 Compose *compose = (Compose *)data;
9731 if (prefs_common.work_offline &&
9732 !inc_offline_should_override(TRUE,
9733 _("Claws Mail needs network access in order "
9734 "to send this email.")))
9737 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9738 g_source_remove(compose->draft_timeout_tag);
9739 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9742 compose_send(compose);
9745 static void compose_send_later_cb(GtkAction *action, gpointer data)
9747 Compose *compose = (Compose *)data;
9751 compose_allow_user_actions(compose, FALSE);
9752 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9753 compose_allow_user_actions(compose, TRUE);
9757 compose_close(compose);
9758 } else if (val == -1) {
9759 alertpanel_error(_("Could not queue message."));
9760 } else if (val == -2) {
9761 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9762 } else if (val == -3) {
9763 if (privacy_peek_error())
9764 alertpanel_error(_("Could not queue message for sending:\n\n"
9765 "Signature failed: %s"), privacy_get_error());
9766 } else if (val == -4) {
9767 alertpanel_error(_("Could not queue message for sending:\n\n"
9768 "Charset conversion failed."));
9769 } else if (val == -5) {
9770 alertpanel_error(_("Could not queue message for sending:\n\n"
9771 "Couldn't get recipient encryption key."));
9772 } else if (val == -6) {
9775 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9778 #define DRAFTED_AT_EXIT "drafted_at_exit"
9779 static void compose_register_draft(MsgInfo *info)
9781 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9782 DRAFTED_AT_EXIT, NULL);
9783 FILE *fp = g_fopen(filepath, "ab");
9786 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9794 gboolean compose_draft (gpointer data, guint action)
9796 Compose *compose = (Compose *)data;
9801 MsgFlags flag = {0, 0};
9802 static gboolean lock = FALSE;
9803 MsgInfo *newmsginfo;
9805 gboolean target_locked = FALSE;
9806 gboolean err = FALSE;
9808 if (lock) return FALSE;
9810 if (compose->sending)
9813 draft = account_get_special_folder(compose->account, F_DRAFT);
9814 cm_return_val_if_fail(draft != NULL, FALSE);
9816 if (!g_mutex_trylock(compose->mutex)) {
9817 /* we don't want to lock the mutex once it's available,
9818 * because as the only other part of compose.c locking
9819 * it is compose_close - which means once unlocked,
9820 * the compose struct will be freed */
9821 debug_print("couldn't lock mutex, probably sending\n");
9827 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9828 G_DIR_SEPARATOR, compose);
9829 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9830 FILE_OP_ERROR(tmp, "fopen");
9834 /* chmod for security */
9835 if (change_file_mode_rw(fp, tmp) < 0) {
9836 FILE_OP_ERROR(tmp, "chmod");
9837 g_warning("can't change file mode\n");
9840 /* Save draft infos */
9841 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9842 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9844 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9845 gchar *savefolderid;
9847 savefolderid = compose_get_save_to(compose);
9848 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9849 g_free(savefolderid);
9851 if (compose->return_receipt) {
9852 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9854 if (compose->privacy_system) {
9855 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9856 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9857 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9860 /* Message-ID of message replying to */
9861 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9862 gchar *folderid = NULL;
9864 if (compose->replyinfo->folder)
9865 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9866 if (folderid == NULL)
9867 folderid = g_strdup("NULL");
9869 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9872 /* Message-ID of message forwarding to */
9873 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9874 gchar *folderid = NULL;
9876 if (compose->fwdinfo->folder)
9877 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9878 if (folderid == NULL)
9879 folderid = g_strdup("NULL");
9881 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9885 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9886 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9888 sheaders = compose_get_manual_headers_info(compose);
9889 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9892 /* end of headers */
9893 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9900 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9904 if (fclose(fp) == EOF) {
9908 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9909 if (compose->targetinfo) {
9910 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9912 flag.perm_flags |= MSG_LOCKED;
9914 flag.tmp_flags = MSG_DRAFT;
9916 folder_item_scan(draft);
9917 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9918 MsgInfo *tmpinfo = NULL;
9919 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9920 if (compose->msgid) {
9921 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9924 msgnum = tmpinfo->msgnum;
9925 procmsg_msginfo_free(tmpinfo);
9926 debug_print("got draft msgnum %d from scanning\n", msgnum);
9928 debug_print("didn't get draft msgnum after scanning\n");
9931 debug_print("got draft msgnum %d from adding\n", msgnum);
9937 if (action != COMPOSE_AUTO_SAVE) {
9938 if (action != COMPOSE_DRAFT_FOR_EXIT)
9939 alertpanel_error(_("Could not save draft."));
9942 gtkut_window_popup(compose->window);
9943 val = alertpanel_full(_("Could not save draft"),
9944 _("Could not save draft.\n"
9945 "Do you want to cancel exit or discard this email?"),
9946 _("_Cancel exit"), _("_Discard email"), NULL,
9947 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9948 if (val == G_ALERTALTERNATE) {
9950 g_mutex_unlock(compose->mutex); /* must be done before closing */
9951 compose_close(compose);
9955 g_mutex_unlock(compose->mutex); /* must be done before closing */
9964 if (compose->mode == COMPOSE_REEDIT) {
9965 compose_remove_reedit_target(compose, TRUE);
9968 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9971 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9973 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9975 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9976 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9977 procmsg_msginfo_set_flags(newmsginfo, 0,
9978 MSG_HAS_ATTACHMENT);
9980 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9981 compose_register_draft(newmsginfo);
9983 procmsg_msginfo_free(newmsginfo);
9986 folder_item_scan(draft);
9988 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9990 g_mutex_unlock(compose->mutex); /* must be done before closing */
9991 compose_close(compose);
9997 path = folder_item_fetch_msg(draft, msgnum);
9999 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10002 if (g_stat(path, &s) < 0) {
10003 FILE_OP_ERROR(path, "stat");
10009 procmsg_msginfo_free(compose->targetinfo);
10010 compose->targetinfo = procmsg_msginfo_new();
10011 compose->targetinfo->msgnum = msgnum;
10012 compose->targetinfo->size = (goffset)s.st_size;
10013 compose->targetinfo->mtime = s.st_mtime;
10014 compose->targetinfo->folder = draft;
10016 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10017 compose->mode = COMPOSE_REEDIT;
10019 if (action == COMPOSE_AUTO_SAVE) {
10020 compose->autosaved_draft = compose->targetinfo;
10022 compose->modified = FALSE;
10023 compose_set_title(compose);
10027 g_mutex_unlock(compose->mutex);
10031 void compose_clear_exit_drafts(void)
10033 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10034 DRAFTED_AT_EXIT, NULL);
10035 if (is_file_exist(filepath))
10036 claws_unlink(filepath);
10041 void compose_reopen_exit_drafts(void)
10043 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10044 DRAFTED_AT_EXIT, NULL);
10045 FILE *fp = g_fopen(filepath, "rb");
10049 while (fgets(buf, sizeof(buf), fp)) {
10050 gchar **parts = g_strsplit(buf, "\t", 2);
10051 const gchar *folder = parts[0];
10052 int msgnum = parts[1] ? atoi(parts[1]):-1;
10054 if (folder && *folder && msgnum > -1) {
10055 FolderItem *item = folder_find_item_from_identifier(folder);
10056 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10058 compose_reedit(info, FALSE);
10065 compose_clear_exit_drafts();
10068 static void compose_save_cb(GtkAction *action, gpointer data)
10070 Compose *compose = (Compose *)data;
10071 compose_draft(compose, COMPOSE_KEEP_EDITING);
10072 compose->rmode = COMPOSE_REEDIT;
10075 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10077 if (compose && file_list) {
10080 for ( tmp = file_list; tmp; tmp = tmp->next) {
10081 gchar *file = (gchar *) tmp->data;
10082 gchar *utf8_filename = conv_filename_to_utf8(file);
10083 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10084 compose_changed_cb(NULL, compose);
10089 g_free(utf8_filename);
10094 static void compose_attach_cb(GtkAction *action, gpointer data)
10096 Compose *compose = (Compose *)data;
10099 if (compose->redirect_filename != NULL)
10102 /* Set focus_window properly, in case we were called via popup menu,
10103 * which unsets it (via focus_out_event callback on compose window). */
10104 manage_window_focus_in(compose->window, NULL, NULL);
10106 file_list = filesel_select_multiple_files_open(_("Select file"));
10109 compose_attach_from_list(compose, file_list, TRUE);
10110 g_list_free(file_list);
10114 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10116 Compose *compose = (Compose *)data;
10118 gint files_inserted = 0;
10120 file_list = filesel_select_multiple_files_open(_("Select file"));
10125 for ( tmp = file_list; tmp; tmp = tmp->next) {
10126 gchar *file = (gchar *) tmp->data;
10127 gchar *filedup = g_strdup(file);
10128 gchar *shortfile = g_path_get_basename(filedup);
10129 ComposeInsertResult res;
10130 /* insert the file if the file is short or if the user confirmed that
10131 he/she wants to insert the large file */
10132 res = compose_insert_file(compose, file);
10133 if (res == COMPOSE_INSERT_READ_ERROR) {
10134 alertpanel_error(_("File '%s' could not be read."), shortfile);
10135 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10136 alertpanel_error(_("File '%s' contained invalid characters\n"
10137 "for the current encoding, insertion may be incorrect."),
10139 } else if (res == COMPOSE_INSERT_SUCCESS)
10146 g_list_free(file_list);
10150 if (files_inserted > 0 && compose->gtkaspell &&
10151 compose->gtkaspell->check_while_typing)
10152 gtkaspell_highlight_all(compose->gtkaspell);
10156 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10158 Compose *compose = (Compose *)data;
10160 compose_insert_sig(compose, FALSE);
10163 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10165 Compose *compose = (Compose *)data;
10167 compose_insert_sig(compose, TRUE);
10170 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10174 Compose *compose = (Compose *)data;
10176 gtkut_widget_get_uposition(widget, &x, &y);
10177 if (!compose->batch) {
10178 prefs_common.compose_x = x;
10179 prefs_common.compose_y = y;
10181 if (compose->sending || compose->updating)
10183 compose_close_cb(NULL, compose);
10187 void compose_close_toolbar(Compose *compose)
10189 compose_close_cb(NULL, compose);
10192 static gboolean compose_can_autosave(Compose *compose)
10194 if (compose->privacy_system && compose->use_encryption)
10195 return prefs_common.autosave && prefs_common.autosave_encrypted;
10197 return prefs_common.autosave;
10200 static void compose_close_cb(GtkAction *action, gpointer data)
10202 Compose *compose = (Compose *)data;
10206 if (compose->exteditor_tag != -1) {
10207 if (!compose_ext_editor_kill(compose))
10212 if (compose->modified) {
10213 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10214 if (!g_mutex_trylock(compose->mutex)) {
10215 /* we don't want to lock the mutex once it's available,
10216 * because as the only other part of compose.c locking
10217 * it is compose_close - which means once unlocked,
10218 * the compose struct will be freed */
10219 debug_print("couldn't lock mutex, probably sending\n");
10223 val = alertpanel(_("Discard message"),
10224 _("This message has been modified. Discard it?"),
10225 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10227 val = alertpanel(_("Save changes"),
10228 _("This message has been modified. Save the latest changes?"),
10229 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10231 g_mutex_unlock(compose->mutex);
10233 case G_ALERTDEFAULT:
10234 if (compose_can_autosave(compose) && !reedit)
10235 compose_remove_draft(compose);
10237 case G_ALERTALTERNATE:
10238 compose_draft(data, COMPOSE_QUIT_EDITING);
10245 compose_close(compose);
10248 static void compose_print_cb(GtkAction *action, gpointer data)
10250 Compose *compose = (Compose *) data;
10252 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10253 if (compose->targetinfo)
10254 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10257 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10259 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10260 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10261 Compose *compose = (Compose *) data;
10264 compose->out_encoding = (CharSet)value;
10267 static void compose_address_cb(GtkAction *action, gpointer data)
10269 Compose *compose = (Compose *)data;
10271 #ifndef USE_NEW_ADDRBOOK
10272 addressbook_open(compose);
10274 GError* error = NULL;
10275 addressbook_connect_signals(compose);
10276 addressbook_dbus_open(TRUE, &error);
10278 g_warning("%s", error->message);
10279 g_error_free(error);
10284 static void about_show_cb(GtkAction *action, gpointer data)
10289 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10291 Compose *compose = (Compose *)data;
10296 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10297 cm_return_if_fail(tmpl != NULL);
10299 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10301 val = alertpanel(_("Apply template"), msg,
10302 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10305 if (val == G_ALERTDEFAULT)
10306 compose_template_apply(compose, tmpl, TRUE);
10307 else if (val == G_ALERTALTERNATE)
10308 compose_template_apply(compose, tmpl, FALSE);
10311 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10313 Compose *compose = (Compose *)data;
10315 compose_exec_ext_editor(compose);
10318 static void compose_undo_cb(GtkAction *action, gpointer data)
10320 Compose *compose = (Compose *)data;
10321 gboolean prev_autowrap = compose->autowrap;
10323 compose->autowrap = FALSE;
10324 undo_undo(compose->undostruct);
10325 compose->autowrap = prev_autowrap;
10328 static void compose_redo_cb(GtkAction *action, gpointer data)
10330 Compose *compose = (Compose *)data;
10331 gboolean prev_autowrap = compose->autowrap;
10333 compose->autowrap = FALSE;
10334 undo_redo(compose->undostruct);
10335 compose->autowrap = prev_autowrap;
10338 static void entry_cut_clipboard(GtkWidget *entry)
10340 if (GTK_IS_EDITABLE(entry))
10341 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10342 else if (GTK_IS_TEXT_VIEW(entry))
10343 gtk_text_buffer_cut_clipboard(
10344 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10345 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10349 static void entry_copy_clipboard(GtkWidget *entry)
10351 if (GTK_IS_EDITABLE(entry))
10352 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10353 else if (GTK_IS_TEXT_VIEW(entry))
10354 gtk_text_buffer_copy_clipboard(
10355 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10356 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10359 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10360 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10362 if (GTK_IS_TEXT_VIEW(entry)) {
10363 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10364 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10365 GtkTextIter start_iter, end_iter;
10367 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10369 if (contents == NULL)
10372 /* we shouldn't delete the selection when middle-click-pasting, or we
10373 * can't mid-click-paste our own selection */
10374 if (clip != GDK_SELECTION_PRIMARY) {
10375 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10376 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10379 if (insert_place == NULL) {
10380 /* if insert_place isn't specified, insert at the cursor.
10381 * used for Ctrl-V pasting */
10382 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10383 start = gtk_text_iter_get_offset(&start_iter);
10384 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10386 /* if insert_place is specified, paste here.
10387 * used for mid-click-pasting */
10388 start = gtk_text_iter_get_offset(insert_place);
10389 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10390 if (prefs_common.primary_paste_unselects)
10391 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10395 /* paste unwrapped: mark the paste so it's not wrapped later */
10396 end = start + strlen(contents);
10397 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10398 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10399 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10400 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10401 /* rewrap paragraph now (after a mid-click-paste) */
10402 mark_start = gtk_text_buffer_get_insert(buffer);
10403 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10404 gtk_text_iter_backward_char(&start_iter);
10405 compose_beautify_paragraph(compose, &start_iter, TRUE);
10407 } else if (GTK_IS_EDITABLE(entry))
10408 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10410 compose->modified = TRUE;
10413 static void entry_allsel(GtkWidget *entry)
10415 if (GTK_IS_EDITABLE(entry))
10416 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10417 else if (GTK_IS_TEXT_VIEW(entry)) {
10418 GtkTextIter startiter, enditer;
10419 GtkTextBuffer *textbuf;
10421 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10422 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10423 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10425 gtk_text_buffer_move_mark_by_name(textbuf,
10426 "selection_bound", &startiter);
10427 gtk_text_buffer_move_mark_by_name(textbuf,
10428 "insert", &enditer);
10432 static void compose_cut_cb(GtkAction *action, gpointer data)
10434 Compose *compose = (Compose *)data;
10435 if (compose->focused_editable
10436 #ifndef GENERIC_UMPC
10437 && gtk_widget_has_focus(compose->focused_editable)
10440 entry_cut_clipboard(compose->focused_editable);
10443 static void compose_copy_cb(GtkAction *action, gpointer data)
10445 Compose *compose = (Compose *)data;
10446 if (compose->focused_editable
10447 #ifndef GENERIC_UMPC
10448 && gtk_widget_has_focus(compose->focused_editable)
10451 entry_copy_clipboard(compose->focused_editable);
10454 static void compose_paste_cb(GtkAction *action, gpointer data)
10456 Compose *compose = (Compose *)data;
10457 gint prev_autowrap;
10458 GtkTextBuffer *buffer;
10460 if (compose->focused_editable &&
10461 #ifndef GENERIC_UMPC
10462 gtk_widget_has_focus(compose->focused_editable)
10465 entry_paste_clipboard(compose, compose->focused_editable,
10466 prefs_common.linewrap_pastes,
10467 GDK_SELECTION_CLIPBOARD, NULL);
10472 #ifndef GENERIC_UMPC
10473 gtk_widget_has_focus(compose->text) &&
10475 compose->gtkaspell &&
10476 compose->gtkaspell->check_while_typing)
10477 gtkaspell_highlight_all(compose->gtkaspell);
10481 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10483 Compose *compose = (Compose *)data;
10484 gint wrap_quote = prefs_common.linewrap_quote;
10485 if (compose->focused_editable
10486 #ifndef GENERIC_UMPC
10487 && gtk_widget_has_focus(compose->focused_editable)
10490 /* let text_insert() (called directly or at a later time
10491 * after the gtk_editable_paste_clipboard) know that
10492 * text is to be inserted as a quotation. implemented
10493 * by using a simple refcount... */
10494 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10495 G_OBJECT(compose->focused_editable),
10496 "paste_as_quotation"));
10497 g_object_set_data(G_OBJECT(compose->focused_editable),
10498 "paste_as_quotation",
10499 GINT_TO_POINTER(paste_as_quotation + 1));
10500 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10501 entry_paste_clipboard(compose, compose->focused_editable,
10502 prefs_common.linewrap_pastes,
10503 GDK_SELECTION_CLIPBOARD, NULL);
10504 prefs_common.linewrap_quote = wrap_quote;
10508 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10510 Compose *compose = (Compose *)data;
10511 gint prev_autowrap;
10512 GtkTextBuffer *buffer;
10514 if (compose->focused_editable
10515 #ifndef GENERIC_UMPC
10516 && gtk_widget_has_focus(compose->focused_editable)
10519 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10520 GDK_SELECTION_CLIPBOARD, NULL);
10525 #ifndef GENERIC_UMPC
10526 gtk_widget_has_focus(compose->text) &&
10528 compose->gtkaspell &&
10529 compose->gtkaspell->check_while_typing)
10530 gtkaspell_highlight_all(compose->gtkaspell);
10534 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10536 Compose *compose = (Compose *)data;
10537 gint prev_autowrap;
10538 GtkTextBuffer *buffer;
10540 if (compose->focused_editable
10541 #ifndef GENERIC_UMPC
10542 && gtk_widget_has_focus(compose->focused_editable)
10545 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10546 GDK_SELECTION_CLIPBOARD, NULL);
10551 #ifndef GENERIC_UMPC
10552 gtk_widget_has_focus(compose->text) &&
10554 compose->gtkaspell &&
10555 compose->gtkaspell->check_while_typing)
10556 gtkaspell_highlight_all(compose->gtkaspell);
10560 static void compose_allsel_cb(GtkAction *action, gpointer data)
10562 Compose *compose = (Compose *)data;
10563 if (compose->focused_editable
10564 #ifndef GENERIC_UMPC
10565 && gtk_widget_has_focus(compose->focused_editable)
10568 entry_allsel(compose->focused_editable);
10571 static void textview_move_beginning_of_line (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 gtk_text_iter_set_line_offset(&ins, 0);
10583 gtk_text_buffer_place_cursor(buffer, &ins);
10586 static void textview_move_forward_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_forward_cursor_position(&ins))
10598 gtk_text_buffer_place_cursor(buffer, &ins);
10601 static void textview_move_backward_character (GtkTextView *text)
10603 GtkTextBuffer *buffer;
10607 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10609 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10610 mark = gtk_text_buffer_get_insert(buffer);
10611 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10612 if (gtk_text_iter_backward_cursor_position(&ins))
10613 gtk_text_buffer_place_cursor(buffer, &ins);
10616 static void textview_move_forward_word (GtkTextView *text)
10618 GtkTextBuffer *buffer;
10623 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10625 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10626 mark = gtk_text_buffer_get_insert(buffer);
10627 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10628 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10629 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10630 gtk_text_iter_backward_word_start(&ins);
10631 gtk_text_buffer_place_cursor(buffer, &ins);
10635 static void textview_move_backward_word (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_backward_word_starts(&ins, 1))
10647 gtk_text_buffer_place_cursor(buffer, &ins);
10650 static void textview_move_end_of_line (GtkTextView *text)
10652 GtkTextBuffer *buffer;
10656 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10658 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10659 mark = gtk_text_buffer_get_insert(buffer);
10660 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10661 if (gtk_text_iter_forward_to_line_end(&ins))
10662 gtk_text_buffer_place_cursor(buffer, &ins);
10665 static void textview_move_next_line (GtkTextView *text)
10667 GtkTextBuffer *buffer;
10672 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10674 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10675 mark = gtk_text_buffer_get_insert(buffer);
10676 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10677 offset = gtk_text_iter_get_line_offset(&ins);
10678 if (gtk_text_iter_forward_line(&ins)) {
10679 gtk_text_iter_set_line_offset(&ins, offset);
10680 gtk_text_buffer_place_cursor(buffer, &ins);
10684 static void textview_move_previous_line (GtkTextView *text)
10686 GtkTextBuffer *buffer;
10691 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10693 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10694 mark = gtk_text_buffer_get_insert(buffer);
10695 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10696 offset = gtk_text_iter_get_line_offset(&ins);
10697 if (gtk_text_iter_backward_line(&ins)) {
10698 gtk_text_iter_set_line_offset(&ins, offset);
10699 gtk_text_buffer_place_cursor(buffer, &ins);
10703 static void textview_delete_forward_character (GtkTextView *text)
10705 GtkTextBuffer *buffer;
10707 GtkTextIter ins, end_iter;
10709 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10711 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10712 mark = gtk_text_buffer_get_insert(buffer);
10713 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10715 if (gtk_text_iter_forward_char(&end_iter)) {
10716 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10720 static void textview_delete_backward_character (GtkTextView *text)
10722 GtkTextBuffer *buffer;
10724 GtkTextIter ins, end_iter;
10726 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10728 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10729 mark = gtk_text_buffer_get_insert(buffer);
10730 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10732 if (gtk_text_iter_backward_char(&end_iter)) {
10733 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10737 static void textview_delete_forward_word (GtkTextView *text)
10739 GtkTextBuffer *buffer;
10741 GtkTextIter ins, end_iter;
10743 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10745 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10746 mark = gtk_text_buffer_get_insert(buffer);
10747 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10749 if (gtk_text_iter_forward_word_end(&end_iter)) {
10750 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10754 static void textview_delete_backward_word (GtkTextView *text)
10756 GtkTextBuffer *buffer;
10758 GtkTextIter ins, end_iter;
10760 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10762 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10763 mark = gtk_text_buffer_get_insert(buffer);
10764 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10766 if (gtk_text_iter_backward_word_start(&end_iter)) {
10767 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10771 static void textview_delete_line (GtkTextView *text)
10773 GtkTextBuffer *buffer;
10775 GtkTextIter ins, start_iter, end_iter;
10777 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10779 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10780 mark = gtk_text_buffer_get_insert(buffer);
10781 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10784 gtk_text_iter_set_line_offset(&start_iter, 0);
10787 if (gtk_text_iter_ends_line(&end_iter)){
10788 if (!gtk_text_iter_forward_char(&end_iter))
10789 gtk_text_iter_backward_char(&start_iter);
10792 gtk_text_iter_forward_to_line_end(&end_iter);
10793 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10796 static void textview_delete_to_line_end (GtkTextView *text)
10798 GtkTextBuffer *buffer;
10800 GtkTextIter ins, end_iter;
10802 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10804 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10805 mark = gtk_text_buffer_get_insert(buffer);
10806 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10808 if (gtk_text_iter_ends_line(&end_iter))
10809 gtk_text_iter_forward_char(&end_iter);
10811 gtk_text_iter_forward_to_line_end(&end_iter);
10812 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10815 #define DO_ACTION(name, act) { \
10816 if(!strcmp(name, a_name)) { \
10820 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10822 const gchar *a_name = gtk_action_get_name(action);
10823 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10824 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10825 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10826 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10827 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10828 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10829 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10830 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10831 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10832 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10833 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10834 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10835 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10836 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10840 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10842 Compose *compose = (Compose *)data;
10843 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10844 ComposeCallAdvancedAction action = -1;
10846 action = compose_call_advanced_action_from_path(gaction);
10849 void (*do_action) (GtkTextView *text);
10850 } action_table[] = {
10851 {textview_move_beginning_of_line},
10852 {textview_move_forward_character},
10853 {textview_move_backward_character},
10854 {textview_move_forward_word},
10855 {textview_move_backward_word},
10856 {textview_move_end_of_line},
10857 {textview_move_next_line},
10858 {textview_move_previous_line},
10859 {textview_delete_forward_character},
10860 {textview_delete_backward_character},
10861 {textview_delete_forward_word},
10862 {textview_delete_backward_word},
10863 {textview_delete_line},
10864 {textview_delete_to_line_end}
10867 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10869 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10870 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10871 if (action_table[action].do_action)
10872 action_table[action].do_action(text);
10874 g_warning("Not implemented yet.");
10878 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10880 GtkAllocation allocation;
10884 if (GTK_IS_EDITABLE(widget)) {
10885 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10886 gtk_editable_set_position(GTK_EDITABLE(widget),
10889 if ((parent = gtk_widget_get_parent(widget))
10890 && (parent = gtk_widget_get_parent(parent))
10891 && (parent = gtk_widget_get_parent(parent))) {
10892 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10893 gtk_widget_get_allocation(widget, &allocation);
10894 gint y = allocation.y;
10895 gint height = allocation.height;
10896 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10897 (GTK_SCROLLED_WINDOW(parent));
10899 gfloat value = gtk_adjustment_get_value(shown);
10900 gfloat upper = gtk_adjustment_get_upper(shown);
10901 gfloat page_size = gtk_adjustment_get_page_size(shown);
10902 if (y < (int)value) {
10903 gtk_adjustment_set_value(shown, y - 1);
10905 if ((y + height) > ((int)value + (int)page_size)) {
10906 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10907 gtk_adjustment_set_value(shown,
10908 y + height - (int)page_size - 1);
10910 gtk_adjustment_set_value(shown,
10911 (int)upper - (int)page_size - 1);
10918 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10919 compose->focused_editable = widget;
10921 #ifdef GENERIC_UMPC
10922 if (GTK_IS_TEXT_VIEW(widget)
10923 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10924 g_object_ref(compose->notebook);
10925 g_object_ref(compose->edit_vbox);
10926 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10927 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10928 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10929 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10930 g_object_unref(compose->notebook);
10931 g_object_unref(compose->edit_vbox);
10932 g_signal_handlers_block_by_func(G_OBJECT(widget),
10933 G_CALLBACK(compose_grab_focus_cb),
10935 gtk_widget_grab_focus(widget);
10936 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10937 G_CALLBACK(compose_grab_focus_cb),
10939 } else if (!GTK_IS_TEXT_VIEW(widget)
10940 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10941 g_object_ref(compose->notebook);
10942 g_object_ref(compose->edit_vbox);
10943 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10944 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10945 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10946 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10947 g_object_unref(compose->notebook);
10948 g_object_unref(compose->edit_vbox);
10949 g_signal_handlers_block_by_func(G_OBJECT(widget),
10950 G_CALLBACK(compose_grab_focus_cb),
10952 gtk_widget_grab_focus(widget);
10953 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10954 G_CALLBACK(compose_grab_focus_cb),
10960 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10962 compose->modified = TRUE;
10963 // compose_beautify_paragraph(compose, NULL, TRUE);
10964 #ifndef GENERIC_UMPC
10965 compose_set_title(compose);
10969 static void compose_wrap_cb(GtkAction *action, gpointer data)
10971 Compose *compose = (Compose *)data;
10972 compose_beautify_paragraph(compose, NULL, TRUE);
10975 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10977 Compose *compose = (Compose *)data;
10978 compose_wrap_all_full(compose, TRUE);
10981 static void compose_find_cb(GtkAction *action, gpointer data)
10983 Compose *compose = (Compose *)data;
10985 message_search_compose(compose);
10988 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10991 Compose *compose = (Compose *)data;
10992 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10993 if (compose->autowrap)
10994 compose_wrap_all_full(compose, TRUE);
10995 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10998 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11001 Compose *compose = (Compose *)data;
11002 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11005 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11007 Compose *compose = (Compose *)data;
11009 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11012 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11014 Compose *compose = (Compose *)data;
11016 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11019 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11021 g_free(compose->privacy_system);
11023 compose->privacy_system = g_strdup(account->default_privacy_system);
11024 compose_update_privacy_system_menu_item(compose, warn);
11027 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11029 Compose *compose = (Compose *)data;
11031 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11032 gtk_widget_show(compose->ruler_hbox);
11033 prefs_common.show_ruler = TRUE;
11035 gtk_widget_hide(compose->ruler_hbox);
11036 gtk_widget_queue_resize(compose->edit_vbox);
11037 prefs_common.show_ruler = FALSE;
11041 static void compose_attach_drag_received_cb (GtkWidget *widget,
11042 GdkDragContext *context,
11045 GtkSelectionData *data,
11048 gpointer user_data)
11050 Compose *compose = (Compose *)user_data;
11054 type = gtk_selection_data_get_data_type(data);
11055 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11057 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11059 ) && gtk_drag_get_source_widget(context) !=
11060 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11061 list = uri_list_extract_filenames(
11062 (const gchar *)gtk_selection_data_get_data(data));
11063 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11064 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11065 compose_attach_append
11066 (compose, (const gchar *)tmp->data,
11067 utf8_filename, NULL, NULL);
11068 g_free(utf8_filename);
11070 if (list) compose_changed_cb(NULL, compose);
11071 list_free_strings(list);
11073 } else if (gtk_drag_get_source_widget(context)
11074 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11075 /* comes from our summaryview */
11076 SummaryView * summaryview = NULL;
11077 GSList * list = NULL, *cur = NULL;
11079 if (mainwindow_get_mainwindow())
11080 summaryview = mainwindow_get_mainwindow()->summaryview;
11083 list = summary_get_selected_msg_list(summaryview);
11085 for (cur = list; cur; cur = cur->next) {
11086 MsgInfo *msginfo = (MsgInfo *)cur->data;
11087 gchar *file = NULL;
11089 file = procmsg_get_message_file_full(msginfo,
11092 compose_attach_append(compose, (const gchar *)file,
11093 (const gchar *)file, "message/rfc822", NULL);
11097 g_slist_free(list);
11101 static gboolean compose_drag_drop(GtkWidget *widget,
11102 GdkDragContext *drag_context,
11104 guint time, gpointer user_data)
11106 /* not handling this signal makes compose_insert_drag_received_cb
11111 static gboolean completion_set_focus_to_subject
11112 (GtkWidget *widget,
11113 GdkEventKey *event,
11116 cm_return_val_if_fail(compose != NULL, FALSE);
11118 /* make backtab move to subject field */
11119 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11120 gtk_widget_grab_focus(compose->subject_entry);
11126 static void compose_insert_drag_received_cb (GtkWidget *widget,
11127 GdkDragContext *drag_context,
11130 GtkSelectionData *data,
11133 gpointer user_data)
11135 Compose *compose = (Compose *)user_data;
11139 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11141 type = gtk_selection_data_get_data_type(data);
11143 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11145 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11147 AlertValue val = G_ALERTDEFAULT;
11148 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11150 list = uri_list_extract_filenames(ddata);
11151 if (list == NULL && strstr(ddata, "://")) {
11152 /* Assume a list of no files, and data has ://, is a remote link */
11153 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11154 gchar *tmpfile = get_tmp_file();
11155 str_write_to_file(tmpdata, tmpfile);
11157 compose_insert_file(compose, tmpfile);
11158 claws_unlink(tmpfile);
11160 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11161 compose_beautify_paragraph(compose, NULL, TRUE);
11164 switch (prefs_common.compose_dnd_mode) {
11165 case COMPOSE_DND_ASK:
11166 val = alertpanel_full(_("Insert or attach?"),
11167 _("Do you want to insert the contents of the file(s) "
11168 "into the message body, or attach it to the email?"),
11169 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11170 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11172 case COMPOSE_DND_INSERT:
11173 val = G_ALERTALTERNATE;
11175 case COMPOSE_DND_ATTACH:
11176 val = G_ALERTOTHER;
11179 /* unexpected case */
11180 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11183 if (val & G_ALERTDISABLE) {
11184 val &= ~G_ALERTDISABLE;
11185 /* remember what action to perform by default, only if we don't click Cancel */
11186 if (val == G_ALERTALTERNATE)
11187 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11188 else if (val == G_ALERTOTHER)
11189 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11192 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11193 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11194 list_free_strings(list);
11197 } else if (val == G_ALERTOTHER) {
11198 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11199 list_free_strings(list);
11204 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11205 compose_insert_file(compose, (const gchar *)tmp->data);
11207 list_free_strings(list);
11209 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11214 static void compose_header_drag_received_cb (GtkWidget *widget,
11215 GdkDragContext *drag_context,
11218 GtkSelectionData *data,
11221 gpointer user_data)
11223 GtkEditable *entry = (GtkEditable *)user_data;
11224 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11226 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11229 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11230 gchar *decoded=g_new(gchar, strlen(email));
11233 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11234 gtk_editable_delete_text(entry, 0, -1);
11235 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11236 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11240 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11243 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11245 Compose *compose = (Compose *)data;
11247 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11248 compose->return_receipt = TRUE;
11250 compose->return_receipt = FALSE;
11253 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11255 Compose *compose = (Compose *)data;
11257 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11258 compose->remove_references = TRUE;
11260 compose->remove_references = FALSE;
11263 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11264 ComposeHeaderEntry *headerentry)
11266 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11270 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11271 GdkEventKey *event,
11272 ComposeHeaderEntry *headerentry)
11274 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11275 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11276 !(event->state & GDK_MODIFIER_MASK) &&
11277 (event->keyval == GDK_KEY_BackSpace) &&
11278 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11279 gtk_container_remove
11280 (GTK_CONTAINER(headerentry->compose->header_table),
11281 headerentry->combo);
11282 gtk_container_remove
11283 (GTK_CONTAINER(headerentry->compose->header_table),
11284 headerentry->entry);
11285 headerentry->compose->header_list =
11286 g_slist_remove(headerentry->compose->header_list,
11288 g_free(headerentry);
11289 } else if (event->keyval == GDK_KEY_Tab) {
11290 if (headerentry->compose->header_last == headerentry) {
11291 /* Override default next focus, and give it to subject_entry
11292 * instead of notebook tabs
11294 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11295 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11302 static gboolean scroll_postpone(gpointer data)
11304 Compose *compose = (Compose *)data;
11306 if (compose->batch)
11309 GTK_EVENTS_FLUSH();
11310 compose_show_first_last_header(compose, FALSE);
11314 static void compose_headerentry_changed_cb(GtkWidget *entry,
11315 ComposeHeaderEntry *headerentry)
11317 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11318 compose_create_header_entry(headerentry->compose);
11319 g_signal_handlers_disconnect_matched
11320 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11321 0, 0, NULL, NULL, headerentry);
11323 if (!headerentry->compose->batch)
11324 g_timeout_add(0, scroll_postpone, headerentry->compose);
11328 static gboolean compose_defer_auto_save_draft(Compose *compose)
11330 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11331 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11335 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11337 GtkAdjustment *vadj;
11339 cm_return_if_fail(compose);
11344 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11345 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11346 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11347 gtk_widget_get_parent(compose->header_table)));
11348 gtk_adjustment_set_value(vadj, (show_first ?
11349 gtk_adjustment_get_lower(vadj) :
11350 (gtk_adjustment_get_upper(vadj) -
11351 gtk_adjustment_get_page_size(vadj))));
11352 gtk_adjustment_changed(vadj);
11355 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11356 const gchar *text, gint len, Compose *compose)
11358 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11359 (G_OBJECT(compose->text), "paste_as_quotation"));
11362 cm_return_if_fail(text != NULL);
11364 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11365 G_CALLBACK(text_inserted),
11367 if (paste_as_quotation) {
11369 const gchar *qmark;
11371 GtkTextIter start_iter;
11374 len = strlen(text);
11376 new_text = g_strndup(text, len);
11378 qmark = compose_quote_char_from_context(compose);
11380 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11381 gtk_text_buffer_place_cursor(buffer, iter);
11383 pos = gtk_text_iter_get_offset(iter);
11385 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11386 _("Quote format error at line %d."));
11387 quote_fmt_reset_vartable();
11389 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11390 GINT_TO_POINTER(paste_as_quotation - 1));
11392 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11393 gtk_text_buffer_place_cursor(buffer, iter);
11394 gtk_text_buffer_delete_mark(buffer, mark);
11396 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11397 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11398 compose_beautify_paragraph(compose, &start_iter, FALSE);
11399 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11400 gtk_text_buffer_delete_mark(buffer, mark);
11402 if (strcmp(text, "\n") || compose->automatic_break
11403 || gtk_text_iter_starts_line(iter)) {
11404 GtkTextIter before_ins;
11405 gtk_text_buffer_insert(buffer, iter, text, len);
11406 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11407 before_ins = *iter;
11408 gtk_text_iter_backward_chars(&before_ins, len);
11409 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11412 /* check if the preceding is just whitespace or quote */
11413 GtkTextIter start_line;
11414 gchar *tmp = NULL, *quote = NULL;
11415 gint quote_len = 0, is_normal = 0;
11416 start_line = *iter;
11417 gtk_text_iter_set_line_offset(&start_line, 0);
11418 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11421 if (*tmp == '\0') {
11424 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11432 gtk_text_buffer_insert(buffer, iter, text, len);
11434 gtk_text_buffer_insert_with_tags_by_name(buffer,
11435 iter, text, len, "no_join", NULL);
11440 if (!paste_as_quotation) {
11441 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11442 compose_beautify_paragraph(compose, iter, FALSE);
11443 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11444 gtk_text_buffer_delete_mark(buffer, mark);
11447 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11448 G_CALLBACK(text_inserted),
11450 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11452 if (compose_can_autosave(compose) &&
11453 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11454 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11455 compose->draft_timeout_tag = g_timeout_add
11456 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11460 static void compose_check_all(GtkAction *action, gpointer data)
11462 Compose *compose = (Compose *)data;
11463 if (!compose->gtkaspell)
11466 if (gtk_widget_has_focus(compose->subject_entry))
11467 claws_spell_entry_check_all(
11468 CLAWS_SPELL_ENTRY(compose->subject_entry));
11470 gtkaspell_check_all(compose->gtkaspell);
11473 static void compose_highlight_all(GtkAction *action, gpointer data)
11475 Compose *compose = (Compose *)data;
11476 if (compose->gtkaspell) {
11477 claws_spell_entry_recheck_all(
11478 CLAWS_SPELL_ENTRY(compose->subject_entry));
11479 gtkaspell_highlight_all(compose->gtkaspell);
11483 static void compose_check_backwards(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_backwards(
11493 CLAWS_SPELL_ENTRY(compose->subject_entry));
11495 gtkaspell_check_backwards(compose->gtkaspell);
11498 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11500 Compose *compose = (Compose *)data;
11501 if (!compose->gtkaspell) {
11502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11506 if (gtk_widget_has_focus(compose->subject_entry))
11507 claws_spell_entry_check_forwards_go(
11508 CLAWS_SPELL_ENTRY(compose->subject_entry));
11510 gtkaspell_check_forwards_go(compose->gtkaspell);
11515 *\brief Guess originating forward account from MsgInfo and several
11516 * "common preference" settings. Return NULL if no guess.
11518 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11520 PrefsAccount *account = NULL;
11522 cm_return_val_if_fail(msginfo, NULL);
11523 cm_return_val_if_fail(msginfo->folder, NULL);
11524 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11526 if (msginfo->folder->prefs->enable_default_account)
11527 account = account_find_from_id(msginfo->folder->prefs->default_account);
11530 account = msginfo->folder->folder->account;
11532 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11534 Xstrdup_a(to, msginfo->to, return NULL);
11535 extract_address(to);
11536 account = account_find_from_address(to, FALSE);
11539 if (!account && prefs_common.forward_account_autosel) {
11540 gchar cc[BUFFSIZE];
11541 if (!procheader_get_header_from_msginfo
11542 (msginfo, cc,sizeof cc , "Cc:")) {
11543 gchar *buf = cc + strlen("Cc:");
11544 extract_address(buf);
11545 account = account_find_from_address(buf, FALSE);
11549 if (!account && prefs_common.forward_account_autosel) {
11550 gchar deliveredto[BUFFSIZE];
11551 if (!procheader_get_header_from_msginfo
11552 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11553 gchar *buf = deliveredto + strlen("Delivered-To:");
11554 extract_address(buf);
11555 account = account_find_from_address(buf, FALSE);
11562 gboolean compose_close(Compose *compose)
11566 cm_return_val_if_fail(compose, FALSE);
11568 if (!g_mutex_trylock(compose->mutex)) {
11569 /* we have to wait for the (possibly deferred by auto-save)
11570 * drafting to be done, before destroying the compose under
11572 debug_print("waiting for drafting to finish...\n");
11573 compose_allow_user_actions(compose, FALSE);
11574 if (compose->close_timeout_tag == 0) {
11575 compose->close_timeout_tag =
11576 g_timeout_add (500, (GSourceFunc) compose_close,
11582 if (compose->draft_timeout_tag >= 0) {
11583 g_source_remove(compose->draft_timeout_tag);
11584 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11587 gtkut_widget_get_uposition(compose->window, &x, &y);
11588 if (!compose->batch) {
11589 prefs_common.compose_x = x;
11590 prefs_common.compose_y = y;
11592 g_mutex_unlock(compose->mutex);
11593 compose_destroy(compose);
11598 * Add entry field for each address in list.
11599 * \param compose E-Mail composition object.
11600 * \param listAddress List of (formatted) E-Mail addresses.
11602 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11605 node = listAddress;
11607 addr = ( gchar * ) node->data;
11608 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11609 node = g_list_next( node );
11613 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11614 guint action, gboolean opening_multiple)
11616 gchar *body = NULL;
11617 GSList *new_msglist = NULL;
11618 MsgInfo *tmp_msginfo = NULL;
11619 gboolean originally_enc = FALSE;
11620 gboolean originally_sig = FALSE;
11621 Compose *compose = NULL;
11622 gchar *s_system = NULL;
11624 cm_return_if_fail(msgview != NULL);
11626 cm_return_if_fail(msginfo_list != NULL);
11628 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11629 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11630 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11632 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11633 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11634 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11635 orig_msginfo, mimeinfo);
11636 if (tmp_msginfo != NULL) {
11637 new_msglist = g_slist_append(NULL, tmp_msginfo);
11639 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11640 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11641 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11643 tmp_msginfo->folder = orig_msginfo->folder;
11644 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11645 if (orig_msginfo->tags) {
11646 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11647 tmp_msginfo->folder->tags_dirty = TRUE;
11653 if (!opening_multiple)
11654 body = messageview_get_selection(msgview);
11657 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11658 procmsg_msginfo_free(tmp_msginfo);
11659 g_slist_free(new_msglist);
11661 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11663 if (compose && originally_enc) {
11664 compose_force_encryption(compose, compose->account, FALSE, s_system);
11667 if (compose && originally_sig && compose->account->default_sign_reply) {
11668 compose_force_signing(compose, compose->account, s_system);
11672 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11675 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11678 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11679 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11680 GSList *cur = msginfo_list;
11681 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11682 "messages. Opening the windows "
11683 "could take some time. Do you "
11684 "want to continue?"),
11685 g_slist_length(msginfo_list));
11686 if (g_slist_length(msginfo_list) > 9
11687 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11688 != G_ALERTALTERNATE) {
11693 /* We'll open multiple compose windows */
11694 /* let the WM place the next windows */
11695 compose_force_window_origin = FALSE;
11696 for (; cur; cur = cur->next) {
11698 tmplist.data = cur->data;
11699 tmplist.next = NULL;
11700 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11702 compose_force_window_origin = TRUE;
11704 /* forwarding multiple mails as attachments is done via a
11705 * single compose window */
11706 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11710 void compose_check_for_email_account(Compose *compose)
11712 PrefsAccount *ac = NULL, *curr = NULL;
11718 if (compose->account && compose->account->protocol == A_NNTP) {
11719 ac = account_get_cur_account();
11720 if (ac->protocol == A_NNTP) {
11721 list = account_get_list();
11723 for( ; list != NULL ; list = g_list_next(list)) {
11724 curr = (PrefsAccount *) list->data;
11725 if (curr->protocol != A_NNTP) {
11731 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11736 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11737 const gchar *address)
11739 GSList *msginfo_list = NULL;
11740 gchar *body = messageview_get_selection(msgview);
11743 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11745 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11746 compose_check_for_email_account(compose);
11747 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11748 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11749 compose_reply_set_subject(compose, msginfo);
11752 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11755 void compose_set_position(Compose *compose, gint pos)
11757 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11759 gtkut_text_view_set_position(text, pos);
11762 gboolean compose_search_string(Compose *compose,
11763 const gchar *str, gboolean case_sens)
11765 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11767 return gtkut_text_view_search_string(text, str, case_sens);
11770 gboolean compose_search_string_backward(Compose *compose,
11771 const gchar *str, gboolean case_sens)
11773 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11775 return gtkut_text_view_search_string_backward(text, str, case_sens);
11778 /* allocate a msginfo structure and populate its data from a compose data structure */
11779 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11781 MsgInfo *newmsginfo;
11783 gchar buf[BUFFSIZE];
11785 cm_return_val_if_fail( compose != NULL, NULL );
11787 newmsginfo = procmsg_msginfo_new();
11790 get_rfc822_date(buf, sizeof(buf));
11791 newmsginfo->date = g_strdup(buf);
11794 if (compose->from_name) {
11795 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11796 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11800 if (compose->subject_entry)
11801 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11803 /* to, cc, reply-to, newsgroups */
11804 for (list = compose->header_list; list; list = list->next) {
11805 gchar *header = gtk_editable_get_chars(
11807 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11808 gchar *entry = gtk_editable_get_chars(
11809 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11811 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11812 if ( newmsginfo->to == NULL ) {
11813 newmsginfo->to = g_strdup(entry);
11814 } else if (entry && *entry) {
11815 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11816 g_free(newmsginfo->to);
11817 newmsginfo->to = tmp;
11820 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11821 if ( newmsginfo->cc == NULL ) {
11822 newmsginfo->cc = g_strdup(entry);
11823 } else if (entry && *entry) {
11824 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11825 g_free(newmsginfo->cc);
11826 newmsginfo->cc = tmp;
11829 if ( strcasecmp(header,
11830 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11831 if ( newmsginfo->newsgroups == NULL ) {
11832 newmsginfo->newsgroups = g_strdup(entry);
11833 } else if (entry && *entry) {
11834 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11835 g_free(newmsginfo->newsgroups);
11836 newmsginfo->newsgroups = tmp;
11844 /* other data is unset */
11850 /* update compose's dictionaries from folder dict settings */
11851 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11852 FolderItem *folder_item)
11854 cm_return_if_fail(compose != NULL);
11856 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11857 FolderItemPrefs *prefs = folder_item->prefs;
11859 if (prefs->enable_default_dictionary)
11860 gtkaspell_change_dict(compose->gtkaspell,
11861 prefs->default_dictionary, FALSE);
11862 if (folder_item->prefs->enable_default_alt_dictionary)
11863 gtkaspell_change_alt_dict(compose->gtkaspell,
11864 prefs->default_alt_dictionary);
11865 if (prefs->enable_default_dictionary
11866 || prefs->enable_default_alt_dictionary)
11867 compose_spell_menu_changed(compose);
11872 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11874 Compose *compose = (Compose *)data;
11876 cm_return_if_fail(compose != NULL);
11878 gtk_widget_grab_focus(compose->text);
11881 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
11883 gtk_combo_box_popup(GTK_COMBO_BOX(data));