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);
566 static GtkActionEntry compose_popup_entries[] =
568 {"Compose", NULL, "Compose" },
569 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
570 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
571 {"Compose/---", NULL, "---", NULL, NULL, NULL },
572 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
575 static GtkActionEntry compose_entries[] =
577 {"Menu", NULL, "Menu" },
579 {"Message", NULL, N_("_Message") },
580 {"Edit", NULL, N_("_Edit") },
582 {"Spelling", NULL, N_("_Spelling") },
584 {"Options", NULL, N_("_Options") },
585 {"Tools", NULL, N_("_Tools") },
586 {"Help", NULL, N_("_Help") },
588 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
589 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
590 {"Message/---", NULL, "---" },
592 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
593 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
594 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
595 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
596 /* {"Message/---", NULL, "---" }, */
597 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
598 /* {"Message/---", NULL, "---" }, */
599 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
600 /* {"Message/---", NULL, "---" }, */
601 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
604 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
605 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
606 {"Edit/---", NULL, "---" },
608 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
609 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
610 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
612 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
613 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
614 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
615 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
617 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
619 {"Edit/Advanced", NULL, N_("A_dvanced") },
620 {"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*/
621 {"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*/
622 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
623 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
624 {"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*/
625 {"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*/
626 {"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*/
627 {"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*/
628 {"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*/
629 {"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*/
630 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
631 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
632 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
633 {"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*/
635 /* {"Edit/---", NULL, "---" }, */
636 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
638 /* {"Edit/---", NULL, "---" }, */
639 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
640 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
641 /* {"Edit/---", NULL, "---" }, */
642 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
645 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
646 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
647 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
648 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
650 {"Spelling/---", NULL, "---" },
651 {"Spelling/Options", NULL, N_("_Options") },
656 {"Options/ReplyMode", NULL, N_("Reply _mode") },
657 {"Options/---", NULL, "---" },
658 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
659 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
661 /* {"Options/---", NULL, "---" }, */
663 {"Options/Priority", NULL, N_("_Priority") },
665 {"Options/Encoding", NULL, N_("Character _encoding") },
666 {"Options/Encoding/---", NULL, "---" },
667 #define ENC_ACTION(cs_char,c_char,string) \
668 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
670 {"Options/Encoding/Western", NULL, N_("Western European") },
671 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
672 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
673 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
674 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
675 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
676 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
677 {"Options/Encoding/Korean", NULL, N_("Korean") },
678 {"Options/Encoding/Thai", NULL, N_("Thai") },
681 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
683 {"Tools/Template", NULL, N_("_Template") },
684 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
685 {"Tools/Actions", NULL, N_("Actio_ns") },
686 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
689 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
692 static GtkToggleActionEntry compose_toggle_entries[] =
694 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
695 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
696 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
697 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
698 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
699 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
700 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
703 static GtkRadioActionEntry compose_radio_rm_entries[] =
705 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
706 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
707 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
708 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
711 static GtkRadioActionEntry compose_radio_prio_entries[] =
713 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
714 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
716 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
717 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
720 static GtkRadioActionEntry compose_radio_enc_entries[] =
722 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
756 static GtkTargetEntry compose_mime_types[] =
758 {"text/uri-list", 0, 0},
759 {"UTF8_STRING", 0, 0},
763 static gboolean compose_put_existing_to_front(MsgInfo *info)
765 const GList *compose_list = compose_get_compose_list();
766 const GList *elem = NULL;
769 for (elem = compose_list; elem != NULL && elem->data != NULL;
771 Compose *c = (Compose*)elem->data;
773 if (!c->targetinfo || !c->targetinfo->msgid ||
777 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
778 gtkut_window_popup(c->window);
786 static GdkColor quote_color1 =
787 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
788 static GdkColor quote_color2 =
789 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
790 static GdkColor quote_color3 =
791 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
793 static GdkColor quote_bgcolor1 =
794 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor quote_bgcolor2 =
796 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
797 static GdkColor quote_bgcolor3 =
798 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
800 static GdkColor signature_color = {
807 static GdkColor uri_color = {
814 static void compose_create_tags(GtkTextView *text, Compose *compose)
816 GtkTextBuffer *buffer;
817 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 #if !GTK_CHECK_VERSION(2, 24, 0)
825 buffer = gtk_text_view_get_buffer(text);
827 if (prefs_common.enable_color) {
828 /* grab the quote colors, converting from an int to a GdkColor */
829 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
831 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
833 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
835 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
837 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
839 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
841 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
843 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
846 signature_color = quote_color1 = quote_color2 = quote_color3 =
847 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
850 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
851 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
852 "foreground-gdk", "e_color1,
853 "paragraph-background-gdk", "e_bgcolor1,
855 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
856 "foreground-gdk", "e_color2,
857 "paragraph-background-gdk", "e_bgcolor2,
859 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
860 "foreground-gdk", "e_color3,
861 "paragraph-background-gdk", "e_bgcolor3,
864 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
865 "foreground-gdk", "e_color1,
867 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
868 "foreground-gdk", "e_color2,
870 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
871 "foreground-gdk", "e_color3,
875 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
876 "foreground-gdk", &signature_color,
879 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
880 "foreground-gdk", &uri_color,
882 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
883 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
885 #if !GTK_CHECK_VERSION(2, 24, 0)
886 color[0] = quote_color1;
887 color[1] = quote_color2;
888 color[2] = quote_color3;
889 color[3] = quote_bgcolor1;
890 color[4] = quote_bgcolor2;
891 color[5] = quote_bgcolor3;
892 color[6] = signature_color;
893 color[7] = uri_color;
895 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
896 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
898 for (i = 0; i < 8; i++) {
899 if (success[i] == FALSE) {
900 g_warning("Compose: color allocation failed.\n");
901 quote_color1 = quote_color2 = quote_color3 =
902 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
903 signature_color = uri_color = black;
909 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
912 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
915 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
917 return compose_generic_new(account, mailto, item, NULL, NULL);
920 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
922 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
925 #define SCROLL_TO_CURSOR(compose) { \
926 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
927 gtk_text_view_get_buffer( \
928 GTK_TEXT_VIEW(compose->text))); \
929 gtk_text_view_scroll_mark_onscreen( \
930 GTK_TEXT_VIEW(compose->text), \
934 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
937 if (folderidentifier) {
938 #if !GTK_CHECK_VERSION(2, 24, 0)
939 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
941 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
943 prefs_common.compose_save_to_history = add_history(
944 prefs_common.compose_save_to_history, folderidentifier);
945 #if !GTK_CHECK_VERSION(2, 24, 0)
946 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
947 prefs_common.compose_save_to_history);
949 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
950 prefs_common.compose_save_to_history);
954 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
955 if (folderidentifier)
956 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
958 gtk_entry_set_text(GTK_ENTRY(entry), "");
961 static gchar *compose_get_save_to(Compose *compose)
964 gchar *result = NULL;
965 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
966 result = gtk_editable_get_chars(entry, 0, -1);
969 #if !GTK_CHECK_VERSION(2, 24, 0)
970 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
972 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
974 prefs_common.compose_save_to_history = add_history(
975 prefs_common.compose_save_to_history, result);
976 #if !GTK_CHECK_VERSION(2, 24, 0)
977 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
978 prefs_common.compose_save_to_history);
980 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
981 prefs_common.compose_save_to_history);
987 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
988 GList *attach_files, GList *listAddress )
991 GtkTextView *textview;
992 GtkTextBuffer *textbuf;
994 const gchar *subject_format = NULL;
995 const gchar *body_format = NULL;
996 gchar *mailto_from = NULL;
997 PrefsAccount *mailto_account = NULL;
998 MsgInfo* dummyinfo = NULL;
999 gint cursor_pos = -1;
1000 MailField mfield = NO_FIELD_PRESENT;
1004 /* check if mailto defines a from */
1005 if (mailto && *mailto != '\0') {
1006 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1007 /* mailto defines a from, check if we can get account prefs from it,
1008 if not, the account prefs will be guessed using other ways, but we'll keep
1011 mailto_account = account_find_from_address(mailto_from, TRUE);
1012 if (mailto_account == NULL) {
1014 Xstrdup_a(tmp_from, mailto_from, return NULL);
1015 extract_address(tmp_from);
1016 mailto_account = account_find_from_address(tmp_from, TRUE);
1020 account = mailto_account;
1023 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1024 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1025 account = account_find_from_id(item->prefs->default_account);
1027 /* if no account prefs set, fallback to the current one */
1028 if (!account) account = cur_account;
1029 cm_return_val_if_fail(account != NULL, NULL);
1031 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1033 /* override from name if mailto asked for it */
1035 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1036 g_free(mailto_from);
1038 /* override from name according to folder properties */
1039 if (item && item->prefs &&
1040 item->prefs->compose_with_format &&
1041 item->prefs->compose_override_from_format &&
1042 *item->prefs->compose_override_from_format != '\0') {
1047 dummyinfo = compose_msginfo_new_from_compose(compose);
1049 /* decode \-escape sequences in the internal representation of the quote format */
1050 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1051 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1054 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1055 compose->gtkaspell);
1057 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1059 quote_fmt_scan_string(tmp);
1062 buf = quote_fmt_get_buffer();
1064 alertpanel_error(_("New message From format error."));
1066 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1067 quote_fmt_reset_vartable();
1072 compose->replyinfo = NULL;
1073 compose->fwdinfo = NULL;
1075 textview = GTK_TEXT_VIEW(compose->text);
1076 textbuf = gtk_text_view_get_buffer(textview);
1077 compose_create_tags(textview, compose);
1079 undo_block(compose->undostruct);
1081 compose_set_dictionaries_from_folder_prefs(compose, item);
1084 if (account->auto_sig)
1085 compose_insert_sig(compose, FALSE);
1086 gtk_text_buffer_get_start_iter(textbuf, &iter);
1087 gtk_text_buffer_place_cursor(textbuf, &iter);
1089 if (account->protocol != A_NNTP) {
1090 if (mailto && *mailto != '\0') {
1091 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1094 compose_set_folder_prefs(compose, item, TRUE);
1096 if (item && item->ret_rcpt) {
1097 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1100 if (mailto && *mailto != '\0') {
1101 if (!strchr(mailto, '@'))
1102 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1104 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1105 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1106 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1107 mfield = TO_FIELD_PRESENT;
1110 * CLAWS: just don't allow return receipt request, even if the user
1111 * may want to send an email. simple but foolproof.
1113 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1115 compose_add_field_list( compose, listAddress );
1117 if (item && item->prefs && item->prefs->compose_with_format) {
1118 subject_format = item->prefs->compose_subject_format;
1119 body_format = item->prefs->compose_body_format;
1120 } else if (account->compose_with_format) {
1121 subject_format = account->compose_subject_format;
1122 body_format = account->compose_body_format;
1123 } else if (prefs_common.compose_with_format) {
1124 subject_format = prefs_common.compose_subject_format;
1125 body_format = prefs_common.compose_body_format;
1128 if (subject_format || body_format) {
1131 && *subject_format != '\0' )
1133 gchar *subject = NULL;
1138 dummyinfo = compose_msginfo_new_from_compose(compose);
1140 /* decode \-escape sequences in the internal representation of the quote format */
1141 tmp = g_malloc(strlen(subject_format)+1);
1142 pref_get_unescaped_pref(tmp, subject_format);
1144 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1146 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1147 compose->gtkaspell);
1149 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1151 quote_fmt_scan_string(tmp);
1154 buf = quote_fmt_get_buffer();
1156 alertpanel_error(_("New message subject format error."));
1158 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1159 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1160 quote_fmt_reset_vartable();
1164 mfield = SUBJECT_FIELD_PRESENT;
1168 && *body_format != '\0' )
1171 GtkTextBuffer *buffer;
1172 GtkTextIter start, end;
1176 dummyinfo = compose_msginfo_new_from_compose(compose);
1178 text = GTK_TEXT_VIEW(compose->text);
1179 buffer = gtk_text_view_get_buffer(text);
1180 gtk_text_buffer_get_start_iter(buffer, &start);
1181 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1182 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1184 compose_quote_fmt(compose, dummyinfo,
1186 NULL, tmp, FALSE, TRUE,
1187 _("The body of the \"New message\" template has an error at line %d."));
1188 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1189 quote_fmt_reset_vartable();
1193 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1194 gtkaspell_highlight_all(compose->gtkaspell);
1196 mfield = BODY_FIELD_PRESENT;
1200 procmsg_msginfo_free( dummyinfo );
1206 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1207 ainfo = (AttachInfo *) curr->data;
1208 compose_attach_append(compose, ainfo->file, ainfo->file,
1209 ainfo->content_type, ainfo->charset);
1213 compose_show_first_last_header(compose, TRUE);
1215 /* Set save folder */
1216 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1217 gchar *folderidentifier;
1219 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1220 folderidentifier = folder_item_get_identifier(item);
1221 compose_set_save_to(compose, folderidentifier);
1222 g_free(folderidentifier);
1225 /* Place cursor according to provided input (mfield) */
1227 case NO_FIELD_PRESENT:
1228 if (compose->header_last)
1229 gtk_widget_grab_focus(compose->header_last->entry);
1231 case TO_FIELD_PRESENT:
1232 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1234 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1237 gtk_widget_grab_focus(compose->subject_entry);
1239 case SUBJECT_FIELD_PRESENT:
1240 textview = GTK_TEXT_VIEW(compose->text);
1243 textbuf = gtk_text_view_get_buffer(textview);
1246 mark = gtk_text_buffer_get_insert(textbuf);
1247 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1248 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1250 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1251 * only defers where it comes to the variable body
1252 * is not null. If no body is present compose->text
1253 * will be null in which case you cannot place the
1254 * cursor inside the component so. An empty component
1255 * is therefore created before placing the cursor
1257 case BODY_FIELD_PRESENT:
1258 cursor_pos = quote_fmt_get_cursor_pos();
1259 if (cursor_pos == -1)
1260 gtk_widget_grab_focus(compose->header_last->entry);
1262 gtk_widget_grab_focus(compose->text);
1266 undo_unblock(compose->undostruct);
1268 if (prefs_common.auto_exteditor)
1269 compose_exec_ext_editor(compose);
1271 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1273 SCROLL_TO_CURSOR(compose);
1275 compose->modified = FALSE;
1276 compose_set_title(compose);
1278 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1283 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1284 gboolean override_pref, const gchar *system)
1286 const gchar *privacy = NULL;
1288 cm_return_if_fail(compose != NULL);
1289 cm_return_if_fail(account != NULL);
1291 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1294 if (account->default_privacy_system && strlen(account->default_privacy_system))
1295 privacy = account->default_privacy_system;
1299 GSList *privacy_avail = privacy_get_system_ids();
1300 if (privacy_avail && g_slist_length(privacy_avail)) {
1301 privacy = (gchar *)(privacy_avail->data);
1304 if (privacy != NULL) {
1306 g_free(compose->privacy_system);
1307 compose->privacy_system = NULL;
1309 if (compose->privacy_system == NULL)
1310 compose->privacy_system = g_strdup(privacy);
1311 else if (*(compose->privacy_system) == '\0') {
1312 g_free(compose->privacy_system);
1313 compose->privacy_system = g_strdup(privacy);
1315 compose_update_privacy_system_menu_item(compose, FALSE);
1316 compose_use_encryption(compose, TRUE);
1320 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1322 const gchar *privacy = NULL;
1324 if (account->default_privacy_system && strlen(account->default_privacy_system))
1325 privacy = account->default_privacy_system;
1329 GSList *privacy_avail = privacy_get_system_ids();
1330 if (privacy_avail && g_slist_length(privacy_avail)) {
1331 privacy = (gchar *)(privacy_avail->data);
1335 if (privacy != NULL) {
1337 g_free(compose->privacy_system);
1338 compose->privacy_system = NULL;
1340 if (compose->privacy_system == NULL)
1341 compose->privacy_system = g_strdup(privacy);
1342 compose_update_privacy_system_menu_item(compose, FALSE);
1343 compose_use_signing(compose, TRUE);
1347 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1351 Compose *compose = NULL;
1353 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1355 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1356 cm_return_val_if_fail(msginfo != NULL, NULL);
1358 list_len = g_slist_length(msginfo_list);
1362 case COMPOSE_REPLY_TO_ADDRESS:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1364 FALSE, prefs_common.default_reply_list, FALSE, body);
1366 case COMPOSE_REPLY_WITH_QUOTE:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1368 FALSE, prefs_common.default_reply_list, FALSE, body);
1370 case COMPOSE_REPLY_WITHOUT_QUOTE:
1371 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1372 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1374 case COMPOSE_REPLY_TO_SENDER:
1375 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1376 FALSE, FALSE, TRUE, body);
1378 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1379 compose = compose_followup_and_reply_to(msginfo,
1380 COMPOSE_QUOTE_CHECK,
1381 FALSE, FALSE, body);
1383 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1384 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1385 FALSE, FALSE, TRUE, body);
1387 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1388 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1389 FALSE, FALSE, TRUE, NULL);
1391 case COMPOSE_REPLY_TO_ALL:
1392 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1393 TRUE, FALSE, FALSE, body);
1395 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1396 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1397 TRUE, FALSE, FALSE, body);
1399 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1400 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1401 TRUE, FALSE, FALSE, NULL);
1403 case COMPOSE_REPLY_TO_LIST:
1404 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1405 FALSE, TRUE, FALSE, body);
1407 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1408 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1409 FALSE, TRUE, FALSE, body);
1411 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1412 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1413 FALSE, TRUE, FALSE, NULL);
1415 case COMPOSE_FORWARD:
1416 if (prefs_common.forward_as_attachment) {
1417 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1420 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1424 case COMPOSE_FORWARD_INLINE:
1425 /* check if we reply to more than one Message */
1426 if (list_len == 1) {
1427 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1430 /* more messages FALL THROUGH */
1431 case COMPOSE_FORWARD_AS_ATTACH:
1432 compose = compose_forward_multiple(NULL, msginfo_list);
1434 case COMPOSE_REDIRECT:
1435 compose = compose_redirect(NULL, msginfo, FALSE);
1438 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1441 if (compose == NULL) {
1442 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1446 compose->rmode = mode;
1447 switch (compose->rmode) {
1449 case COMPOSE_REPLY_WITH_QUOTE:
1450 case COMPOSE_REPLY_WITHOUT_QUOTE:
1451 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1452 debug_print("reply mode Normal\n");
1453 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1454 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1456 case COMPOSE_REPLY_TO_SENDER:
1457 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1458 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1459 debug_print("reply mode Sender\n");
1460 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1462 case COMPOSE_REPLY_TO_ALL:
1463 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1464 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1465 debug_print("reply mode All\n");
1466 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1468 case COMPOSE_REPLY_TO_LIST:
1469 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1470 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1471 debug_print("reply mode List\n");
1472 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1474 case COMPOSE_REPLY_TO_ADDRESS:
1475 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1483 static Compose *compose_reply(MsgInfo *msginfo,
1484 ComposeQuoteMode quote_mode,
1490 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1491 to_sender, FALSE, body);
1494 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1495 ComposeQuoteMode quote_mode,
1500 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1501 to_sender, TRUE, body);
1504 static void compose_extract_original_charset(Compose *compose)
1506 MsgInfo *info = NULL;
1507 if (compose->replyinfo) {
1508 info = compose->replyinfo;
1509 } else if (compose->fwdinfo) {
1510 info = compose->fwdinfo;
1511 } else if (compose->targetinfo) {
1512 info = compose->targetinfo;
1515 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1516 MimeInfo *partinfo = mimeinfo;
1517 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1518 partinfo = procmime_mimeinfo_next(partinfo);
1520 compose->orig_charset =
1521 g_strdup(procmime_mimeinfo_get_parameter(
1522 partinfo, "charset"));
1524 procmime_mimeinfo_free_all(mimeinfo);
1528 #define SIGNAL_BLOCK(buffer) { \
1529 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1530 G_CALLBACK(compose_changed_cb), \
1532 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1533 G_CALLBACK(text_inserted), \
1537 #define SIGNAL_UNBLOCK(buffer) { \
1538 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1539 G_CALLBACK(compose_changed_cb), \
1541 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1542 G_CALLBACK(text_inserted), \
1546 static Compose *compose_generic_reply(MsgInfo *msginfo,
1547 ComposeQuoteMode quote_mode,
1548 gboolean to_all, gboolean to_ml,
1550 gboolean followup_and_reply_to,
1554 PrefsAccount *account = NULL;
1555 GtkTextView *textview;
1556 GtkTextBuffer *textbuf;
1557 gboolean quote = FALSE;
1558 const gchar *qmark = NULL;
1559 const gchar *body_fmt = NULL;
1560 gchar *s_system = NULL;
1562 cm_return_val_if_fail(msginfo != NULL, NULL);
1563 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1565 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1567 cm_return_val_if_fail(account != NULL, NULL);
1569 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1571 compose->updating = TRUE;
1573 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1574 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1576 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1577 if (!compose->replyinfo)
1578 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1580 compose_extract_original_charset(compose);
1582 if (msginfo->folder && msginfo->folder->ret_rcpt)
1583 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1585 /* Set save folder */
1586 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1587 gchar *folderidentifier;
1589 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1590 folderidentifier = folder_item_get_identifier(msginfo->folder);
1591 compose_set_save_to(compose, folderidentifier);
1592 g_free(folderidentifier);
1595 if (compose_parse_header(compose, msginfo) < 0) {
1596 compose->updating = FALSE;
1597 compose_destroy(compose);
1601 /* override from name according to folder properties */
1602 if (msginfo->folder && msginfo->folder->prefs &&
1603 msginfo->folder->prefs->reply_with_format &&
1604 msginfo->folder->prefs->reply_override_from_format &&
1605 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1610 /* decode \-escape sequences in the internal representation of the quote format */
1611 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1612 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1615 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1616 compose->gtkaspell);
1618 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1620 quote_fmt_scan_string(tmp);
1623 buf = quote_fmt_get_buffer();
1625 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1627 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1628 quote_fmt_reset_vartable();
1633 textview = (GTK_TEXT_VIEW(compose->text));
1634 textbuf = gtk_text_view_get_buffer(textview);
1635 compose_create_tags(textview, compose);
1637 undo_block(compose->undostruct);
1639 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1640 gtkaspell_block_check(compose->gtkaspell);
1643 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1644 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1645 /* use the reply format of folder (if enabled), or the account's one
1646 (if enabled) or fallback to the global reply format, which is always
1647 enabled (even if empty), and use the relevant quotemark */
1649 if (msginfo->folder && msginfo->folder->prefs &&
1650 msginfo->folder->prefs->reply_with_format) {
1651 qmark = msginfo->folder->prefs->reply_quotemark;
1652 body_fmt = msginfo->folder->prefs->reply_body_format;
1654 } else if (account->reply_with_format) {
1655 qmark = account->reply_quotemark;
1656 body_fmt = account->reply_body_format;
1659 qmark = prefs_common.quotemark;
1660 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1661 body_fmt = gettext(prefs_common.quotefmt);
1668 /* empty quotemark is not allowed */
1669 if (qmark == NULL || *qmark == '\0')
1671 compose_quote_fmt(compose, compose->replyinfo,
1672 body_fmt, qmark, body, FALSE, TRUE,
1673 _("The body of the \"Reply\" template has an error at line %d."));
1674 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1675 quote_fmt_reset_vartable();
1678 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1679 compose_force_encryption(compose, account, FALSE, s_system);
1682 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1683 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1684 compose_force_signing(compose, account, s_system);
1688 SIGNAL_BLOCK(textbuf);
1690 if (account->auto_sig)
1691 compose_insert_sig(compose, FALSE);
1693 compose_wrap_all(compose);
1696 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1697 gtkaspell_highlight_all(compose->gtkaspell);
1698 gtkaspell_unblock_check(compose->gtkaspell);
1700 SIGNAL_UNBLOCK(textbuf);
1702 gtk_widget_grab_focus(compose->text);
1704 undo_unblock(compose->undostruct);
1706 if (prefs_common.auto_exteditor)
1707 compose_exec_ext_editor(compose);
1709 compose->modified = FALSE;
1710 compose_set_title(compose);
1712 compose->updating = FALSE;
1713 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1714 SCROLL_TO_CURSOR(compose);
1716 if (compose->deferred_destroy) {
1717 compose_destroy(compose);
1725 #define INSERT_FW_HEADER(var, hdr) \
1726 if (msginfo->var && *msginfo->var) { \
1727 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1728 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1732 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1733 gboolean as_attach, const gchar *body,
1734 gboolean no_extedit,
1738 GtkTextView *textview;
1739 GtkTextBuffer *textbuf;
1740 gint cursor_pos = -1;
1743 cm_return_val_if_fail(msginfo != NULL, NULL);
1744 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1747 !(account = compose_guess_forward_account_from_msginfo
1749 account = cur_account;
1751 if (!prefs_common.forward_as_attachment)
1752 mode = COMPOSE_FORWARD_INLINE;
1754 mode = COMPOSE_FORWARD;
1755 compose = compose_create(account, msginfo->folder, mode, batch);
1757 compose->updating = TRUE;
1758 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1759 if (!compose->fwdinfo)
1760 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1762 compose_extract_original_charset(compose);
1764 if (msginfo->subject && *msginfo->subject) {
1765 gchar *buf, *buf2, *p;
1767 buf = p = g_strdup(msginfo->subject);
1768 p += subject_get_prefix_length(p);
1769 memmove(buf, p, strlen(p) + 1);
1771 buf2 = g_strdup_printf("Fw: %s", buf);
1772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1778 /* override from name according to folder properties */
1779 if (msginfo->folder && msginfo->folder->prefs &&
1780 msginfo->folder->prefs->forward_with_format &&
1781 msginfo->folder->prefs->forward_override_from_format &&
1782 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1786 MsgInfo *full_msginfo = NULL;
1789 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1791 full_msginfo = procmsg_msginfo_copy(msginfo);
1793 /* decode \-escape sequences in the internal representation of the quote format */
1794 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1795 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1798 gtkaspell_block_check(compose->gtkaspell);
1799 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1800 compose->gtkaspell);
1802 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1804 quote_fmt_scan_string(tmp);
1807 buf = quote_fmt_get_buffer();
1809 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1811 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1812 quote_fmt_reset_vartable();
1815 procmsg_msginfo_free(full_msginfo);
1818 textview = GTK_TEXT_VIEW(compose->text);
1819 textbuf = gtk_text_view_get_buffer(textview);
1820 compose_create_tags(textview, compose);
1822 undo_block(compose->undostruct);
1826 msgfile = procmsg_get_message_file(msginfo);
1827 if (!is_file_exist(msgfile))
1828 g_warning("%s: file not exist\n", msgfile);
1830 compose_attach_append(compose, msgfile, msgfile,
1831 "message/rfc822", NULL);
1835 const gchar *qmark = NULL;
1836 const gchar *body_fmt = NULL;
1837 MsgInfo *full_msginfo;
1839 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1841 full_msginfo = procmsg_msginfo_copy(msginfo);
1843 /* use the forward format of folder (if enabled), or the account's one
1844 (if enabled) or fallback to the global forward format, which is always
1845 enabled (even if empty), and use the relevant quotemark */
1846 if (msginfo->folder && msginfo->folder->prefs &&
1847 msginfo->folder->prefs->forward_with_format) {
1848 qmark = msginfo->folder->prefs->forward_quotemark;
1849 body_fmt = msginfo->folder->prefs->forward_body_format;
1851 } else if (account->forward_with_format) {
1852 qmark = account->forward_quotemark;
1853 body_fmt = account->forward_body_format;
1856 qmark = prefs_common.fw_quotemark;
1857 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1858 body_fmt = gettext(prefs_common.fw_quotefmt);
1863 /* empty quotemark is not allowed */
1864 if (qmark == NULL || *qmark == '\0')
1867 compose_quote_fmt(compose, full_msginfo,
1868 body_fmt, qmark, body, FALSE, TRUE,
1869 _("The body of the \"Forward\" template has an error at line %d."));
1870 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1871 quote_fmt_reset_vartable();
1872 compose_attach_parts(compose, msginfo);
1874 procmsg_msginfo_free(full_msginfo);
1877 SIGNAL_BLOCK(textbuf);
1879 if (account->auto_sig)
1880 compose_insert_sig(compose, FALSE);
1882 compose_wrap_all(compose);
1885 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1886 gtkaspell_highlight_all(compose->gtkaspell);
1887 gtkaspell_unblock_check(compose->gtkaspell);
1889 SIGNAL_UNBLOCK(textbuf);
1891 cursor_pos = quote_fmt_get_cursor_pos();
1892 if (cursor_pos == -1)
1893 gtk_widget_grab_focus(compose->header_last->entry);
1895 gtk_widget_grab_focus(compose->text);
1897 if (!no_extedit && prefs_common.auto_exteditor)
1898 compose_exec_ext_editor(compose);
1901 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1902 gchar *folderidentifier;
1904 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1905 folderidentifier = folder_item_get_identifier(msginfo->folder);
1906 compose_set_save_to(compose, folderidentifier);
1907 g_free(folderidentifier);
1910 undo_unblock(compose->undostruct);
1912 compose->modified = FALSE;
1913 compose_set_title(compose);
1915 compose->updating = FALSE;
1916 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1917 SCROLL_TO_CURSOR(compose);
1919 if (compose->deferred_destroy) {
1920 compose_destroy(compose);
1924 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1929 #undef INSERT_FW_HEADER
1931 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1934 GtkTextView *textview;
1935 GtkTextBuffer *textbuf;
1939 gboolean single_mail = TRUE;
1941 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1943 if (g_slist_length(msginfo_list) > 1)
1944 single_mail = FALSE;
1946 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1947 if (((MsgInfo *)msginfo->data)->folder == NULL)
1950 /* guess account from first selected message */
1952 !(account = compose_guess_forward_account_from_msginfo
1953 (msginfo_list->data)))
1954 account = cur_account;
1956 cm_return_val_if_fail(account != NULL, NULL);
1958 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1959 if (msginfo->data) {
1960 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1961 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1965 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1966 g_warning("no msginfo_list");
1970 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1972 compose->updating = TRUE;
1974 /* override from name according to folder properties */
1975 if (msginfo_list->data) {
1976 MsgInfo *msginfo = msginfo_list->data;
1978 if (msginfo->folder && msginfo->folder->prefs &&
1979 msginfo->folder->prefs->forward_with_format &&
1980 msginfo->folder->prefs->forward_override_from_format &&
1981 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1986 /* decode \-escape sequences in the internal representation of the quote format */
1987 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1988 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1991 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1992 compose->gtkaspell);
1994 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1996 quote_fmt_scan_string(tmp);
1999 buf = quote_fmt_get_buffer();
2001 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2003 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2004 quote_fmt_reset_vartable();
2010 textview = GTK_TEXT_VIEW(compose->text);
2011 textbuf = gtk_text_view_get_buffer(textview);
2012 compose_create_tags(textview, compose);
2014 undo_block(compose->undostruct);
2015 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2016 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2018 if (!is_file_exist(msgfile))
2019 g_warning("%s: file not exist\n", msgfile);
2021 compose_attach_append(compose, msgfile, msgfile,
2022 "message/rfc822", NULL);
2027 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2028 if (info->subject && *info->subject) {
2029 gchar *buf, *buf2, *p;
2031 buf = p = g_strdup(info->subject);
2032 p += subject_get_prefix_length(p);
2033 memmove(buf, p, strlen(p) + 1);
2035 buf2 = g_strdup_printf("Fw: %s", buf);
2036 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2042 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2043 _("Fw: multiple emails"));
2046 SIGNAL_BLOCK(textbuf);
2048 if (account->auto_sig)
2049 compose_insert_sig(compose, FALSE);
2051 compose_wrap_all(compose);
2053 SIGNAL_UNBLOCK(textbuf);
2055 gtk_text_buffer_get_start_iter(textbuf, &iter);
2056 gtk_text_buffer_place_cursor(textbuf, &iter);
2058 gtk_widget_grab_focus(compose->header_last->entry);
2059 undo_unblock(compose->undostruct);
2060 compose->modified = FALSE;
2061 compose_set_title(compose);
2063 compose->updating = FALSE;
2064 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2065 SCROLL_TO_CURSOR(compose);
2067 if (compose->deferred_destroy) {
2068 compose_destroy(compose);
2072 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2077 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2079 GtkTextIter start = *iter;
2080 GtkTextIter end_iter;
2081 int start_pos = gtk_text_iter_get_offset(&start);
2083 if (!compose->account->sig_sep)
2086 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2087 start_pos+strlen(compose->account->sig_sep));
2089 /* check sig separator */
2090 str = gtk_text_iter_get_text(&start, &end_iter);
2091 if (!strcmp(str, compose->account->sig_sep)) {
2093 /* check end of line (\n) */
2094 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2095 start_pos+strlen(compose->account->sig_sep));
2096 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2097 start_pos+strlen(compose->account->sig_sep)+1);
2098 tmp = gtk_text_iter_get_text(&start, &end_iter);
2099 if (!strcmp(tmp,"\n")) {
2111 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2113 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2114 Compose *compose = (Compose *)data;
2115 FolderItem *old_item = NULL;
2116 FolderItem *new_item = NULL;
2117 gchar *old_id, *new_id;
2119 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2120 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2123 old_item = hookdata->item;
2124 new_item = hookdata->item2;
2126 old_id = folder_item_get_identifier(old_item);
2127 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2129 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2130 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2131 compose->targetinfo->folder = new_item;
2134 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2135 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2136 compose->replyinfo->folder = new_item;
2139 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2140 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2141 compose->fwdinfo->folder = new_item;
2149 static void compose_colorize_signature(Compose *compose)
2151 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2153 GtkTextIter end_iter;
2154 gtk_text_buffer_get_start_iter(buffer, &iter);
2155 while (gtk_text_iter_forward_line(&iter))
2156 if (compose_is_sig_separator(compose, buffer, &iter)) {
2157 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2158 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2162 #define BLOCK_WRAP() { \
2163 prev_autowrap = compose->autowrap; \
2164 buffer = gtk_text_view_get_buffer( \
2165 GTK_TEXT_VIEW(compose->text)); \
2166 compose->autowrap = FALSE; \
2168 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2169 G_CALLBACK(compose_changed_cb), \
2171 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2172 G_CALLBACK(text_inserted), \
2175 #define UNBLOCK_WRAP() { \
2176 compose->autowrap = prev_autowrap; \
2177 if (compose->autowrap) { \
2178 gint old = compose->draft_timeout_tag; \
2179 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2180 compose_wrap_all(compose); \
2181 compose->draft_timeout_tag = old; \
2184 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2185 G_CALLBACK(compose_changed_cb), \
2187 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2188 G_CALLBACK(text_inserted), \
2192 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2194 Compose *compose = NULL;
2195 PrefsAccount *account = NULL;
2196 GtkTextView *textview;
2197 GtkTextBuffer *textbuf;
2201 gchar buf[BUFFSIZE];
2202 gboolean use_signing = FALSE;
2203 gboolean use_encryption = FALSE;
2204 gchar *privacy_system = NULL;
2205 int priority = PRIORITY_NORMAL;
2206 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2207 gboolean autowrap = prefs_common.autowrap;
2208 gboolean autoindent = prefs_common.auto_indent;
2209 HeaderEntry *manual_headers = NULL;
2211 cm_return_val_if_fail(msginfo != NULL, NULL);
2212 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2214 if (compose_put_existing_to_front(msginfo)) {
2218 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2219 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2220 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2221 gchar queueheader_buf[BUFFSIZE];
2224 /* Select Account from queue headers */
2225 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2226 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2227 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2228 account = account_find_from_id(id);
2230 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2231 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2232 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2233 account = account_find_from_id(id);
2235 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2236 sizeof(queueheader_buf), "NAID:")) {
2237 id = atoi(&queueheader_buf[strlen("NAID:")]);
2238 account = account_find_from_id(id);
2240 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2241 sizeof(queueheader_buf), "MAID:")) {
2242 id = atoi(&queueheader_buf[strlen("MAID:")]);
2243 account = account_find_from_id(id);
2245 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2246 sizeof(queueheader_buf), "S:")) {
2247 account = account_find_from_address(queueheader_buf, FALSE);
2249 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2250 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2251 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2252 use_signing = param;
2255 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2256 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2257 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2258 use_signing = param;
2261 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2262 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2263 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2264 use_encryption = param;
2266 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2267 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2268 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2269 use_encryption = param;
2271 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2272 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2273 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2276 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2277 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2278 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2281 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2282 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2283 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2285 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2286 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2287 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2289 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2290 sizeof(queueheader_buf), "X-Priority: ")) {
2291 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2294 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2295 sizeof(queueheader_buf), "RMID:")) {
2296 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2297 if (tokens[0] && tokens[1] && tokens[2]) {
2298 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2299 if (orig_item != NULL) {
2300 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2305 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2306 sizeof(queueheader_buf), "FMID:")) {
2307 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2308 if (tokens[0] && tokens[1] && tokens[2]) {
2309 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2310 if (orig_item != NULL) {
2311 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2316 /* Get manual headers */
2317 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2318 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2319 if (*listmh != '\0') {
2320 debug_print("Got manual headers: %s\n", listmh);
2321 manual_headers = procheader_entries_from_str(listmh);
2326 account = msginfo->folder->folder->account;
2329 if (!account && prefs_common.reedit_account_autosel) {
2330 gchar from[BUFFSIZE];
2331 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2332 extract_address(from);
2333 account = account_find_from_address(from, FALSE);
2337 account = cur_account;
2339 cm_return_val_if_fail(account != NULL, NULL);
2341 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2343 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2344 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2345 compose->autowrap = autowrap;
2346 compose->replyinfo = replyinfo;
2347 compose->fwdinfo = fwdinfo;
2349 compose->updating = TRUE;
2350 compose->priority = priority;
2352 if (privacy_system != NULL) {
2353 compose->privacy_system = privacy_system;
2354 compose_use_signing(compose, use_signing);
2355 compose_use_encryption(compose, use_encryption);
2356 compose_update_privacy_system_menu_item(compose, FALSE);
2358 activate_privacy_system(compose, account, FALSE);
2361 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2363 compose_extract_original_charset(compose);
2365 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2366 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2367 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2368 gchar queueheader_buf[BUFFSIZE];
2370 /* Set message save folder */
2371 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2372 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2373 compose_set_save_to(compose, &queueheader_buf[4]);
2375 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2376 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2378 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2383 if (compose_parse_header(compose, msginfo) < 0) {
2384 compose->updating = FALSE;
2385 compose_destroy(compose);
2388 compose_reedit_set_entry(compose, msginfo);
2390 textview = GTK_TEXT_VIEW(compose->text);
2391 textbuf = gtk_text_view_get_buffer(textview);
2392 compose_create_tags(textview, compose);
2394 mark = gtk_text_buffer_get_insert(textbuf);
2395 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2397 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2398 G_CALLBACK(compose_changed_cb),
2401 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2402 fp = procmime_get_first_encrypted_text_content(msginfo);
2404 compose_force_encryption(compose, account, TRUE, NULL);
2407 fp = procmime_get_first_text_content(msginfo);
2410 g_warning("Can't get text part\n");
2414 gboolean prev_autowrap;
2415 GtkTextBuffer *buffer;
2417 while (fgets(buf, sizeof(buf), fp) != NULL) {
2419 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2425 compose_attach_parts(compose, msginfo);
2427 compose_colorize_signature(compose);
2429 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2430 G_CALLBACK(compose_changed_cb),
2433 if (manual_headers != NULL) {
2434 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2435 procheader_entries_free(manual_headers);
2436 compose->updating = FALSE;
2437 compose_destroy(compose);
2440 procheader_entries_free(manual_headers);
2443 gtk_widget_grab_focus(compose->text);
2445 if (prefs_common.auto_exteditor) {
2446 compose_exec_ext_editor(compose);
2448 compose->modified = FALSE;
2449 compose_set_title(compose);
2451 compose->updating = FALSE;
2452 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2453 SCROLL_TO_CURSOR(compose);
2455 if (compose->deferred_destroy) {
2456 compose_destroy(compose);
2460 compose->sig_str = account_get_signature_str(compose->account);
2462 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2467 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2474 cm_return_val_if_fail(msginfo != NULL, NULL);
2477 account = account_get_reply_account(msginfo,
2478 prefs_common.reply_account_autosel);
2479 cm_return_val_if_fail(account != NULL, NULL);
2481 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2483 compose->updating = TRUE;
2485 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2486 compose->replyinfo = NULL;
2487 compose->fwdinfo = NULL;
2489 compose_show_first_last_header(compose, TRUE);
2491 gtk_widget_grab_focus(compose->header_last->entry);
2493 filename = procmsg_get_message_file(msginfo);
2495 if (filename == NULL) {
2496 compose->updating = FALSE;
2497 compose_destroy(compose);
2502 compose->redirect_filename = filename;
2504 /* Set save folder */
2505 item = msginfo->folder;
2506 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2507 gchar *folderidentifier;
2509 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2510 folderidentifier = folder_item_get_identifier(item);
2511 compose_set_save_to(compose, folderidentifier);
2512 g_free(folderidentifier);
2515 compose_attach_parts(compose, msginfo);
2517 if (msginfo->subject)
2518 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2520 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2522 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2523 _("The body of the \"Redirect\" template has an error at line %d."));
2524 quote_fmt_reset_vartable();
2525 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2527 compose_colorize_signature(compose);
2530 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2531 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2532 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2534 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2544 if (compose->toolbar->draft_btn)
2545 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2546 if (compose->toolbar->insert_btn)
2547 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2548 if (compose->toolbar->attach_btn)
2549 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2550 if (compose->toolbar->sig_btn)
2551 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2552 if (compose->toolbar->exteditor_btn)
2553 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2554 if (compose->toolbar->linewrap_current_btn)
2555 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2556 if (compose->toolbar->linewrap_all_btn)
2557 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2559 compose->modified = FALSE;
2560 compose_set_title(compose);
2561 compose->updating = FALSE;
2562 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2563 SCROLL_TO_CURSOR(compose);
2565 if (compose->deferred_destroy) {
2566 compose_destroy(compose);
2570 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2575 const GList *compose_get_compose_list(void)
2577 return compose_list;
2580 void compose_entry_append(Compose *compose, const gchar *address,
2581 ComposeEntryType type, ComposePrefType pref_type)
2583 const gchar *header;
2585 gboolean in_quote = FALSE;
2586 if (!address || *address == '\0') return;
2593 header = N_("Bcc:");
2595 case COMPOSE_REPLYTO:
2596 header = N_("Reply-To:");
2598 case COMPOSE_NEWSGROUPS:
2599 header = N_("Newsgroups:");
2601 case COMPOSE_FOLLOWUPTO:
2602 header = N_( "Followup-To:");
2604 case COMPOSE_INREPLYTO:
2605 header = N_( "In-Reply-To:");
2612 header = prefs_common_translated_header_name(header);
2614 cur = begin = (gchar *)address;
2616 /* we separate the line by commas, but not if we're inside a quoted
2618 while (*cur != '\0') {
2620 in_quote = !in_quote;
2621 if (*cur == ',' && !in_quote) {
2622 gchar *tmp = g_strdup(begin);
2624 tmp[cur-begin]='\0';
2627 while (*tmp == ' ' || *tmp == '\t')
2629 compose_add_header_entry(compose, header, tmp, pref_type);
2636 gchar *tmp = g_strdup(begin);
2638 tmp[cur-begin]='\0';
2639 while (*tmp == ' ' || *tmp == '\t')
2641 compose_add_header_entry(compose, header, tmp, pref_type);
2646 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2648 #if !GTK_CHECK_VERSION(3, 0, 0)
2649 static GdkColor yellow;
2650 static GdkColor black;
2651 static gboolean yellow_initialised = FALSE;
2653 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2654 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2659 #if !GTK_CHECK_VERSION(3, 0, 0)
2660 if (!yellow_initialised) {
2661 gdk_color_parse("#f5f6be", &yellow);
2662 gdk_color_parse("#000000", &black);
2663 yellow_initialised = gdk_colormap_alloc_color(
2664 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2665 yellow_initialised &= gdk_colormap_alloc_color(
2666 gdk_colormap_get_system(), &black, FALSE, TRUE);
2670 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2671 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2672 if (gtk_entry_get_text(entry) &&
2673 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2674 #if !GTK_CHECK_VERSION(3, 0, 0)
2675 if (yellow_initialised) {
2677 gtk_widget_modify_base(
2678 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2679 GTK_STATE_NORMAL, &yellow);
2680 gtk_widget_modify_text(
2681 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2682 GTK_STATE_NORMAL, &black);
2683 #if !GTK_CHECK_VERSION(3, 0, 0)
2690 void compose_toolbar_cb(gint action, gpointer data)
2692 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2693 Compose *compose = (Compose*)toolbar_item->parent;
2695 cm_return_if_fail(compose != NULL);
2699 compose_send_cb(NULL, compose);
2702 compose_send_later_cb(NULL, compose);
2705 compose_draft(compose, COMPOSE_QUIT_EDITING);
2708 compose_insert_file_cb(NULL, compose);
2711 compose_attach_cb(NULL, compose);
2714 compose_insert_sig(compose, FALSE);
2717 compose_insert_sig(compose, TRUE);
2720 compose_ext_editor_cb(NULL, compose);
2722 case A_LINEWRAP_CURRENT:
2723 compose_beautify_paragraph(compose, NULL, TRUE);
2725 case A_LINEWRAP_ALL:
2726 compose_wrap_all_full(compose, TRUE);
2729 compose_address_cb(NULL, compose);
2732 case A_CHECK_SPELLING:
2733 compose_check_all(NULL, compose);
2741 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2746 gchar *subject = NULL;
2750 gchar **attach = NULL;
2751 gchar *inreplyto = NULL;
2752 MailField mfield = NO_FIELD_PRESENT;
2754 /* get mailto parts but skip from */
2755 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2758 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2759 mfield = TO_FIELD_PRESENT;
2762 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2764 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2766 if (!g_utf8_validate (subject, -1, NULL)) {
2767 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2768 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2771 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2773 mfield = SUBJECT_FIELD_PRESENT;
2776 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2777 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2780 gboolean prev_autowrap = compose->autowrap;
2782 compose->autowrap = FALSE;
2784 mark = gtk_text_buffer_get_insert(buffer);
2785 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2787 if (!g_utf8_validate (body, -1, NULL)) {
2788 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2789 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2792 gtk_text_buffer_insert(buffer, &iter, body, -1);
2794 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2796 compose->autowrap = prev_autowrap;
2797 if (compose->autowrap)
2798 compose_wrap_all(compose);
2799 mfield = BODY_FIELD_PRESENT;
2803 gint i = 0, att = 0;
2804 gchar *warn_files = NULL;
2805 while (attach[i] != NULL) {
2806 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2807 if (utf8_filename) {
2808 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2809 gchar *tmp = g_strdup_printf("%s%s\n",
2810 warn_files?warn_files:"",
2816 g_free(utf8_filename);
2818 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2823 alertpanel_notice(ngettext(
2824 "The following file has been attached: \n%s",
2825 "The following files have been attached: \n%s", att), warn_files);
2830 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2843 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2845 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2846 {"Cc:", NULL, TRUE},
2847 {"References:", NULL, FALSE},
2848 {"Bcc:", NULL, TRUE},
2849 {"Newsgroups:", NULL, TRUE},
2850 {"Followup-To:", NULL, TRUE},
2851 {"List-Post:", NULL, FALSE},
2852 {"X-Priority:", NULL, FALSE},
2853 {NULL, NULL, FALSE}};
2869 cm_return_val_if_fail(msginfo != NULL, -1);
2871 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2872 procheader_get_header_fields(fp, hentry);
2875 if (hentry[H_REPLY_TO].body != NULL) {
2876 if (hentry[H_REPLY_TO].body[0] != '\0') {
2878 conv_unmime_header(hentry[H_REPLY_TO].body,
2881 g_free(hentry[H_REPLY_TO].body);
2882 hentry[H_REPLY_TO].body = NULL;
2884 if (hentry[H_CC].body != NULL) {
2885 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2886 g_free(hentry[H_CC].body);
2887 hentry[H_CC].body = NULL;
2889 if (hentry[H_REFERENCES].body != NULL) {
2890 if (compose->mode == COMPOSE_REEDIT)
2891 compose->references = hentry[H_REFERENCES].body;
2893 compose->references = compose_parse_references
2894 (hentry[H_REFERENCES].body, msginfo->msgid);
2895 g_free(hentry[H_REFERENCES].body);
2897 hentry[H_REFERENCES].body = NULL;
2899 if (hentry[H_BCC].body != NULL) {
2900 if (compose->mode == COMPOSE_REEDIT)
2902 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2903 g_free(hentry[H_BCC].body);
2904 hentry[H_BCC].body = NULL;
2906 if (hentry[H_NEWSGROUPS].body != NULL) {
2907 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2908 hentry[H_NEWSGROUPS].body = NULL;
2910 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2911 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2912 compose->followup_to =
2913 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2916 g_free(hentry[H_FOLLOWUP_TO].body);
2917 hentry[H_FOLLOWUP_TO].body = NULL;
2919 if (hentry[H_LIST_POST].body != NULL) {
2920 gchar *to = NULL, *start = NULL;
2922 extract_address(hentry[H_LIST_POST].body);
2923 if (hentry[H_LIST_POST].body[0] != '\0') {
2924 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2926 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2927 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2930 g_free(compose->ml_post);
2931 compose->ml_post = to;
2934 g_free(hentry[H_LIST_POST].body);
2935 hentry[H_LIST_POST].body = NULL;
2938 /* CLAWS - X-Priority */
2939 if (compose->mode == COMPOSE_REEDIT)
2940 if (hentry[H_X_PRIORITY].body != NULL) {
2943 priority = atoi(hentry[H_X_PRIORITY].body);
2944 g_free(hentry[H_X_PRIORITY].body);
2946 hentry[H_X_PRIORITY].body = NULL;
2948 if (priority < PRIORITY_HIGHEST ||
2949 priority > PRIORITY_LOWEST)
2950 priority = PRIORITY_NORMAL;
2952 compose->priority = priority;
2955 if (compose->mode == COMPOSE_REEDIT) {
2956 if (msginfo->inreplyto && *msginfo->inreplyto)
2957 compose->inreplyto = g_strdup(msginfo->inreplyto);
2961 if (msginfo->msgid && *msginfo->msgid)
2962 compose->inreplyto = g_strdup(msginfo->msgid);
2964 if (!compose->references) {
2965 if (msginfo->msgid && *msginfo->msgid) {
2966 if (msginfo->inreplyto && *msginfo->inreplyto)
2967 compose->references =
2968 g_strdup_printf("<%s>\n\t<%s>",
2972 compose->references =
2973 g_strconcat("<", msginfo->msgid, ">",
2975 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2976 compose->references =
2977 g_strconcat("<", msginfo->inreplyto, ">",
2985 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2990 cm_return_val_if_fail(msginfo != NULL, -1);
2992 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2993 procheader_get_header_fields(fp, entries);
2997 while (he != NULL && he->name != NULL) {
2999 GtkListStore *model = NULL;
3001 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3002 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3003 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3004 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3005 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3012 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3014 GSList *ref_id_list, *cur;
3018 ref_id_list = references_list_append(NULL, ref);
3019 if (!ref_id_list) return NULL;
3020 if (msgid && *msgid)
3021 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3026 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3027 /* "<" + Message-ID + ">" + CR+LF+TAB */
3028 len += strlen((gchar *)cur->data) + 5;
3030 if (len > MAX_REFERENCES_LEN) {
3031 /* remove second message-ID */
3032 if (ref_id_list && ref_id_list->next &&
3033 ref_id_list->next->next) {
3034 g_free(ref_id_list->next->data);
3035 ref_id_list = g_slist_remove
3036 (ref_id_list, ref_id_list->next->data);
3038 slist_free_strings_full(ref_id_list);
3045 new_ref = g_string_new("");
3046 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3047 if (new_ref->len > 0)
3048 g_string_append(new_ref, "\n\t");
3049 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3052 slist_free_strings_full(ref_id_list);
3054 new_ref_str = new_ref->str;
3055 g_string_free(new_ref, FALSE);
3060 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3061 const gchar *fmt, const gchar *qmark,
3062 const gchar *body, gboolean rewrap,
3063 gboolean need_unescape,
3064 const gchar *err_msg)
3066 MsgInfo* dummyinfo = NULL;
3067 gchar *quote_str = NULL;
3069 gboolean prev_autowrap;
3070 const gchar *trimmed_body = body;
3071 gint cursor_pos = -1;
3072 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3073 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3078 SIGNAL_BLOCK(buffer);
3081 dummyinfo = compose_msginfo_new_from_compose(compose);
3082 msginfo = dummyinfo;
3085 if (qmark != NULL) {
3087 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3088 compose->gtkaspell);
3090 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3092 quote_fmt_scan_string(qmark);
3095 buf = quote_fmt_get_buffer();
3097 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3099 Xstrdup_a(quote_str, buf, goto error)
3102 if (fmt && *fmt != '\0') {
3105 while (*trimmed_body == '\n')
3109 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3110 compose->gtkaspell);
3112 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3114 if (need_unescape) {
3117 /* decode \-escape sequences in the internal representation of the quote format */
3118 tmp = g_malloc(strlen(fmt)+1);
3119 pref_get_unescaped_pref(tmp, fmt);
3120 quote_fmt_scan_string(tmp);
3124 quote_fmt_scan_string(fmt);
3128 buf = quote_fmt_get_buffer();
3130 gint line = quote_fmt_get_line();
3131 alertpanel_error(err_msg, line);
3137 prev_autowrap = compose->autowrap;
3138 compose->autowrap = FALSE;
3140 mark = gtk_text_buffer_get_insert(buffer);
3141 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3142 if (g_utf8_validate(buf, -1, NULL)) {
3143 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3145 gchar *tmpout = NULL;
3146 tmpout = conv_codeset_strdup
3147 (buf, conv_get_locale_charset_str_no_utf8(),
3149 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3151 tmpout = g_malloc(strlen(buf)*2+1);
3152 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3154 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3158 cursor_pos = quote_fmt_get_cursor_pos();
3159 if (cursor_pos == -1)
3160 cursor_pos = gtk_text_iter_get_offset(&iter);
3161 compose->set_cursor_pos = cursor_pos;
3163 gtk_text_buffer_get_start_iter(buffer, &iter);
3164 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3165 gtk_text_buffer_place_cursor(buffer, &iter);
3167 compose->autowrap = prev_autowrap;
3168 if (compose->autowrap && rewrap)
3169 compose_wrap_all(compose);
3176 SIGNAL_UNBLOCK(buffer);
3178 procmsg_msginfo_free( dummyinfo );
3183 /* if ml_post is of type addr@host and from is of type
3184 * addr-anything@host, return TRUE
3186 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3188 gchar *left_ml = NULL;
3189 gchar *right_ml = NULL;
3190 gchar *left_from = NULL;
3191 gchar *right_from = NULL;
3192 gboolean result = FALSE;
3194 if (!ml_post || !from)
3197 left_ml = g_strdup(ml_post);
3198 if (strstr(left_ml, "@")) {
3199 right_ml = strstr(left_ml, "@")+1;
3200 *(strstr(left_ml, "@")) = '\0';
3203 left_from = g_strdup(from);
3204 if (strstr(left_from, "@")) {
3205 right_from = strstr(left_from, "@")+1;
3206 *(strstr(left_from, "@")) = '\0';
3209 if (right_ml && right_from
3210 && !strncmp(left_from, left_ml, strlen(left_ml))
3211 && !strcmp(right_from, right_ml)) {
3220 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3221 gboolean respect_default_to)
3225 if (!folder || !folder->prefs)
3228 if (respect_default_to && folder->prefs->enable_default_to) {
3229 compose_entry_append(compose, folder->prefs->default_to,
3230 COMPOSE_TO, PREF_FOLDER);
3231 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3233 if (folder->prefs->enable_default_cc)
3234 compose_entry_append(compose, folder->prefs->default_cc,
3235 COMPOSE_CC, PREF_FOLDER);
3236 if (folder->prefs->enable_default_bcc)
3237 compose_entry_append(compose, folder->prefs->default_bcc,
3238 COMPOSE_BCC, PREF_FOLDER);
3239 if (folder->prefs->enable_default_replyto)
3240 compose_entry_append(compose, folder->prefs->default_replyto,
3241 COMPOSE_REPLYTO, PREF_FOLDER);
3244 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3249 if (!compose || !msginfo)
3252 if (msginfo->subject && *msginfo->subject) {
3253 buf = p = g_strdup(msginfo->subject);
3254 p += subject_get_prefix_length(p);
3255 memmove(buf, p, strlen(p) + 1);
3257 buf2 = g_strdup_printf("Re: %s", buf);
3258 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3263 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3266 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3267 gboolean to_all, gboolean to_ml,
3269 gboolean followup_and_reply_to)
3271 GSList *cc_list = NULL;
3274 gchar *replyto = NULL;
3275 gchar *ac_email = NULL;
3277 gboolean reply_to_ml = FALSE;
3278 gboolean default_reply_to = FALSE;
3280 cm_return_if_fail(compose->account != NULL);
3281 cm_return_if_fail(msginfo != NULL);
3283 reply_to_ml = to_ml && compose->ml_post;
3285 default_reply_to = msginfo->folder &&
3286 msginfo->folder->prefs->enable_default_reply_to;
3288 if (compose->account->protocol != A_NNTP) {
3289 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3291 if (reply_to_ml && !default_reply_to) {
3293 gboolean is_subscr = is_subscription(compose->ml_post,
3296 /* normal answer to ml post with a reply-to */
3297 compose_entry_append(compose,
3299 COMPOSE_TO, PREF_ML);
3300 if (compose->replyto)
3301 compose_entry_append(compose,
3303 COMPOSE_CC, PREF_ML);
3305 /* answer to subscription confirmation */
3306 if (compose->replyto)
3307 compose_entry_append(compose,
3309 COMPOSE_TO, PREF_ML);
3310 else if (msginfo->from)
3311 compose_entry_append(compose,
3313 COMPOSE_TO, PREF_ML);
3316 else if (!(to_all || to_sender) && default_reply_to) {
3317 compose_entry_append(compose,
3318 msginfo->folder->prefs->default_reply_to,
3319 COMPOSE_TO, PREF_FOLDER);
3320 compose_entry_mark_default_to(compose,
3321 msginfo->folder->prefs->default_reply_to);
3327 compose_entry_append(compose, msginfo->from,
3328 COMPOSE_TO, PREF_NONE);
3330 Xstrdup_a(tmp1, msginfo->from, return);
3331 extract_address(tmp1);
3332 compose_entry_append(compose,
3333 (!account_find_from_address(tmp1, FALSE))
3336 COMPOSE_TO, PREF_NONE);
3338 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3339 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3340 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3341 if (compose->replyto) {
3342 compose_entry_append(compose,
3344 COMPOSE_TO, PREF_NONE);
3346 compose_entry_append(compose,
3347 msginfo->from ? msginfo->from : "",
3348 COMPOSE_TO, PREF_NONE);
3351 /* replying to own mail, use original recp */
3352 compose_entry_append(compose,
3353 msginfo->to ? msginfo->to : "",
3354 COMPOSE_TO, PREF_NONE);
3355 compose_entry_append(compose,
3356 msginfo->cc ? msginfo->cc : "",
3357 COMPOSE_CC, PREF_NONE);
3362 if (to_sender || (compose->followup_to &&
3363 !strncmp(compose->followup_to, "poster", 6)))
3364 compose_entry_append
3366 (compose->replyto ? compose->replyto :
3367 msginfo->from ? msginfo->from : ""),
3368 COMPOSE_TO, PREF_NONE);
3370 else if (followup_and_reply_to || to_all) {
3371 compose_entry_append
3373 (compose->replyto ? compose->replyto :
3374 msginfo->from ? msginfo->from : ""),
3375 COMPOSE_TO, PREF_NONE);
3377 compose_entry_append
3379 compose->followup_to ? compose->followup_to :
3380 compose->newsgroups ? compose->newsgroups : "",
3381 COMPOSE_NEWSGROUPS, PREF_NONE);
3384 compose_entry_append
3386 compose->followup_to ? compose->followup_to :
3387 compose->newsgroups ? compose->newsgroups : "",
3388 COMPOSE_NEWSGROUPS, PREF_NONE);
3390 compose_reply_set_subject(compose, msginfo);
3392 if (to_ml && compose->ml_post) return;
3393 if (!to_all || compose->account->protocol == A_NNTP) return;
3395 if (compose->replyto) {
3396 Xstrdup_a(replyto, compose->replyto, return);
3397 extract_address(replyto);
3399 if (msginfo->from) {
3400 Xstrdup_a(from, msginfo->from, return);
3401 extract_address(from);
3404 if (replyto && from)
3405 cc_list = address_list_append_with_comments(cc_list, from);
3406 if (to_all && msginfo->folder &&
3407 msginfo->folder->prefs->enable_default_reply_to)
3408 cc_list = address_list_append_with_comments(cc_list,
3409 msginfo->folder->prefs->default_reply_to);
3410 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3411 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3413 ac_email = g_utf8_strdown(compose->account->address, -1);
3416 for (cur = cc_list; cur != NULL; cur = cur->next) {
3417 gchar *addr = g_utf8_strdown(cur->data, -1);
3418 extract_address(addr);
3420 if (strcmp(ac_email, addr))
3421 compose_entry_append(compose, (gchar *)cur->data,
3422 COMPOSE_CC, PREF_NONE);
3424 debug_print("Cc address same as compose account's, ignoring\n");
3429 slist_free_strings_full(cc_list);
3435 #define SET_ENTRY(entry, str) \
3438 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3441 #define SET_ADDRESS(type, str) \
3444 compose_entry_append(compose, str, type, PREF_NONE); \
3447 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3449 cm_return_if_fail(msginfo != NULL);
3451 SET_ENTRY(subject_entry, msginfo->subject);
3452 SET_ENTRY(from_name, msginfo->from);
3453 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3454 SET_ADDRESS(COMPOSE_CC, compose->cc);
3455 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3456 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3457 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3458 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3460 compose_update_priority_menu_item(compose);
3461 compose_update_privacy_system_menu_item(compose, FALSE);
3462 compose_show_first_last_header(compose, TRUE);
3468 static void compose_insert_sig(Compose *compose, gboolean replace)
3470 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3471 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3473 GtkTextIter iter, iter_end;
3474 gint cur_pos, ins_pos;
3475 gboolean prev_autowrap;
3476 gboolean found = FALSE;
3477 gboolean exists = FALSE;
3479 cm_return_if_fail(compose->account != NULL);
3483 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3484 G_CALLBACK(compose_changed_cb),
3487 mark = gtk_text_buffer_get_insert(buffer);
3488 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3489 cur_pos = gtk_text_iter_get_offset (&iter);
3492 gtk_text_buffer_get_end_iter(buffer, &iter);
3494 exists = (compose->sig_str != NULL);
3497 GtkTextIter first_iter, start_iter, end_iter;
3499 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3501 if (!exists || compose->sig_str[0] == '\0')
3504 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3505 compose->signature_tag);
3508 /* include previous \n\n */
3509 gtk_text_iter_backward_chars(&first_iter, 1);
3510 start_iter = first_iter;
3511 end_iter = first_iter;
3513 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3514 compose->signature_tag);
3515 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3516 compose->signature_tag);
3518 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3524 g_free(compose->sig_str);
3525 compose->sig_str = account_get_signature_str(compose->account);
3527 cur_pos = gtk_text_iter_get_offset(&iter);
3529 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3530 g_free(compose->sig_str);
3531 compose->sig_str = NULL;
3533 if (compose->sig_inserted == FALSE)
3534 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3535 compose->sig_inserted = TRUE;
3537 cur_pos = gtk_text_iter_get_offset(&iter);
3538 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3540 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3541 gtk_text_iter_forward_chars(&iter, 1);
3542 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3543 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3545 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3546 cur_pos = gtk_text_buffer_get_char_count (buffer);
3549 /* put the cursor where it should be
3550 * either where the quote_fmt says, either where it was */
3551 if (compose->set_cursor_pos < 0)
3552 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3554 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3555 compose->set_cursor_pos);
3557 compose->set_cursor_pos = -1;
3558 gtk_text_buffer_place_cursor(buffer, &iter);
3559 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3560 G_CALLBACK(compose_changed_cb),
3566 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3569 GtkTextBuffer *buffer;
3572 const gchar *cur_encoding;
3573 gchar buf[BUFFSIZE];
3576 gboolean prev_autowrap;
3577 struct stat file_stat;
3579 GString *file_contents = NULL;
3581 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3583 /* get the size of the file we are about to insert */
3584 ret = g_stat(file, &file_stat);
3586 gchar *shortfile = g_path_get_basename(file);
3587 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3589 return COMPOSE_INSERT_NO_FILE;
3590 } else if (prefs_common.warn_large_insert == TRUE) {
3592 /* ask user for confirmation if the file is large */
3593 if (prefs_common.warn_large_insert_size < 0 ||
3594 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3598 msg = g_strdup_printf(_("You are about to insert a file of %s "
3599 "in the message body. Are you sure you want to do that?"),
3600 to_human_readable(file_stat.st_size));
3601 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3602 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3605 /* do we ask for confirmation next time? */
3606 if (aval & G_ALERTDISABLE) {
3607 /* no confirmation next time, disable feature in preferences */
3608 aval &= ~G_ALERTDISABLE;
3609 prefs_common.warn_large_insert = FALSE;
3612 /* abort file insertion if user canceled action */
3613 if (aval != G_ALERTALTERNATE) {
3614 return COMPOSE_INSERT_NO_FILE;
3620 if ((fp = g_fopen(file, "rb")) == NULL) {
3621 FILE_OP_ERROR(file, "fopen");
3622 return COMPOSE_INSERT_READ_ERROR;
3625 prev_autowrap = compose->autowrap;
3626 compose->autowrap = FALSE;
3628 text = GTK_TEXT_VIEW(compose->text);
3629 buffer = gtk_text_view_get_buffer(text);
3630 mark = gtk_text_buffer_get_insert(buffer);
3631 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3633 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3634 G_CALLBACK(text_inserted),
3637 cur_encoding = conv_get_locale_charset_str_no_utf8();
3639 file_contents = g_string_new("");
3640 while (fgets(buf, sizeof(buf), fp) != NULL) {
3643 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3644 str = g_strdup(buf);
3646 str = conv_codeset_strdup
3647 (buf, cur_encoding, CS_INTERNAL);
3650 /* strip <CR> if DOS/Windows file,
3651 replace <CR> with <LF> if Macintosh file. */
3654 if (len > 0 && str[len - 1] != '\n') {
3656 if (str[len] == '\r') str[len] = '\n';
3659 file_contents = g_string_append(file_contents, str);
3663 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3664 g_string_free(file_contents, TRUE);
3666 compose_changed_cb(NULL, compose);
3667 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3668 G_CALLBACK(text_inserted),
3670 compose->autowrap = prev_autowrap;
3671 if (compose->autowrap)
3672 compose_wrap_all(compose);
3676 return COMPOSE_INSERT_SUCCESS;
3679 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3680 const gchar *filename,
3681 const gchar *content_type,
3682 const gchar *charset)
3690 GtkListStore *store;
3692 gboolean has_binary = FALSE;
3694 if (!is_file_exist(file)) {
3695 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3696 gboolean result = FALSE;
3697 if (file_from_uri && is_file_exist(file_from_uri)) {
3698 result = compose_attach_append(
3699 compose, file_from_uri,
3700 filename, content_type,
3703 g_free(file_from_uri);
3706 alertpanel_error("File %s doesn't exist\n", filename);
3709 if ((size = get_file_size(file)) < 0) {
3710 alertpanel_error("Can't get file size of %s\n", filename);
3714 /* In batch mode, we allow 0-length files to be attached no questions asked */
3715 if (size == 0 && !compose->batch) {
3716 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3717 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3718 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3719 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3722 if (aval != G_ALERTALTERNATE) {
3726 if ((fp = g_fopen(file, "rb")) == NULL) {
3727 alertpanel_error(_("Can't read %s."), filename);
3732 ainfo = g_new0(AttachInfo, 1);
3733 auto_ainfo = g_auto_pointer_new_with_free
3734 (ainfo, (GFreeFunc) compose_attach_info_free);
3735 ainfo->file = g_strdup(file);
3738 ainfo->content_type = g_strdup(content_type);
3739 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3741 MsgFlags flags = {0, 0};
3743 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3744 ainfo->encoding = ENC_7BIT;
3746 ainfo->encoding = ENC_8BIT;
3748 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3749 if (msginfo && msginfo->subject)
3750 name = g_strdup(msginfo->subject);
3752 name = g_path_get_basename(filename ? filename : file);
3754 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3756 procmsg_msginfo_free(msginfo);
3758 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3759 ainfo->charset = g_strdup(charset);
3760 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3762 ainfo->encoding = ENC_BASE64;
3764 name = g_path_get_basename(filename ? filename : file);
3765 ainfo->name = g_strdup(name);
3769 ainfo->content_type = procmime_get_mime_type(file);
3770 if (!ainfo->content_type) {
3771 ainfo->content_type =
3772 g_strdup("application/octet-stream");
3773 ainfo->encoding = ENC_BASE64;
3774 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3776 procmime_get_encoding_for_text_file(file, &has_binary);
3778 ainfo->encoding = ENC_BASE64;
3779 name = g_path_get_basename(filename ? filename : file);
3780 ainfo->name = g_strdup(name);
3784 if (ainfo->name != NULL
3785 && !strcmp(ainfo->name, ".")) {
3786 g_free(ainfo->name);
3790 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3791 g_free(ainfo->content_type);
3792 ainfo->content_type = g_strdup("application/octet-stream");
3793 g_free(ainfo->charset);
3794 ainfo->charset = NULL;
3797 ainfo->size = (goffset)size;
3798 size_text = to_human_readable((goffset)size);
3800 store = GTK_LIST_STORE(gtk_tree_view_get_model
3801 (GTK_TREE_VIEW(compose->attach_clist)));
3803 gtk_list_store_append(store, &iter);
3804 gtk_list_store_set(store, &iter,
3805 COL_MIMETYPE, ainfo->content_type,
3806 COL_SIZE, size_text,
3807 COL_NAME, ainfo->name,
3808 COL_CHARSET, ainfo->charset,
3810 COL_AUTODATA, auto_ainfo,
3813 g_auto_pointer_free(auto_ainfo);
3814 compose_attach_update_label(compose);
3818 static void compose_use_signing(Compose *compose, gboolean use_signing)
3820 compose->use_signing = use_signing;
3821 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3824 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3826 compose->use_encryption = use_encryption;
3827 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3830 #define NEXT_PART_NOT_CHILD(info) \
3832 node = info->node; \
3833 while (node->children) \
3834 node = g_node_last_child(node); \
3835 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3838 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3842 MimeInfo *firsttext = NULL;
3843 MimeInfo *encrypted = NULL;
3846 const gchar *partname = NULL;
3848 mimeinfo = procmime_scan_message(msginfo);
3849 if (!mimeinfo) return;
3851 if (mimeinfo->node->children == NULL) {
3852 procmime_mimeinfo_free_all(mimeinfo);
3856 /* find first content part */
3857 child = (MimeInfo *) mimeinfo->node->children->data;
3858 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3859 child = (MimeInfo *)child->node->children->data;
3862 if (child->type == MIMETYPE_TEXT) {
3864 debug_print("First text part found\n");
3865 } else if (compose->mode == COMPOSE_REEDIT &&
3866 child->type == MIMETYPE_APPLICATION &&
3867 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3868 encrypted = (MimeInfo *)child->node->parent->data;
3871 child = (MimeInfo *) mimeinfo->node->children->data;
3872 while (child != NULL) {
3875 if (child == encrypted) {
3876 /* skip this part of tree */
3877 NEXT_PART_NOT_CHILD(child);
3881 if (child->type == MIMETYPE_MULTIPART) {
3882 /* get the actual content */
3883 child = procmime_mimeinfo_next(child);
3887 if (child == firsttext) {
3888 child = procmime_mimeinfo_next(child);
3892 outfile = procmime_get_tmp_file_name(child);
3893 if ((err = procmime_get_part(outfile, child)) < 0)
3894 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3896 gchar *content_type;
3898 content_type = procmime_get_content_type_str(child->type, child->subtype);
3900 /* if we meet a pgp signature, we don't attach it, but
3901 * we force signing. */
3902 if ((strcmp(content_type, "application/pgp-signature") &&
3903 strcmp(content_type, "application/pkcs7-signature") &&
3904 strcmp(content_type, "application/x-pkcs7-signature"))
3905 || compose->mode == COMPOSE_REDIRECT) {
3906 partname = procmime_mimeinfo_get_parameter(child, "filename");
3907 if (partname == NULL)
3908 partname = procmime_mimeinfo_get_parameter(child, "name");
3909 if (partname == NULL)
3911 compose_attach_append(compose, outfile,
3912 partname, content_type,
3913 procmime_mimeinfo_get_parameter(child, "charset"));
3915 compose_force_signing(compose, compose->account, NULL);
3917 g_free(content_type);
3920 NEXT_PART_NOT_CHILD(child);
3922 procmime_mimeinfo_free_all(mimeinfo);
3925 #undef NEXT_PART_NOT_CHILD
3930 WAIT_FOR_INDENT_CHAR,
3931 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3934 /* return indent length, we allow:
3935 indent characters followed by indent characters or spaces/tabs,
3936 alphabets and numbers immediately followed by indent characters,
3937 and the repeating sequences of the above
3938 If quote ends with multiple spaces, only the first one is included. */
3939 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3940 const GtkTextIter *start, gint *len)
3942 GtkTextIter iter = *start;
3946 IndentState state = WAIT_FOR_INDENT_CHAR;
3949 gint alnum_count = 0;
3950 gint space_count = 0;
3953 if (prefs_common.quote_chars == NULL) {
3957 while (!gtk_text_iter_ends_line(&iter)) {
3958 wc = gtk_text_iter_get_char(&iter);
3959 if (g_unichar_iswide(wc))
3961 clen = g_unichar_to_utf8(wc, ch);
3965 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3966 is_space = g_unichar_isspace(wc);
3968 if (state == WAIT_FOR_INDENT_CHAR) {
3969 if (!is_indent && !g_unichar_isalnum(wc))
3972 quote_len += alnum_count + space_count + 1;
3973 alnum_count = space_count = 0;
3974 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3977 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3978 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3982 else if (is_indent) {
3983 quote_len += alnum_count + space_count + 1;
3984 alnum_count = space_count = 0;
3987 state = WAIT_FOR_INDENT_CHAR;
3991 gtk_text_iter_forward_char(&iter);
3994 if (quote_len > 0 && space_count > 0)
4000 if (quote_len > 0) {
4002 gtk_text_iter_forward_chars(&iter, quote_len);
4003 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4009 /* return >0 if the line is itemized */
4010 static int compose_itemized_length(GtkTextBuffer *buffer,
4011 const GtkTextIter *start)
4013 GtkTextIter iter = *start;
4018 if (gtk_text_iter_ends_line(&iter))
4023 wc = gtk_text_iter_get_char(&iter);
4024 if (!g_unichar_isspace(wc))
4026 gtk_text_iter_forward_char(&iter);
4027 if (gtk_text_iter_ends_line(&iter))
4031 clen = g_unichar_to_utf8(wc, ch);
4035 if (!strchr("*-+", ch[0]))
4038 gtk_text_iter_forward_char(&iter);
4039 if (gtk_text_iter_ends_line(&iter))
4041 wc = gtk_text_iter_get_char(&iter);
4042 if (g_unichar_isspace(wc)) {
4048 /* return the string at the start of the itemization */
4049 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4050 const GtkTextIter *start)
4052 GtkTextIter iter = *start;
4055 GString *item_chars = g_string_new("");
4058 if (gtk_text_iter_ends_line(&iter))
4063 wc = gtk_text_iter_get_char(&iter);
4064 if (!g_unichar_isspace(wc))
4066 gtk_text_iter_forward_char(&iter);
4067 if (gtk_text_iter_ends_line(&iter))
4069 g_string_append_unichar(item_chars, wc);
4072 str = item_chars->str;
4073 g_string_free(item_chars, FALSE);
4077 /* return the number of spaces at a line's start */
4078 static int compose_left_offset_length(GtkTextBuffer *buffer,
4079 const GtkTextIter *start)
4081 GtkTextIter iter = *start;
4084 if (gtk_text_iter_ends_line(&iter))
4088 wc = gtk_text_iter_get_char(&iter);
4089 if (!g_unichar_isspace(wc))
4092 gtk_text_iter_forward_char(&iter);
4093 if (gtk_text_iter_ends_line(&iter))
4097 gtk_text_iter_forward_char(&iter);
4098 if (gtk_text_iter_ends_line(&iter))
4103 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4104 const GtkTextIter *start,
4105 GtkTextIter *break_pos,
4109 GtkTextIter iter = *start, line_end = *start;
4110 PangoLogAttr *attrs;
4117 gboolean can_break = FALSE;
4118 gboolean do_break = FALSE;
4119 gboolean was_white = FALSE;
4120 gboolean prev_dont_break = FALSE;
4122 gtk_text_iter_forward_to_line_end(&line_end);
4123 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4124 len = g_utf8_strlen(str, -1);
4128 g_warning("compose_get_line_break_pos: len = 0!\n");
4132 /* g_print("breaking line: %d: %s (len = %d)\n",
4133 gtk_text_iter_get_line(&iter), str, len); */
4135 attrs = g_new(PangoLogAttr, len + 1);
4137 pango_default_break(str, -1, NULL, attrs, len + 1);
4141 /* skip quote and leading spaces */
4142 for (i = 0; *p != '\0' && i < len; i++) {
4145 wc = g_utf8_get_char(p);
4146 if (i >= quote_len && !g_unichar_isspace(wc))
4148 if (g_unichar_iswide(wc))
4150 else if (*p == '\t')
4154 p = g_utf8_next_char(p);
4157 for (; *p != '\0' && i < len; i++) {
4158 PangoLogAttr *attr = attrs + i;
4162 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4165 was_white = attr->is_white;
4167 /* don't wrap URI */
4168 if ((uri_len = get_uri_len(p)) > 0) {
4170 if (pos > 0 && col > max_col) {
4180 wc = g_utf8_get_char(p);
4181 if (g_unichar_iswide(wc)) {
4183 if (prev_dont_break && can_break && attr->is_line_break)
4185 } else if (*p == '\t')
4189 if (pos > 0 && col > max_col) {
4194 if (*p == '-' || *p == '/')
4195 prev_dont_break = TRUE;
4197 prev_dont_break = FALSE;
4199 p = g_utf8_next_char(p);
4203 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4208 *break_pos = *start;
4209 gtk_text_iter_set_line_offset(break_pos, pos);
4214 static gboolean compose_join_next_line(Compose *compose,
4215 GtkTextBuffer *buffer,
4217 const gchar *quote_str)
4219 GtkTextIter iter_ = *iter, cur, prev, next, end;
4220 PangoLogAttr attrs[3];
4222 gchar *next_quote_str;
4225 gboolean keep_cursor = FALSE;
4227 if (!gtk_text_iter_forward_line(&iter_) ||
4228 gtk_text_iter_ends_line(&iter_)) {
4231 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4233 if ((quote_str || next_quote_str) &&
4234 strcmp2(quote_str, next_quote_str) != 0) {
4235 g_free(next_quote_str);
4238 g_free(next_quote_str);
4241 if (quote_len > 0) {
4242 gtk_text_iter_forward_chars(&end, quote_len);
4243 if (gtk_text_iter_ends_line(&end)) {
4248 /* don't join itemized lines */
4249 if (compose_itemized_length(buffer, &end) > 0) {
4253 /* don't join signature separator */
4254 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4257 /* delete quote str */
4259 gtk_text_buffer_delete(buffer, &iter_, &end);
4261 /* don't join line breaks put by the user */
4263 gtk_text_iter_backward_char(&cur);
4264 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4265 gtk_text_iter_forward_char(&cur);
4269 gtk_text_iter_forward_char(&cur);
4270 /* delete linebreak and extra spaces */
4271 while (gtk_text_iter_backward_char(&cur)) {
4272 wc1 = gtk_text_iter_get_char(&cur);
4273 if (!g_unichar_isspace(wc1))
4278 while (!gtk_text_iter_ends_line(&cur)) {
4279 wc1 = gtk_text_iter_get_char(&cur);
4280 if (!g_unichar_isspace(wc1))
4282 gtk_text_iter_forward_char(&cur);
4285 if (!gtk_text_iter_equal(&prev, &next)) {
4288 mark = gtk_text_buffer_get_insert(buffer);
4289 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4290 if (gtk_text_iter_equal(&prev, &cur))
4292 gtk_text_buffer_delete(buffer, &prev, &next);
4296 /* insert space if required */
4297 gtk_text_iter_backward_char(&prev);
4298 wc1 = gtk_text_iter_get_char(&prev);
4299 wc2 = gtk_text_iter_get_char(&next);
4300 gtk_text_iter_forward_char(&next);
4301 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4302 pango_default_break(str, -1, NULL, attrs, 3);
4303 if (!attrs[1].is_line_break ||
4304 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4305 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4307 gtk_text_iter_backward_char(&iter_);
4308 gtk_text_buffer_place_cursor(buffer, &iter_);
4317 #define ADD_TXT_POS(bp_, ep_, pti_) \
4318 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4319 last = last->next; \
4320 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4321 last->next = NULL; \
4323 g_warning("alloc error scanning URIs\n"); \
4326 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4328 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4329 GtkTextBuffer *buffer;
4330 GtkTextIter iter, break_pos, end_of_line;
4331 gchar *quote_str = NULL;
4333 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4334 gboolean prev_autowrap = compose->autowrap;
4335 gint startq_offset = -1, noq_offset = -1;
4336 gint uri_start = -1, uri_stop = -1;
4337 gint nouri_start = -1, nouri_stop = -1;
4338 gint num_blocks = 0;
4339 gint quotelevel = -1;
4340 gboolean modified = force;
4341 gboolean removed = FALSE;
4342 gboolean modified_before_remove = FALSE;
4344 gboolean start = TRUE;
4345 gint itemized_len = 0, rem_item_len = 0;
4346 gchar *itemized_chars = NULL;
4347 gboolean item_continuation = FALSE;
4352 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4356 compose->autowrap = FALSE;
4358 buffer = gtk_text_view_get_buffer(text);
4359 undo_wrapping(compose->undostruct, TRUE);
4364 mark = gtk_text_buffer_get_insert(buffer);
4365 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4369 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4370 if (gtk_text_iter_ends_line(&iter)) {
4371 while (gtk_text_iter_ends_line(&iter) &&
4372 gtk_text_iter_forward_line(&iter))
4375 while (gtk_text_iter_backward_line(&iter)) {
4376 if (gtk_text_iter_ends_line(&iter)) {
4377 gtk_text_iter_forward_line(&iter);
4383 /* move to line start */
4384 gtk_text_iter_set_line_offset(&iter, 0);
4387 itemized_len = compose_itemized_length(buffer, &iter);
4389 if (!itemized_len) {
4390 itemized_len = compose_left_offset_length(buffer, &iter);
4391 item_continuation = TRUE;
4395 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4397 /* go until paragraph end (empty line) */
4398 while (start || !gtk_text_iter_ends_line(&iter)) {
4399 gchar *scanpos = NULL;
4400 /* parse table - in order of priority */
4402 const gchar *needle; /* token */
4404 /* token search function */
4405 gchar *(*search) (const gchar *haystack,
4406 const gchar *needle);
4407 /* part parsing function */
4408 gboolean (*parse) (const gchar *start,
4409 const gchar *scanpos,
4413 /* part to URI function */
4414 gchar *(*build_uri) (const gchar *bp,
4418 static struct table parser[] = {
4419 {"http://", strcasestr, get_uri_part, make_uri_string},
4420 {"https://", strcasestr, get_uri_part, make_uri_string},
4421 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4422 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4423 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4424 {"www.", strcasestr, get_uri_part, make_http_string},
4425 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4426 {"@", strcasestr, get_email_part, make_email_string}
4428 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4429 gint last_index = PARSE_ELEMS;
4431 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4435 if (!prev_autowrap && num_blocks == 0) {
4437 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4438 G_CALLBACK(text_inserted),
4441 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4444 uri_start = uri_stop = -1;
4446 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4449 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4450 if (startq_offset == -1)
4451 startq_offset = gtk_text_iter_get_offset(&iter);
4452 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4453 if (quotelevel > 2) {
4454 /* recycle colors */
4455 if (prefs_common.recycle_quote_colors)
4464 if (startq_offset == -1)
4465 noq_offset = gtk_text_iter_get_offset(&iter);
4469 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4472 if (gtk_text_iter_ends_line(&iter)) {
4474 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4475 prefs_common.linewrap_len,
4477 GtkTextIter prev, next, cur;
4478 if (prev_autowrap != FALSE || force) {
4479 compose->automatic_break = TRUE;
4481 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4482 compose->automatic_break = FALSE;
4483 if (itemized_len && compose->autoindent) {
4484 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4485 if (!item_continuation)
4486 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4488 } else if (quote_str && wrap_quote) {
4489 compose->automatic_break = TRUE;
4491 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4492 compose->automatic_break = FALSE;
4493 if (itemized_len && compose->autoindent) {
4494 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4495 if (!item_continuation)
4496 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4500 /* remove trailing spaces */
4502 rem_item_len = itemized_len;
4503 while (compose->autoindent && rem_item_len-- > 0)
4504 gtk_text_iter_backward_char(&cur);
4505 gtk_text_iter_backward_char(&cur);
4508 while (!gtk_text_iter_starts_line(&cur)) {
4511 gtk_text_iter_backward_char(&cur);
4512 wc = gtk_text_iter_get_char(&cur);
4513 if (!g_unichar_isspace(wc))
4517 if (!gtk_text_iter_equal(&prev, &next)) {
4518 gtk_text_buffer_delete(buffer, &prev, &next);
4520 gtk_text_iter_forward_char(&break_pos);
4524 gtk_text_buffer_insert(buffer, &break_pos,
4528 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4530 /* move iter to current line start */
4531 gtk_text_iter_set_line_offset(&iter, 0);
4538 /* move iter to next line start */
4544 if (!prev_autowrap && num_blocks > 0) {
4546 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4547 G_CALLBACK(text_inserted),
4551 while (!gtk_text_iter_ends_line(&end_of_line)) {
4552 gtk_text_iter_forward_char(&end_of_line);
4554 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4556 nouri_start = gtk_text_iter_get_offset(&iter);
4557 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4559 walk_pos = gtk_text_iter_get_offset(&iter);
4560 /* FIXME: this looks phony. scanning for anything in the parse table */
4561 for (n = 0; n < PARSE_ELEMS; n++) {
4564 tmp = parser[n].search(walk, parser[n].needle);
4566 if (scanpos == NULL || tmp < scanpos) {
4575 /* check if URI can be parsed */
4576 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4577 (const gchar **)&ep, FALSE)
4578 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4582 strlen(parser[last_index].needle);
4585 uri_start = walk_pos + (bp - o_walk);
4586 uri_stop = walk_pos + (ep - o_walk);
4590 gtk_text_iter_forward_line(&iter);
4593 if (startq_offset != -1) {
4594 GtkTextIter startquote, endquote;
4595 gtk_text_buffer_get_iter_at_offset(
4596 buffer, &startquote, startq_offset);
4599 switch (quotelevel) {
4601 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4602 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4603 gtk_text_buffer_apply_tag_by_name(
4604 buffer, "quote0", &startquote, &endquote);
4605 gtk_text_buffer_remove_tag_by_name(
4606 buffer, "quote1", &startquote, &endquote);
4607 gtk_text_buffer_remove_tag_by_name(
4608 buffer, "quote2", &startquote, &endquote);
4613 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4614 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4615 gtk_text_buffer_apply_tag_by_name(
4616 buffer, "quote1", &startquote, &endquote);
4617 gtk_text_buffer_remove_tag_by_name(
4618 buffer, "quote0", &startquote, &endquote);
4619 gtk_text_buffer_remove_tag_by_name(
4620 buffer, "quote2", &startquote, &endquote);
4625 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4626 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4627 gtk_text_buffer_apply_tag_by_name(
4628 buffer, "quote2", &startquote, &endquote);
4629 gtk_text_buffer_remove_tag_by_name(
4630 buffer, "quote0", &startquote, &endquote);
4631 gtk_text_buffer_remove_tag_by_name(
4632 buffer, "quote1", &startquote, &endquote);
4638 } else if (noq_offset != -1) {
4639 GtkTextIter startnoquote, endnoquote;
4640 gtk_text_buffer_get_iter_at_offset(
4641 buffer, &startnoquote, noq_offset);
4644 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4645 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4646 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4647 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4648 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4649 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4650 gtk_text_buffer_remove_tag_by_name(
4651 buffer, "quote0", &startnoquote, &endnoquote);
4652 gtk_text_buffer_remove_tag_by_name(
4653 buffer, "quote1", &startnoquote, &endnoquote);
4654 gtk_text_buffer_remove_tag_by_name(
4655 buffer, "quote2", &startnoquote, &endnoquote);
4661 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4662 GtkTextIter nouri_start_iter, nouri_end_iter;
4663 gtk_text_buffer_get_iter_at_offset(
4664 buffer, &nouri_start_iter, nouri_start);
4665 gtk_text_buffer_get_iter_at_offset(
4666 buffer, &nouri_end_iter, nouri_stop);
4667 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4668 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4669 gtk_text_buffer_remove_tag_by_name(
4670 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4671 modified_before_remove = modified;
4676 if (uri_start >= 0 && uri_stop > 0) {
4677 GtkTextIter uri_start_iter, uri_end_iter, back;
4678 gtk_text_buffer_get_iter_at_offset(
4679 buffer, &uri_start_iter, uri_start);
4680 gtk_text_buffer_get_iter_at_offset(
4681 buffer, &uri_end_iter, uri_stop);
4682 back = uri_end_iter;
4683 gtk_text_iter_backward_char(&back);
4684 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4685 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4686 gtk_text_buffer_apply_tag_by_name(
4687 buffer, "link", &uri_start_iter, &uri_end_iter);
4689 if (removed && !modified_before_remove) {
4695 // debug_print("not modified, out after %d lines\n", lines);
4699 // debug_print("modified, out after %d lines\n", lines);
4701 g_free(itemized_chars);
4704 undo_wrapping(compose->undostruct, FALSE);
4705 compose->autowrap = prev_autowrap;
4710 void compose_action_cb(void *data)
4712 Compose *compose = (Compose *)data;
4713 compose_wrap_all(compose);
4716 static void compose_wrap_all(Compose *compose)
4718 compose_wrap_all_full(compose, FALSE);
4721 static void compose_wrap_all_full(Compose *compose, gboolean force)
4723 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4724 GtkTextBuffer *buffer;
4726 gboolean modified = TRUE;
4728 buffer = gtk_text_view_get_buffer(text);
4730 gtk_text_buffer_get_start_iter(buffer, &iter);
4732 undo_wrapping(compose->undostruct, TRUE);
4734 while (!gtk_text_iter_is_end(&iter) && modified)
4735 modified = compose_beautify_paragraph(compose, &iter, force);
4737 undo_wrapping(compose->undostruct, FALSE);
4741 static void compose_set_title(Compose *compose)
4747 edited = compose->modified ? _(" [Edited]") : "";
4749 subject = gtk_editable_get_chars(
4750 GTK_EDITABLE(compose->subject_entry), 0, -1);
4752 #ifndef GENERIC_UMPC
4753 if (subject && strlen(subject))
4754 str = g_strdup_printf(_("%s - Compose message%s"),
4757 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4759 str = g_strdup(_("Compose message"));
4762 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4768 * compose_current_mail_account:
4770 * Find a current mail account (the currently selected account, or the
4771 * default account, if a news account is currently selected). If a
4772 * mail account cannot be found, display an error message.
4774 * Return value: Mail account, or NULL if not found.
4776 static PrefsAccount *
4777 compose_current_mail_account(void)
4781 if (cur_account && cur_account->protocol != A_NNTP)
4784 ac = account_get_default();
4785 if (!ac || ac->protocol == A_NNTP) {
4786 alertpanel_error(_("Account for sending mail is not specified.\n"
4787 "Please select a mail account before sending."));
4794 #define QUOTE_IF_REQUIRED(out, str) \
4796 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4800 len = strlen(str) + 3; \
4801 if ((__tmp = alloca(len)) == NULL) { \
4802 g_warning("can't allocate memory\n"); \
4803 g_string_free(header, TRUE); \
4806 g_snprintf(__tmp, len, "\"%s\"", str); \
4811 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4812 g_warning("can't allocate memory\n"); \
4813 g_string_free(header, TRUE); \
4816 strcpy(__tmp, str); \
4822 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4824 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4828 len = strlen(str) + 3; \
4829 if ((__tmp = alloca(len)) == NULL) { \
4830 g_warning("can't allocate memory\n"); \
4833 g_snprintf(__tmp, len, "\"%s\"", str); \
4838 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4839 g_warning("can't allocate memory\n"); \
4842 strcpy(__tmp, str); \
4848 static void compose_select_account(Compose *compose, PrefsAccount *account,
4851 gchar *from = NULL, *header = NULL;
4852 ComposeHeaderEntry *header_entry;
4853 #if GTK_CHECK_VERSION(2, 24, 0)
4857 cm_return_if_fail(account != NULL);
4859 compose->account = account;
4860 if (account->name && *account->name) {
4862 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4863 qbuf = escape_internal_quotes(buf, '"');
4864 from = g_strdup_printf("%s <%s>",
4865 qbuf, account->address);
4868 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4870 from = g_strdup_printf("<%s>",
4872 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4877 compose_set_title(compose);
4879 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4880 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4882 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4883 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4884 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4886 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4888 activate_privacy_system(compose, account, FALSE);
4890 if (!init && compose->mode != COMPOSE_REDIRECT) {
4891 undo_block(compose->undostruct);
4892 compose_insert_sig(compose, TRUE);
4893 undo_unblock(compose->undostruct);
4896 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4897 #if !GTK_CHECK_VERSION(2, 24, 0)
4898 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4900 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4901 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4902 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4905 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4906 if (account->protocol == A_NNTP) {
4907 if (!strcmp(header, _("To:")))
4908 combobox_select_by_text(
4909 GTK_COMBO_BOX(header_entry->combo),
4912 if (!strcmp(header, _("Newsgroups:")))
4913 combobox_select_by_text(
4914 GTK_COMBO_BOX(header_entry->combo),
4922 /* use account's dict info if set */
4923 if (compose->gtkaspell) {
4924 if (account->enable_default_dictionary)
4925 gtkaspell_change_dict(compose->gtkaspell,
4926 account->default_dictionary, FALSE);
4927 if (account->enable_default_alt_dictionary)
4928 gtkaspell_change_alt_dict(compose->gtkaspell,
4929 account->default_alt_dictionary);
4930 if (account->enable_default_dictionary
4931 || account->enable_default_alt_dictionary)
4932 compose_spell_menu_changed(compose);
4937 gboolean compose_check_for_valid_recipient(Compose *compose) {
4938 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4939 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4940 gboolean recipient_found = FALSE;
4944 /* free to and newsgroup list */
4945 slist_free_strings_full(compose->to_list);
4946 compose->to_list = NULL;
4948 slist_free_strings_full(compose->newsgroup_list);
4949 compose->newsgroup_list = NULL;
4951 /* search header entries for to and newsgroup entries */
4952 for (list = compose->header_list; list; list = list->next) {
4955 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4956 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4959 if (entry[0] != '\0') {
4960 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4961 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4962 compose->to_list = address_list_append(compose->to_list, entry);
4963 recipient_found = TRUE;
4966 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4967 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4968 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4969 recipient_found = TRUE;
4976 return recipient_found;
4979 static gboolean compose_check_for_set_recipients(Compose *compose)
4981 if (compose->account->set_autocc && compose->account->auto_cc) {
4982 gboolean found_other = FALSE;
4984 /* search header entries for to and newsgroup entries */
4985 for (list = compose->header_list; list; list = list->next) {
4988 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4989 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4992 if (strcmp(entry, compose->account->auto_cc)
4993 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5003 if (compose->batch) {
5004 gtk_widget_show_all(compose->window);
5006 aval = alertpanel(_("Send"),
5007 _("The only recipient is the default CC address. Send anyway?"),
5008 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5009 if (aval != G_ALERTALTERNATE)
5013 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5014 gboolean found_other = FALSE;
5016 /* search header entries for to and newsgroup entries */
5017 for (list = compose->header_list; list; list = list->next) {
5020 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5021 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5024 if (strcmp(entry, compose->account->auto_bcc)
5025 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5035 if (compose->batch) {
5036 gtk_widget_show_all(compose->window);
5038 aval = alertpanel(_("Send"),
5039 _("The only recipient is the default BCC address. Send anyway?"),
5040 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5041 if (aval != G_ALERTALTERNATE)
5048 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5052 if (compose_check_for_valid_recipient(compose) == FALSE) {
5053 if (compose->batch) {
5054 gtk_widget_show_all(compose->window);
5056 alertpanel_error(_("Recipient is not specified."));
5060 if (compose_check_for_set_recipients(compose) == FALSE) {
5064 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5065 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5066 if (*str == '\0' && check_everything == TRUE &&
5067 compose->mode != COMPOSE_REDIRECT) {
5069 gchar *button_label;
5072 if (compose->sending)
5073 button_label = _("+_Send");
5075 button_label = _("+_Queue");
5076 message = g_strdup_printf(_("Subject is empty. %s"),
5077 compose->sending?_("Send it anyway?"):
5078 _("Queue it anyway?"));
5080 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5081 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5082 ALERT_QUESTION, G_ALERTDEFAULT);
5084 if (aval & G_ALERTDISABLE) {
5085 aval &= ~G_ALERTDISABLE;
5086 prefs_common.warn_empty_subj = FALSE;
5088 if (aval != G_ALERTALTERNATE)
5093 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5099 gint compose_send(Compose *compose)
5102 FolderItem *folder = NULL;
5104 gchar *msgpath = NULL;
5105 gboolean discard_window = FALSE;
5106 gchar *errstr = NULL;
5107 gchar *tmsgid = NULL;
5108 MainWindow *mainwin = mainwindow_get_mainwindow();
5109 gboolean queued_removed = FALSE;
5111 if (prefs_common.send_dialog_invisible
5112 || compose->batch == TRUE)
5113 discard_window = TRUE;
5115 compose_allow_user_actions (compose, FALSE);
5116 compose->sending = TRUE;
5118 if (compose_check_entries(compose, TRUE) == FALSE) {
5119 if (compose->batch) {
5120 gtk_widget_show_all(compose->window);
5126 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5129 if (compose->batch) {
5130 gtk_widget_show_all(compose->window);
5133 alertpanel_error(_("Could not queue message for sending:\n\n"
5134 "Charset conversion failed."));
5135 } else if (val == -5) {
5136 alertpanel_error(_("Could not queue message for sending:\n\n"
5137 "Couldn't get recipient encryption key."));
5138 } else if (val == -6) {
5140 } else if (val == -3) {
5141 if (privacy_peek_error())
5142 alertpanel_error(_("Could not queue message for sending:\n\n"
5143 "Signature failed: %s"), privacy_get_error());
5144 } else if (val == -2 && errno != 0) {
5145 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5147 alertpanel_error(_("Could not queue message for sending."));
5152 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5153 if (discard_window) {
5154 compose->sending = FALSE;
5155 compose_close(compose);
5156 /* No more compose access in the normal codepath
5157 * after this point! */
5162 alertpanel_error(_("The message was queued but could not be "
5163 "sent.\nUse \"Send queued messages\" from "
5164 "the main window to retry."));
5165 if (!discard_window) {
5172 if (msgpath == NULL) {
5173 msgpath = folder_item_fetch_msg(folder, msgnum);
5174 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5177 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5178 claws_unlink(msgpath);
5181 if (!discard_window) {
5183 if (!queued_removed)
5184 folder_item_remove_msg(folder, msgnum);
5185 folder_item_scan(folder);
5187 /* make sure we delete that */
5188 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5190 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5191 folder_item_remove_msg(folder, tmp->msgnum);
5192 procmsg_msginfo_free(tmp);
5199 if (!queued_removed)
5200 folder_item_remove_msg(folder, msgnum);
5201 folder_item_scan(folder);
5203 /* make sure we delete that */
5204 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5206 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5207 folder_item_remove_msg(folder, tmp->msgnum);
5208 procmsg_msginfo_free(tmp);
5211 if (!discard_window) {
5212 compose->sending = FALSE;
5213 compose_allow_user_actions (compose, TRUE);
5214 compose_close(compose);
5218 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5219 "the main window to retry."), errstr);
5222 alertpanel_error_log(_("The message was queued but could not be "
5223 "sent.\nUse \"Send queued messages\" from "
5224 "the main window to retry."));
5226 if (!discard_window) {
5235 toolbar_main_set_sensitive(mainwin);
5236 main_window_set_menu_sensitive(mainwin);
5242 compose_allow_user_actions (compose, TRUE);
5243 compose->sending = FALSE;
5244 compose->modified = TRUE;
5245 toolbar_main_set_sensitive(mainwin);
5246 main_window_set_menu_sensitive(mainwin);
5251 static gboolean compose_use_attach(Compose *compose)
5253 GtkTreeModel *model = gtk_tree_view_get_model
5254 (GTK_TREE_VIEW(compose->attach_clist));
5255 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5258 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5261 gchar buf[BUFFSIZE];
5263 gboolean first_to_address;
5264 gboolean first_cc_address;
5266 ComposeHeaderEntry *headerentry;
5267 const gchar *headerentryname;
5268 const gchar *cc_hdr;
5269 const gchar *to_hdr;
5270 gboolean err = FALSE;
5272 debug_print("Writing redirect header\n");
5274 cc_hdr = prefs_common_translated_header_name("Cc:");
5275 to_hdr = prefs_common_translated_header_name("To:");
5277 first_to_address = TRUE;
5278 for (list = compose->header_list; list; list = list->next) {
5279 headerentry = ((ComposeHeaderEntry *)list->data);
5280 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5282 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5283 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5284 Xstrdup_a(str, entstr, return -1);
5286 if (str[0] != '\0') {
5287 compose_convert_header
5288 (compose, buf, sizeof(buf), str,
5289 strlen("Resent-To") + 2, TRUE);
5291 if (first_to_address) {
5292 err |= (fprintf(fp, "Resent-To: ") < 0);
5293 first_to_address = FALSE;
5295 err |= (fprintf(fp, ",") < 0);
5297 err |= (fprintf(fp, "%s", buf) < 0);
5301 if (!first_to_address) {
5302 err |= (fprintf(fp, "\n") < 0);
5305 first_cc_address = TRUE;
5306 for (list = compose->header_list; list; list = list->next) {
5307 headerentry = ((ComposeHeaderEntry *)list->data);
5308 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5310 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5311 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5312 Xstrdup_a(str, strg, return -1);
5314 if (str[0] != '\0') {
5315 compose_convert_header
5316 (compose, buf, sizeof(buf), str,
5317 strlen("Resent-Cc") + 2, TRUE);
5319 if (first_cc_address) {
5320 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5321 first_cc_address = FALSE;
5323 err |= (fprintf(fp, ",") < 0);
5325 err |= (fprintf(fp, "%s", buf) < 0);
5329 if (!first_cc_address) {
5330 err |= (fprintf(fp, "\n") < 0);
5333 return (err ? -1:0);
5336 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5338 gchar buf[BUFFSIZE];
5340 const gchar *entstr;
5341 /* struct utsname utsbuf; */
5342 gboolean err = FALSE;
5344 cm_return_val_if_fail(fp != NULL, -1);
5345 cm_return_val_if_fail(compose->account != NULL, -1);
5346 cm_return_val_if_fail(compose->account->address != NULL, -1);
5349 get_rfc822_date(buf, sizeof(buf));
5350 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5353 if (compose->account->name && *compose->account->name) {
5354 compose_convert_header
5355 (compose, buf, sizeof(buf), compose->account->name,
5356 strlen("From: "), TRUE);
5357 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5358 buf, compose->account->address) < 0);
5360 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5363 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5364 if (*entstr != '\0') {
5365 Xstrdup_a(str, entstr, return -1);
5368 compose_convert_header(compose, buf, sizeof(buf), str,
5369 strlen("Subject: "), FALSE);
5370 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5374 /* Resent-Message-ID */
5375 if (compose->account->set_domain && compose->account->domain) {
5376 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5377 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5378 g_snprintf(buf, sizeof(buf), "%s",
5379 strchr(compose->account->address, '@') ?
5380 strchr(compose->account->address, '@')+1 :
5381 compose->account->address);
5383 g_snprintf(buf, sizeof(buf), "%s", "");
5386 if (compose->account->gen_msgid) {
5388 if (compose->account->msgid_with_addr) {
5389 addr = compose->account->address;
5391 generate_msgid(buf, sizeof(buf), addr);
5392 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5394 g_free(compose->msgid);
5395 compose->msgid = g_strdup(buf);
5397 compose->msgid = NULL;
5400 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5403 /* separator between header and body */
5404 err |= (fputs("\n", fp) == EOF);
5406 return (err ? -1:0);
5409 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5413 gchar buf[BUFFSIZE];
5415 gboolean skip = FALSE;
5416 gboolean err = FALSE;
5417 gchar *not_included[]={
5418 "Return-Path:", "Delivered-To:", "Received:",
5419 "Subject:", "X-UIDL:", "AF:",
5420 "NF:", "PS:", "SRH:",
5421 "SFN:", "DSR:", "MID:",
5422 "CFG:", "PT:", "S:",
5423 "RQ:", "SSV:", "NSV:",
5424 "SSH:", "R:", "MAID:",
5425 "NAID:", "RMID:", "FMID:",
5426 "SCF:", "RRCPT:", "NG:",
5427 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5428 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5429 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5430 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5431 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5434 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5435 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5439 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5441 for (i = 0; not_included[i] != NULL; i++) {
5442 if (g_ascii_strncasecmp(buf, not_included[i],
5443 strlen(not_included[i])) == 0) {
5450 if (fputs(buf, fdest) == -1)
5453 if (!prefs_common.redirect_keep_from) {
5454 if (g_ascii_strncasecmp(buf, "From:",
5455 strlen("From:")) == 0) {
5456 err |= (fputs(" (by way of ", fdest) == EOF);
5457 if (compose->account->name
5458 && *compose->account->name) {
5459 compose_convert_header
5460 (compose, buf, sizeof(buf),
5461 compose->account->name,
5464 err |= (fprintf(fdest, "%s <%s>",
5466 compose->account->address) < 0);
5468 err |= (fprintf(fdest, "%s",
5469 compose->account->address) < 0);
5470 err |= (fputs(")", fdest) == EOF);
5474 if (fputs("\n", fdest) == -1)
5481 if (compose_redirect_write_headers(compose, fdest))
5484 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5485 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5498 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5500 GtkTextBuffer *buffer;
5501 GtkTextIter start, end;
5504 const gchar *out_codeset;
5505 EncodingType encoding = ENC_UNKNOWN;
5506 MimeInfo *mimemsg, *mimetext;
5508 const gchar *src_codeset = CS_INTERNAL;
5509 gchar *from_addr = NULL;
5510 gchar *from_name = NULL;
5512 if (action == COMPOSE_WRITE_FOR_SEND)
5513 attach_parts = TRUE;
5515 /* create message MimeInfo */
5516 mimemsg = procmime_mimeinfo_new();
5517 mimemsg->type = MIMETYPE_MESSAGE;
5518 mimemsg->subtype = g_strdup("rfc822");
5519 mimemsg->content = MIMECONTENT_MEM;
5520 mimemsg->tmp = TRUE; /* must free content later */
5521 mimemsg->data.mem = compose_get_header(compose);
5523 /* Create text part MimeInfo */
5524 /* get all composed text */
5525 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5526 gtk_text_buffer_get_start_iter(buffer, &start);
5527 gtk_text_buffer_get_end_iter(buffer, &end);
5528 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5530 out_codeset = conv_get_charset_str(compose->out_encoding);
5532 if (!out_codeset && is_ascii_str(chars)) {
5533 out_codeset = CS_US_ASCII;
5534 } else if (prefs_common.outgoing_fallback_to_ascii &&
5535 is_ascii_str(chars)) {
5536 out_codeset = CS_US_ASCII;
5537 encoding = ENC_7BIT;
5541 gchar *test_conv_global_out = NULL;
5542 gchar *test_conv_reply = NULL;
5544 /* automatic mode. be automatic. */
5545 codeconv_set_strict(TRUE);
5547 out_codeset = conv_get_outgoing_charset_str();
5549 debug_print("trying to convert to %s\n", out_codeset);
5550 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5553 if (!test_conv_global_out && compose->orig_charset
5554 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5555 out_codeset = compose->orig_charset;
5556 debug_print("failure; trying to convert to %s\n", out_codeset);
5557 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5560 if (!test_conv_global_out && !test_conv_reply) {
5562 out_codeset = CS_INTERNAL;
5563 debug_print("failure; finally using %s\n", out_codeset);
5565 g_free(test_conv_global_out);
5566 g_free(test_conv_reply);
5567 codeconv_set_strict(FALSE);
5570 if (encoding == ENC_UNKNOWN) {
5571 if (prefs_common.encoding_method == CTE_BASE64)
5572 encoding = ENC_BASE64;
5573 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5574 encoding = ENC_QUOTED_PRINTABLE;
5575 else if (prefs_common.encoding_method == CTE_8BIT)
5576 encoding = ENC_8BIT;
5578 encoding = procmime_get_encoding_for_charset(out_codeset);
5581 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5582 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5584 if (action == COMPOSE_WRITE_FOR_SEND) {
5585 codeconv_set_strict(TRUE);
5586 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5587 codeconv_set_strict(FALSE);
5593 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5594 "to the specified %s charset.\n"
5595 "Send it as %s?"), out_codeset, src_codeset);
5596 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5597 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5600 if (aval != G_ALERTALTERNATE) {
5605 out_codeset = src_codeset;
5611 out_codeset = src_codeset;
5616 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5617 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5618 strstr(buf, "\nFrom ") != NULL) {
5619 encoding = ENC_QUOTED_PRINTABLE;
5623 mimetext = procmime_mimeinfo_new();
5624 mimetext->content = MIMECONTENT_MEM;
5625 mimetext->tmp = TRUE; /* must free content later */
5626 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5627 * and free the data, which we need later. */
5628 mimetext->data.mem = g_strdup(buf);
5629 mimetext->type = MIMETYPE_TEXT;
5630 mimetext->subtype = g_strdup("plain");
5631 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5632 g_strdup(out_codeset));
5634 /* protect trailing spaces when signing message */
5635 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5636 privacy_system_can_sign(compose->privacy_system)) {
5637 encoding = ENC_QUOTED_PRINTABLE;
5640 debug_print("main text: %zd bytes encoded as %s in %d\n",
5641 strlen(buf), out_codeset, encoding);
5643 /* check for line length limit */
5644 if (action == COMPOSE_WRITE_FOR_SEND &&
5645 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5646 check_line_length(buf, 1000, &line) < 0) {
5650 msg = g_strdup_printf
5651 (_("Line %d exceeds the line length limit (998 bytes).\n"
5652 "The contents of the message might be broken on the way to the delivery.\n"
5654 "Send it anyway?"), line + 1);
5655 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5657 if (aval != G_ALERTALTERNATE) {
5663 if (encoding != ENC_UNKNOWN)
5664 procmime_encode_content(mimetext, encoding);
5666 /* append attachment parts */
5667 if (compose_use_attach(compose) && attach_parts) {
5668 MimeInfo *mimempart;
5669 gchar *boundary = NULL;
5670 mimempart = procmime_mimeinfo_new();
5671 mimempart->content = MIMECONTENT_EMPTY;
5672 mimempart->type = MIMETYPE_MULTIPART;
5673 mimempart->subtype = g_strdup("mixed");
5677 boundary = generate_mime_boundary(NULL);
5678 } while (strstr(buf, boundary) != NULL);
5680 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5683 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5685 g_node_append(mimempart->node, mimetext->node);
5686 g_node_append(mimemsg->node, mimempart->node);
5688 if (compose_add_attachments(compose, mimempart) < 0)
5691 g_node_append(mimemsg->node, mimetext->node);
5695 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5696 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5697 /* extract name and address */
5698 if (strstr(spec, " <") && strstr(spec, ">")) {
5699 from_addr = g_strdup(strrchr(spec, '<')+1);
5700 *(strrchr(from_addr, '>')) = '\0';
5701 from_name = g_strdup(spec);
5702 *(strrchr(from_name, '<')) = '\0';
5709 /* sign message if sending */
5710 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5711 privacy_system_can_sign(compose->privacy_system))
5712 if (!privacy_sign(compose->privacy_system, mimemsg,
5713 compose->account, from_addr)) {
5720 procmime_write_mimeinfo(mimemsg, fp);
5722 procmime_mimeinfo_free_all(mimemsg);
5727 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5729 GtkTextBuffer *buffer;
5730 GtkTextIter start, end;
5735 if ((fp = g_fopen(file, "wb")) == NULL) {
5736 FILE_OP_ERROR(file, "fopen");
5740 /* chmod for security */
5741 if (change_file_mode_rw(fp, file) < 0) {
5742 FILE_OP_ERROR(file, "chmod");
5743 g_warning("can't change file mode\n");
5746 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5747 gtk_text_buffer_get_start_iter(buffer, &start);
5748 gtk_text_buffer_get_end_iter(buffer, &end);
5749 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5751 chars = conv_codeset_strdup
5752 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5761 len = strlen(chars);
5762 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5763 FILE_OP_ERROR(file, "fwrite");
5772 if (fclose(fp) == EOF) {
5773 FILE_OP_ERROR(file, "fclose");
5780 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5783 MsgInfo *msginfo = compose->targetinfo;
5785 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5786 if (!msginfo) return -1;
5788 if (!force && MSG_IS_LOCKED(msginfo->flags))
5791 item = msginfo->folder;
5792 cm_return_val_if_fail(item != NULL, -1);
5794 if (procmsg_msg_exist(msginfo) &&
5795 (folder_has_parent_of_type(item, F_QUEUE) ||
5796 folder_has_parent_of_type(item, F_DRAFT)
5797 || msginfo == compose->autosaved_draft)) {
5798 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5799 g_warning("can't remove the old message\n");
5802 debug_print("removed reedit target %d\n", msginfo->msgnum);
5809 static void compose_remove_draft(Compose *compose)
5812 MsgInfo *msginfo = compose->targetinfo;
5813 drafts = account_get_special_folder(compose->account, F_DRAFT);
5815 if (procmsg_msg_exist(msginfo)) {
5816 folder_item_remove_msg(drafts, msginfo->msgnum);
5821 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5822 gboolean remove_reedit_target)
5824 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5827 static gboolean compose_warn_encryption(Compose *compose)
5829 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5830 AlertValue val = G_ALERTALTERNATE;
5832 if (warning == NULL)
5835 val = alertpanel_full(_("Encryption warning"), warning,
5836 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5837 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5838 if (val & G_ALERTDISABLE) {
5839 val &= ~G_ALERTDISABLE;
5840 if (val == G_ALERTALTERNATE)
5841 privacy_inhibit_encrypt_warning(compose->privacy_system,
5845 if (val == G_ALERTALTERNATE) {
5852 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5853 gchar **msgpath, gboolean check_subject,
5854 gboolean remove_reedit_target)
5861 PrefsAccount *mailac = NULL, *newsac = NULL;
5862 gboolean err = FALSE;
5864 debug_print("queueing message...\n");
5865 cm_return_val_if_fail(compose->account != NULL, -1);
5867 if (compose_check_entries(compose, check_subject) == FALSE) {
5868 if (compose->batch) {
5869 gtk_widget_show_all(compose->window);
5874 if (!compose->to_list && !compose->newsgroup_list) {
5875 g_warning("can't get recipient list.");
5879 if (compose->to_list) {
5880 if (compose->account->protocol != A_NNTP)
5881 mailac = compose->account;
5882 else if (cur_account && cur_account->protocol != A_NNTP)
5883 mailac = cur_account;
5884 else if (!(mailac = compose_current_mail_account())) {
5885 alertpanel_error(_("No account for sending mails available!"));
5890 if (compose->newsgroup_list) {
5891 if (compose->account->protocol == A_NNTP)
5892 newsac = compose->account;
5894 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5899 /* write queue header */
5900 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5901 G_DIR_SEPARATOR, compose, (guint) rand());
5902 debug_print("queuing to %s\n", tmp);
5903 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5904 FILE_OP_ERROR(tmp, "fopen");
5909 if (change_file_mode_rw(fp, tmp) < 0) {
5910 FILE_OP_ERROR(tmp, "chmod");
5911 g_warning("can't change file mode\n");
5914 /* queueing variables */
5915 err |= (fprintf(fp, "AF:\n") < 0);
5916 err |= (fprintf(fp, "NF:0\n") < 0);
5917 err |= (fprintf(fp, "PS:10\n") < 0);
5918 err |= (fprintf(fp, "SRH:1\n") < 0);
5919 err |= (fprintf(fp, "SFN:\n") < 0);
5920 err |= (fprintf(fp, "DSR:\n") < 0);
5922 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5924 err |= (fprintf(fp, "MID:\n") < 0);
5925 err |= (fprintf(fp, "CFG:\n") < 0);
5926 err |= (fprintf(fp, "PT:0\n") < 0);
5927 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5928 err |= (fprintf(fp, "RQ:\n") < 0);
5930 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5932 err |= (fprintf(fp, "SSV:\n") < 0);
5934 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5936 err |= (fprintf(fp, "NSV:\n") < 0);
5937 err |= (fprintf(fp, "SSH:\n") < 0);
5938 /* write recepient list */
5939 if (compose->to_list) {
5940 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5941 for (cur = compose->to_list->next; cur != NULL;
5943 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5944 err |= (fprintf(fp, "\n") < 0);
5946 /* write newsgroup list */
5947 if (compose->newsgroup_list) {
5948 err |= (fprintf(fp, "NG:") < 0);
5949 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5950 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5951 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5952 err |= (fprintf(fp, "\n") < 0);
5954 /* Sylpheed account IDs */
5956 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5958 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5961 if (compose->privacy_system != NULL) {
5962 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5963 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5964 if (compose->use_encryption) {
5966 if (!compose_warn_encryption(compose)) {
5972 if (mailac && mailac->encrypt_to_self) {
5973 GSList *tmp_list = g_slist_copy(compose->to_list);
5974 tmp_list = g_slist_append(tmp_list, compose->account->address);
5975 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5976 g_slist_free(tmp_list);
5978 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5980 if (encdata != NULL) {
5981 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5982 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5983 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5985 } /* else we finally dont want to encrypt */
5987 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5988 /* and if encdata was null, it means there's been a problem in
5991 g_warning("failed to write queue message");
6001 /* Save copy folder */
6002 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6003 gchar *savefolderid;
6005 savefolderid = compose_get_save_to(compose);
6006 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6007 g_free(savefolderid);
6009 /* Save copy folder */
6010 if (compose->return_receipt) {
6011 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6013 /* Message-ID of message replying to */
6014 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6015 gchar *folderid = NULL;
6017 if (compose->replyinfo->folder)
6018 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6019 if (folderid == NULL)
6020 folderid = g_strdup("NULL");
6022 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6025 /* Message-ID of message forwarding to */
6026 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6027 gchar *folderid = NULL;
6029 if (compose->fwdinfo->folder)
6030 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6031 if (folderid == NULL)
6032 folderid = g_strdup("NULL");
6034 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6038 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6039 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6041 /* end of headers */
6042 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6044 if (compose->redirect_filename != NULL) {
6045 if (compose_redirect_write_to_file(compose, fp) < 0) {
6053 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6057 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6061 g_warning("failed to write queue message\n");
6067 if (fclose(fp) == EOF) {
6068 FILE_OP_ERROR(tmp, "fclose");
6074 if (item && *item) {
6077 queue = account_get_special_folder(compose->account, F_QUEUE);
6080 g_warning("can't find queue folder\n");
6085 folder_item_scan(queue);
6086 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6087 g_warning("can't queue the message\n");
6093 if (msgpath == NULL) {
6099 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6100 compose_remove_reedit_target(compose, FALSE);
6103 if ((msgnum != NULL) && (item != NULL)) {
6111 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6114 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6116 struct stat statbuf;
6117 gchar *type, *subtype;
6118 GtkTreeModel *model;
6121 model = gtk_tree_view_get_model(tree_view);
6123 if (!gtk_tree_model_get_iter_first(model, &iter))
6126 gtk_tree_model_get(model, &iter,
6130 if (!is_file_exist(ainfo->file)) {
6131 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6132 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6133 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6135 if (val == G_ALERTDEFAULT) {
6140 if (g_stat(ainfo->file, &statbuf) < 0)
6143 mimepart = procmime_mimeinfo_new();
6144 mimepart->content = MIMECONTENT_FILE;
6145 mimepart->data.filename = g_strdup(ainfo->file);
6146 mimepart->tmp = FALSE; /* or we destroy our attachment */
6147 mimepart->offset = 0;
6148 mimepart->length = statbuf.st_size;
6150 type = g_strdup(ainfo->content_type);
6152 if (!strchr(type, '/')) {
6154 type = g_strdup("application/octet-stream");
6157 subtype = strchr(type, '/') + 1;
6158 *(subtype - 1) = '\0';
6159 mimepart->type = procmime_get_media_type(type);
6160 mimepart->subtype = g_strdup(subtype);
6163 if (mimepart->type == MIMETYPE_MESSAGE &&
6164 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6165 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6166 } else if (mimepart->type == MIMETYPE_TEXT) {
6167 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6168 /* Text parts with no name come from multipart/alternative
6169 * forwards. Make sure the recipient won't look at the
6170 * original HTML part by mistake. */
6171 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6172 ainfo->name = g_strdup_printf(_("Original %s part"),
6176 g_hash_table_insert(mimepart->typeparameters,
6177 g_strdup("charset"), g_strdup(ainfo->charset));
6179 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6180 if (mimepart->type == MIMETYPE_APPLICATION &&
6181 !strcmp2(mimepart->subtype, "octet-stream"))
6182 g_hash_table_insert(mimepart->typeparameters,
6183 g_strdup("name"), g_strdup(ainfo->name));
6184 g_hash_table_insert(mimepart->dispositionparameters,
6185 g_strdup("filename"), g_strdup(ainfo->name));
6186 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6189 if (mimepart->type == MIMETYPE_MESSAGE
6190 || mimepart->type == MIMETYPE_MULTIPART)
6191 ainfo->encoding = ENC_BINARY;
6192 else if (compose->use_signing) {
6193 if (ainfo->encoding == ENC_7BIT)
6194 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6195 else if (ainfo->encoding == ENC_8BIT)
6196 ainfo->encoding = ENC_BASE64;
6201 procmime_encode_content(mimepart, ainfo->encoding);
6203 g_node_append(parent->node, mimepart->node);
6204 } while (gtk_tree_model_iter_next(model, &iter));
6209 static gchar *compose_quote_list_of_addresses(gchar *str)
6211 GSList *list = NULL, *item = NULL;
6212 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6214 list = address_list_append_with_comments(list, str);
6215 for (item = list; item != NULL; item = item->next) {
6216 gchar *spec = item->data;
6217 gchar *endofname = strstr(spec, " <");
6218 if (endofname != NULL) {
6221 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6222 qqname = escape_internal_quotes(qname, '"');
6224 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6225 gchar *addr = g_strdup(endofname);
6226 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6227 faddr = g_strconcat(name, addr, NULL);
6230 debug_print("new auto-quoted address: '%s'", faddr);
6234 result = g_strdup((faddr != NULL)? faddr: spec);
6236 result = g_strconcat(result,
6238 (faddr != NULL)? faddr: spec,
6241 if (faddr != NULL) {
6246 slist_free_strings_full(list);
6251 #define IS_IN_CUSTOM_HEADER(header) \
6252 (compose->account->add_customhdr && \
6253 custom_header_find(compose->account->customhdr_list, header) != NULL)
6255 static void compose_add_headerfield_from_headerlist(Compose *compose,
6257 const gchar *fieldname,
6258 const gchar *seperator)
6260 gchar *str, *fieldname_w_colon;
6261 gboolean add_field = FALSE;
6263 ComposeHeaderEntry *headerentry;
6264 const gchar *headerentryname;
6265 const gchar *trans_fieldname;
6268 if (IS_IN_CUSTOM_HEADER(fieldname))
6271 debug_print("Adding %s-fields\n", fieldname);
6273 fieldstr = g_string_sized_new(64);
6275 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6276 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6278 for (list = compose->header_list; list; list = list->next) {
6279 headerentry = ((ComposeHeaderEntry *)list->data);
6280 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6282 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6283 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6285 str = compose_quote_list_of_addresses(ustr);
6287 if (str != NULL && str[0] != '\0') {
6289 g_string_append(fieldstr, seperator);
6290 g_string_append(fieldstr, str);
6299 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6300 compose_convert_header
6301 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6302 strlen(fieldname) + 2, TRUE);
6303 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6307 g_free(fieldname_w_colon);
6308 g_string_free(fieldstr, TRUE);
6313 static gchar *compose_get_manual_headers_info(Compose *compose)
6315 GString *sh_header = g_string_new(" ");
6317 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6319 for (list = compose->header_list; list; list = list->next) {
6320 ComposeHeaderEntry *headerentry;
6323 gchar *headername_wcolon;
6324 const gchar *headername_trans;
6326 gboolean standard_header = FALSE;
6328 headerentry = ((ComposeHeaderEntry *)list->data);
6330 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6332 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6337 if (!strstr(tmp, ":")) {
6338 headername_wcolon = g_strconcat(tmp, ":", NULL);
6339 headername = g_strdup(tmp);
6341 headername_wcolon = g_strdup(tmp);
6342 headername = g_strdup(strtok(tmp, ":"));
6346 string = std_headers;
6347 while (*string != NULL) {
6348 headername_trans = prefs_common_translated_header_name(*string);
6349 if (!strcmp(headername_trans, headername_wcolon))
6350 standard_header = TRUE;
6353 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6354 g_string_append_printf(sh_header, "%s ", headername);
6356 g_free(headername_wcolon);
6358 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6359 return g_string_free(sh_header, FALSE);
6362 static gchar *compose_get_header(Compose *compose)
6364 gchar buf[BUFFSIZE];
6365 const gchar *entry_str;
6369 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6371 gchar *from_name = NULL, *from_address = NULL;
6374 cm_return_val_if_fail(compose->account != NULL, NULL);
6375 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6377 header = g_string_sized_new(64);
6380 get_rfc822_date(buf, sizeof(buf));
6381 g_string_append_printf(header, "Date: %s\n", buf);
6385 if (compose->account->name && *compose->account->name) {
6387 QUOTE_IF_REQUIRED(buf, compose->account->name);
6388 tmp = g_strdup_printf("%s <%s>",
6389 buf, compose->account->address);
6391 tmp = g_strdup_printf("%s",
6392 compose->account->address);
6394 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6395 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6397 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6398 from_address = g_strdup(compose->account->address);
6400 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6401 /* extract name and address */
6402 if (strstr(spec, " <") && strstr(spec, ">")) {
6403 from_address = g_strdup(strrchr(spec, '<')+1);
6404 *(strrchr(from_address, '>')) = '\0';
6405 from_name = g_strdup(spec);
6406 *(strrchr(from_name, '<')) = '\0';
6409 from_address = g_strdup(spec);
6416 if (from_name && *from_name) {
6418 compose_convert_header
6419 (compose, buf, sizeof(buf), from_name,
6420 strlen("From: "), TRUE);
6421 QUOTE_IF_REQUIRED(name, buf);
6422 qname = escape_internal_quotes(name, '"');
6424 g_string_append_printf(header, "From: %s <%s>\n",
6425 qname, from_address);
6429 g_string_append_printf(header, "From: %s\n", from_address);
6432 g_free(from_address);
6435 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6438 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6441 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6445 * If this account is a NNTP account remove Bcc header from
6446 * message body since it otherwise will be publicly shown
6448 if (compose->account->protocol != A_NNTP)
6449 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6452 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6454 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6457 compose_convert_header(compose, buf, sizeof(buf), str,
6458 strlen("Subject: "), FALSE);
6459 g_string_append_printf(header, "Subject: %s\n", buf);
6465 if (compose->account->set_domain && compose->account->domain) {
6466 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6467 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6468 g_snprintf(buf, sizeof(buf), "%s",
6469 strchr(compose->account->address, '@') ?
6470 strchr(compose->account->address, '@')+1 :
6471 compose->account->address);
6473 g_snprintf(buf, sizeof(buf), "%s", "");
6476 if (compose->account->gen_msgid) {
6478 if (compose->account->msgid_with_addr) {
6479 addr = compose->account->address;
6481 generate_msgid(buf, sizeof(buf), addr);
6482 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6484 g_free(compose->msgid);
6485 compose->msgid = g_strdup(buf);
6487 compose->msgid = NULL;
6490 if (compose->remove_references == FALSE) {
6492 if (compose->inreplyto && compose->to_list)
6493 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6496 if (compose->references)
6497 g_string_append_printf(header, "References: %s\n", compose->references);
6501 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6504 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6507 if (compose->account->organization &&
6508 strlen(compose->account->organization) &&
6509 !IS_IN_CUSTOM_HEADER("Organization")) {
6510 compose_convert_header(compose, buf, sizeof(buf),
6511 compose->account->organization,
6512 strlen("Organization: "), FALSE);
6513 g_string_append_printf(header, "Organization: %s\n", buf);
6516 /* Program version and system info */
6517 if (compose->account->gen_xmailer &&
6518 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6519 !compose->newsgroup_list) {
6520 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6522 gtk_major_version, gtk_minor_version, gtk_micro_version,
6525 if (compose->account->gen_xmailer &&
6526 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6527 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6529 gtk_major_version, gtk_minor_version, gtk_micro_version,
6533 /* custom headers */
6534 if (compose->account->add_customhdr) {
6537 for (cur = compose->account->customhdr_list; cur != NULL;
6539 CustomHeader *chdr = (CustomHeader *)cur->data;
6541 if (custom_header_is_allowed(chdr->name)
6542 && chdr->value != NULL
6543 && *(chdr->value) != '\0') {
6544 compose_convert_header
6545 (compose, buf, sizeof(buf),
6547 strlen(chdr->name) + 2, FALSE);
6548 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6553 /* Automatic Faces and X-Faces */
6554 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6555 g_string_append_printf(header, "X-Face: %s\n", buf);
6557 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6558 g_string_append_printf(header, "X-Face: %s\n", buf);
6560 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6561 g_string_append_printf(header, "Face: %s\n", buf);
6563 else if (get_default_face (buf, sizeof(buf)) == 0) {
6564 g_string_append_printf(header, "Face: %s\n", buf);
6568 switch (compose->priority) {
6569 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6570 "X-Priority: 1 (Highest)\n");
6572 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6573 "X-Priority: 2 (High)\n");
6575 case PRIORITY_NORMAL: break;
6576 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6577 "X-Priority: 4 (Low)\n");
6579 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6580 "X-Priority: 5 (Lowest)\n");
6582 default: debug_print("compose: priority unknown : %d\n",
6586 /* Request Return Receipt */
6587 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6588 if (compose->return_receipt) {
6589 if (compose->account->name
6590 && *compose->account->name) {
6591 compose_convert_header(compose, buf, sizeof(buf),
6592 compose->account->name,
6593 strlen("Disposition-Notification-To: "),
6595 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6597 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6601 /* get special headers */
6602 for (list = compose->header_list; list; list = list->next) {
6603 ComposeHeaderEntry *headerentry;
6606 gchar *headername_wcolon;
6607 const gchar *headername_trans;
6610 gboolean standard_header = FALSE;
6612 headerentry = ((ComposeHeaderEntry *)list->data);
6614 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6616 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6621 if (!strstr(tmp, ":")) {
6622 headername_wcolon = g_strconcat(tmp, ":", NULL);
6623 headername = g_strdup(tmp);
6625 headername_wcolon = g_strdup(tmp);
6626 headername = g_strdup(strtok(tmp, ":"));
6630 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6631 Xstrdup_a(headervalue, entry_str, return NULL);
6632 subst_char(headervalue, '\r', ' ');
6633 subst_char(headervalue, '\n', ' ');
6634 string = std_headers;
6635 while (*string != NULL) {
6636 headername_trans = prefs_common_translated_header_name(*string);
6637 if (!strcmp(headername_trans, headername_wcolon))
6638 standard_header = TRUE;
6641 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6642 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6645 g_free(headername_wcolon);
6649 g_string_free(header, FALSE);
6654 #undef IS_IN_CUSTOM_HEADER
6656 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6657 gint header_len, gboolean addr_field)
6659 gchar *tmpstr = NULL;
6660 const gchar *out_codeset = NULL;
6662 cm_return_if_fail(src != NULL);
6663 cm_return_if_fail(dest != NULL);
6665 if (len < 1) return;
6667 tmpstr = g_strdup(src);
6669 subst_char(tmpstr, '\n', ' ');
6670 subst_char(tmpstr, '\r', ' ');
6673 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6674 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6675 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6680 codeconv_set_strict(TRUE);
6681 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6682 conv_get_charset_str(compose->out_encoding));
6683 codeconv_set_strict(FALSE);
6685 if (!dest || *dest == '\0') {
6686 gchar *test_conv_global_out = NULL;
6687 gchar *test_conv_reply = NULL;
6689 /* automatic mode. be automatic. */
6690 codeconv_set_strict(TRUE);
6692 out_codeset = conv_get_outgoing_charset_str();
6694 debug_print("trying to convert to %s\n", out_codeset);
6695 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6698 if (!test_conv_global_out && compose->orig_charset
6699 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6700 out_codeset = compose->orig_charset;
6701 debug_print("failure; trying to convert to %s\n", out_codeset);
6702 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6705 if (!test_conv_global_out && !test_conv_reply) {
6707 out_codeset = CS_INTERNAL;
6708 debug_print("finally using %s\n", out_codeset);
6710 g_free(test_conv_global_out);
6711 g_free(test_conv_reply);
6712 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6714 codeconv_set_strict(FALSE);
6719 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6723 cm_return_if_fail(user_data != NULL);
6725 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6726 g_strstrip(address);
6727 if (*address != '\0') {
6728 gchar *name = procheader_get_fromname(address);
6729 extract_address(address);
6730 #ifndef USE_NEW_ADDRBOOK
6731 addressbook_add_contact(name, address, NULL, NULL);
6733 debug_print("%s: %s\n", name, address);
6734 if (addressadd_selection(name, address, NULL, NULL)) {
6735 debug_print( "addressbook_add_contact - added\n" );
6742 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6744 GtkWidget *menuitem;
6747 cm_return_if_fail(menu != NULL);
6748 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6750 menuitem = gtk_separator_menu_item_new();
6751 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6752 gtk_widget_show(menuitem);
6754 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6755 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6757 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6758 g_strstrip(address);
6759 if (*address == '\0') {
6760 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6763 g_signal_connect(G_OBJECT(menuitem), "activate",
6764 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6765 gtk_widget_show(menuitem);
6768 void compose_add_extra_header(gchar *header, GtkListStore *model)
6771 if (strcmp(header, "")) {
6772 COMBOBOX_ADD(model, header, COMPOSE_TO);
6776 void compose_add_extra_header_entries(GtkListStore *model)
6780 gchar buf[BUFFSIZE];
6783 if (extra_headers == NULL) {
6784 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6785 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6786 debug_print("extra headers file not found\n");
6787 goto extra_headers_done;
6789 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6790 lastc = strlen(buf) - 1; /* remove trailing control chars */
6791 while (lastc >= 0 && buf[lastc] != ':')
6792 buf[lastc--] = '\0';
6793 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6794 buf[lastc] = '\0'; /* remove trailing : for comparison */
6795 if (custom_header_is_allowed(buf)) {
6797 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6800 g_message("disallowed extra header line: %s\n", buf);
6804 g_message("invalid extra header line: %s\n", buf);
6810 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6811 extra_headers = g_slist_reverse(extra_headers);
6813 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6816 static void compose_create_header_entry(Compose *compose)
6818 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6825 const gchar *header = NULL;
6826 ComposeHeaderEntry *headerentry;
6827 gboolean standard_header = FALSE;
6828 GtkListStore *model;
6831 headerentry = g_new0(ComposeHeaderEntry, 1);
6833 /* Combo box model */
6834 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6835 #if !GTK_CHECK_VERSION(2, 24, 0)
6836 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6838 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6840 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6842 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6844 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6845 COMPOSE_NEWSGROUPS);
6846 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6848 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6849 COMPOSE_FOLLOWUPTO);
6850 compose_add_extra_header_entries(model);
6853 #if GTK_CHECK_VERSION(2, 24, 0)
6854 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6855 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6856 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6857 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6858 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6860 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6861 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6862 G_CALLBACK(compose_grab_focus_cb), compose);
6863 gtk_widget_show(combo);
6865 /* Putting only the combobox child into focus chain of its parent causes
6866 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6867 * This eliminates need to pres Tab twice in order to really get from the
6868 * combobox to next widget. */
6870 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6871 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6874 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6875 compose->header_nextrow, compose->header_nextrow+1,
6876 GTK_SHRINK, GTK_FILL, 0, 0);
6877 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6878 const gchar *last_header_entry = gtk_entry_get_text(
6879 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6881 while (*string != NULL) {
6882 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6883 standard_header = TRUE;
6886 if (standard_header)
6887 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6889 if (!compose->header_last || !standard_header) {
6890 switch(compose->account->protocol) {
6892 header = prefs_common_translated_header_name("Newsgroups:");
6895 header = prefs_common_translated_header_name("To:");
6900 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6902 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6903 G_CALLBACK(compose_grab_focus_cb), compose);
6905 /* Entry field with cleanup button */
6906 button = gtk_button_new();
6907 gtk_button_set_image(GTK_BUTTON(button),
6908 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6909 gtk_widget_show(button);
6910 CLAWS_SET_TIP(button,
6911 _("Delete entry contents"));
6912 entry = gtk_entry_new();
6913 gtk_widget_show(entry);
6914 CLAWS_SET_TIP(entry,
6915 _("Use <tab> to autocomplete from addressbook"));
6916 hbox = gtk_hbox_new (FALSE, 0);
6917 gtk_widget_show(hbox);
6918 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6919 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6920 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6921 compose->header_nextrow, compose->header_nextrow+1,
6922 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6924 g_signal_connect(G_OBJECT(entry), "key-press-event",
6925 G_CALLBACK(compose_headerentry_key_press_event_cb),
6927 g_signal_connect(G_OBJECT(entry), "changed",
6928 G_CALLBACK(compose_headerentry_changed_cb),
6930 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6931 G_CALLBACK(compose_grab_focus_cb), compose);
6933 g_signal_connect(G_OBJECT(button), "clicked",
6934 G_CALLBACK(compose_headerentry_button_clicked_cb),
6938 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6939 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6940 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6941 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6942 G_CALLBACK(compose_header_drag_received_cb),
6944 g_signal_connect(G_OBJECT(entry), "drag-drop",
6945 G_CALLBACK(compose_drag_drop),
6947 g_signal_connect(G_OBJECT(entry), "populate-popup",
6948 G_CALLBACK(compose_entry_popup_extend),
6951 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6953 headerentry->compose = compose;
6954 headerentry->combo = combo;
6955 headerentry->entry = entry;
6956 headerentry->button = button;
6957 headerentry->hbox = hbox;
6958 headerentry->headernum = compose->header_nextrow;
6959 headerentry->type = PREF_NONE;
6961 compose->header_nextrow++;
6962 compose->header_last = headerentry;
6963 compose->header_list =
6964 g_slist_append(compose->header_list,
6968 static void compose_add_header_entry(Compose *compose, const gchar *header,
6969 gchar *text, ComposePrefType pref_type)
6971 ComposeHeaderEntry *last_header = compose->header_last;
6972 gchar *tmp = g_strdup(text), *email;
6973 gboolean replyto_hdr;
6975 replyto_hdr = (!strcasecmp(header,
6976 prefs_common_translated_header_name("Reply-To:")) ||
6978 prefs_common_translated_header_name("Followup-To:")) ||
6980 prefs_common_translated_header_name("In-Reply-To:")));
6982 extract_address(tmp);
6983 email = g_utf8_strdown(tmp, -1);
6985 if (replyto_hdr == FALSE &&
6986 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6988 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6989 header, text, (gint) pref_type);
6995 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6996 gtk_entry_set_text(GTK_ENTRY(
6997 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6999 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7000 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7001 last_header->type = pref_type;
7003 if (replyto_hdr == FALSE)
7004 g_hash_table_insert(compose->email_hashtable, email,
7005 GUINT_TO_POINTER(1));
7012 static void compose_destroy_headerentry(Compose *compose,
7013 ComposeHeaderEntry *headerentry)
7015 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7018 extract_address(text);
7019 email = g_utf8_strdown(text, -1);
7020 g_hash_table_remove(compose->email_hashtable, email);
7024 gtk_widget_destroy(headerentry->combo);
7025 gtk_widget_destroy(headerentry->entry);
7026 gtk_widget_destroy(headerentry->button);
7027 gtk_widget_destroy(headerentry->hbox);
7028 g_free(headerentry);
7031 static void compose_remove_header_entries(Compose *compose)
7034 for (list = compose->header_list; list; list = list->next)
7035 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7037 compose->header_last = NULL;
7038 g_slist_free(compose->header_list);
7039 compose->header_list = NULL;
7040 compose->header_nextrow = 1;
7041 compose_create_header_entry(compose);
7044 static GtkWidget *compose_create_header(Compose *compose)
7046 GtkWidget *from_optmenu_hbox;
7047 GtkWidget *header_scrolledwin_main;
7048 GtkWidget *header_table_main;
7049 GtkWidget *header_scrolledwin;
7050 GtkWidget *header_table;
7052 /* parent with account selection and from header */
7053 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7054 gtk_widget_show(header_scrolledwin_main);
7055 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7057 header_table_main = gtk_table_new(2, 2, FALSE);
7058 gtk_widget_show(header_table_main);
7059 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7060 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7061 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7063 from_optmenu_hbox = compose_account_option_menu_create(compose);
7064 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7065 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7067 /* child with header labels and entries */
7068 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7069 gtk_widget_show(header_scrolledwin);
7070 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7072 header_table = gtk_table_new(2, 2, FALSE);
7073 gtk_widget_show(header_table);
7074 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7075 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7076 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7078 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7079 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7081 compose->header_table = header_table;
7082 compose->header_list = NULL;
7083 compose->header_nextrow = 0;
7085 compose_create_header_entry(compose);
7087 compose->table = NULL;
7089 return header_scrolledwin_main;
7092 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7094 Compose *compose = (Compose *)data;
7095 GdkEventButton event;
7098 event.time = gtk_get_current_event_time();
7100 return attach_button_pressed(compose->attach_clist, &event, compose);
7103 static GtkWidget *compose_create_attach(Compose *compose)
7105 GtkWidget *attach_scrwin;
7106 GtkWidget *attach_clist;
7108 GtkListStore *store;
7109 GtkCellRenderer *renderer;
7110 GtkTreeViewColumn *column;
7111 GtkTreeSelection *selection;
7113 /* attachment list */
7114 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7115 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7116 GTK_POLICY_AUTOMATIC,
7117 GTK_POLICY_AUTOMATIC);
7118 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7120 store = gtk_list_store_new(N_ATTACH_COLS,
7126 G_TYPE_AUTO_POINTER,
7128 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7129 (GTK_TREE_MODEL(store)));
7130 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7131 g_object_unref(store);
7133 renderer = gtk_cell_renderer_text_new();
7134 column = gtk_tree_view_column_new_with_attributes
7135 (_("Mime type"), renderer, "text",
7136 COL_MIMETYPE, NULL);
7137 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7139 renderer = gtk_cell_renderer_text_new();
7140 column = gtk_tree_view_column_new_with_attributes
7141 (_("Size"), renderer, "text",
7143 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7145 renderer = gtk_cell_renderer_text_new();
7146 column = gtk_tree_view_column_new_with_attributes
7147 (_("Name"), renderer, "text",
7149 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7151 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7152 prefs_common.use_stripes_everywhere);
7153 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7154 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7156 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7157 G_CALLBACK(attach_selected), compose);
7158 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7159 G_CALLBACK(attach_button_pressed), compose);
7160 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7161 G_CALLBACK(popup_attach_button_pressed), compose);
7162 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7163 G_CALLBACK(attach_key_pressed), compose);
7166 gtk_drag_dest_set(attach_clist,
7167 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7168 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7169 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7170 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7171 G_CALLBACK(compose_attach_drag_received_cb),
7173 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7174 G_CALLBACK(compose_drag_drop),
7177 compose->attach_scrwin = attach_scrwin;
7178 compose->attach_clist = attach_clist;
7180 return attach_scrwin;
7183 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7184 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7186 static GtkWidget *compose_create_others(Compose *compose)
7189 GtkWidget *savemsg_checkbtn;
7190 GtkWidget *savemsg_combo;
7191 GtkWidget *savemsg_select;
7194 gchar *folderidentifier;
7196 /* Table for settings */
7197 table = gtk_table_new(3, 1, FALSE);
7198 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7199 gtk_widget_show(table);
7200 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7203 /* Save Message to folder */
7204 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7205 gtk_widget_show(savemsg_checkbtn);
7206 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7207 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7208 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7210 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7211 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7213 #if !GTK_CHECK_VERSION(2, 24, 0)
7214 savemsg_combo = gtk_combo_box_entry_new_text();
7216 savemsg_combo = gtk_combo_box_text_new_with_entry();
7218 compose->savemsg_checkbtn = savemsg_checkbtn;
7219 compose->savemsg_combo = savemsg_combo;
7220 gtk_widget_show(savemsg_combo);
7222 if (prefs_common.compose_save_to_history)
7223 #if !GTK_CHECK_VERSION(2, 24, 0)
7224 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7225 prefs_common.compose_save_to_history);
7227 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7228 prefs_common.compose_save_to_history);
7230 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7231 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7232 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7233 G_CALLBACK(compose_grab_focus_cb), compose);
7234 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7235 folderidentifier = folder_item_get_identifier(account_get_special_folder
7236 (compose->account, F_OUTBOX));
7237 compose_set_save_to(compose, folderidentifier);
7238 g_free(folderidentifier);
7241 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7242 gtk_widget_show(savemsg_select);
7243 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7244 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7245 G_CALLBACK(compose_savemsg_select_cb),
7251 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7253 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7254 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7257 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7262 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7265 path = folder_item_get_identifier(dest);
7267 compose_set_save_to(compose, path);
7271 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7272 GdkAtom clip, GtkTextIter *insert_place);
7275 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7279 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7281 if (event->button == 3) {
7283 GtkTextIter sel_start, sel_end;
7284 gboolean stuff_selected;
7286 /* move the cursor to allow GtkAspell to check the word
7287 * under the mouse */
7288 if (event->x && event->y) {
7289 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7290 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7292 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7295 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7296 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7299 stuff_selected = gtk_text_buffer_get_selection_bounds(
7301 &sel_start, &sel_end);
7303 gtk_text_buffer_place_cursor (buffer, &iter);
7304 /* reselect stuff */
7306 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7307 gtk_text_buffer_select_range(buffer,
7308 &sel_start, &sel_end);
7310 return FALSE; /* pass the event so that the right-click goes through */
7313 if (event->button == 2) {
7318 /* get the middle-click position to paste at the correct place */
7319 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7320 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7322 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7325 entry_paste_clipboard(compose, text,
7326 prefs_common.linewrap_pastes,
7327 GDK_SELECTION_PRIMARY, &iter);
7335 static void compose_spell_menu_changed(void *data)
7337 Compose *compose = (Compose *)data;
7339 GtkWidget *menuitem;
7340 GtkWidget *parent_item;
7341 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7344 if (compose->gtkaspell == NULL)
7347 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7348 "/Menu/Spelling/Options");
7350 /* setting the submenu removes /Spelling/Options from the factory
7351 * so we need to save it */
7353 if (parent_item == NULL) {
7354 parent_item = compose->aspell_options_menu;
7355 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7357 compose->aspell_options_menu = parent_item;
7359 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7361 spell_menu = g_slist_reverse(spell_menu);
7362 for (items = spell_menu;
7363 items; items = items->next) {
7364 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7365 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7366 gtk_widget_show(GTK_WIDGET(menuitem));
7368 g_slist_free(spell_menu);
7370 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7371 gtk_widget_show(parent_item);
7374 static void compose_dict_changed(void *data)
7376 Compose *compose = (Compose *) data;
7378 if(!compose->gtkaspell)
7380 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7383 gtkaspell_highlight_all(compose->gtkaspell);
7384 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7388 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7390 Compose *compose = (Compose *)data;
7391 GdkEventButton event;
7394 event.time = gtk_get_current_event_time();
7398 return text_clicked(compose->text, &event, compose);
7401 static gboolean compose_force_window_origin = TRUE;
7402 static Compose *compose_create(PrefsAccount *account,
7411 GtkWidget *handlebox;
7413 GtkWidget *notebook;
7415 GtkWidget *attach_hbox;
7416 GtkWidget *attach_lab1;
7417 GtkWidget *attach_lab2;
7422 GtkWidget *subject_hbox;
7423 GtkWidget *subject_frame;
7424 GtkWidget *subject_entry;
7428 GtkWidget *edit_vbox;
7429 GtkWidget *ruler_hbox;
7431 GtkWidget *scrolledwin;
7433 GtkTextBuffer *buffer;
7434 GtkClipboard *clipboard;
7436 UndoMain *undostruct;
7438 GtkWidget *popupmenu;
7439 GtkWidget *tmpl_menu;
7440 GtkActionGroup *action_group = NULL;
7443 GtkAspell * gtkaspell = NULL;
7446 static GdkGeometry geometry;
7448 cm_return_val_if_fail(account != NULL, NULL);
7450 debug_print("Creating compose window...\n");
7451 compose = g_new0(Compose, 1);
7453 compose->batch = batch;
7454 compose->account = account;
7455 compose->folder = folder;
7457 compose->mutex = cm_mutex_new();
7458 compose->set_cursor_pos = -1;
7460 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7462 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7463 gtk_widget_set_size_request(window, prefs_common.compose_width,
7464 prefs_common.compose_height);
7466 if (!geometry.max_width) {
7467 geometry.max_width = gdk_screen_width();
7468 geometry.max_height = gdk_screen_height();
7471 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7472 &geometry, GDK_HINT_MAX_SIZE);
7473 if (!geometry.min_width) {
7474 geometry.min_width = 600;
7475 geometry.min_height = 440;
7477 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7478 &geometry, GDK_HINT_MIN_SIZE);
7480 #ifndef GENERIC_UMPC
7481 if (compose_force_window_origin)
7482 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7483 prefs_common.compose_y);
7485 g_signal_connect(G_OBJECT(window), "delete_event",
7486 G_CALLBACK(compose_delete_cb), compose);
7487 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7488 gtk_widget_realize(window);
7490 gtkut_widget_set_composer_icon(window);
7492 vbox = gtk_vbox_new(FALSE, 0);
7493 gtk_container_add(GTK_CONTAINER(window), vbox);
7495 compose->ui_manager = gtk_ui_manager_new();
7496 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7497 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7498 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7499 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7500 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7501 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7502 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7503 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7504 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7505 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7624 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)
7625 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)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7631 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)
7632 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)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7637 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)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7641 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)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7647 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)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7653 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)
7654 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)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7667 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)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7685 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7686 gtk_widget_show_all(menubar);
7688 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7689 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7691 if (prefs_common.toolbar_detachable) {
7692 handlebox = gtk_handle_box_new();
7694 handlebox = gtk_hbox_new(FALSE, 0);
7696 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7698 gtk_widget_realize(handlebox);
7699 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7702 vbox2 = gtk_vbox_new(FALSE, 2);
7703 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7704 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7707 notebook = gtk_notebook_new();
7708 gtk_widget_show(notebook);
7710 /* header labels and entries */
7711 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7712 compose_create_header(compose),
7713 gtk_label_new_with_mnemonic(_("Hea_der")));
7714 /* attachment list */
7715 attach_hbox = gtk_hbox_new(FALSE, 0);
7716 gtk_widget_show(attach_hbox);
7718 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7719 gtk_widget_show(attach_lab1);
7720 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7722 attach_lab2 = gtk_label_new("");
7723 gtk_widget_show(attach_lab2);
7724 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7726 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7727 compose_create_attach(compose),
7730 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7731 compose_create_others(compose),
7732 gtk_label_new_with_mnemonic(_("Othe_rs")));
7735 subject_hbox = gtk_hbox_new(FALSE, 0);
7736 gtk_widget_show(subject_hbox);
7738 subject_frame = gtk_frame_new(NULL);
7739 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7740 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7741 gtk_widget_show(subject_frame);
7743 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7744 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7745 gtk_widget_show(subject);
7747 label = gtk_label_new_with_mnemonic(_("_Subject:"));
7748 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7749 gtk_widget_show(label);
7752 subject_entry = claws_spell_entry_new();
7754 subject_entry = gtk_entry_new();
7756 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7757 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7758 G_CALLBACK(compose_grab_focus_cb), compose);
7759 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7760 gtk_widget_show(subject_entry);
7761 compose->subject_entry = subject_entry;
7762 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7764 edit_vbox = gtk_vbox_new(FALSE, 0);
7766 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7769 ruler_hbox = gtk_hbox_new(FALSE, 0);
7770 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7772 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7773 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7774 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7778 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7779 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7780 GTK_POLICY_AUTOMATIC,
7781 GTK_POLICY_AUTOMATIC);
7782 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7784 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7786 text = gtk_text_view_new();
7787 if (prefs_common.show_compose_margin) {
7788 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7789 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7791 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7792 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7793 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7794 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7795 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7797 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7798 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7799 G_CALLBACK(compose_edit_size_alloc),
7801 g_signal_connect(G_OBJECT(buffer), "changed",
7802 G_CALLBACK(compose_changed_cb), compose);
7803 g_signal_connect(G_OBJECT(text), "grab_focus",
7804 G_CALLBACK(compose_grab_focus_cb), compose);
7805 g_signal_connect(G_OBJECT(buffer), "insert_text",
7806 G_CALLBACK(text_inserted), compose);
7807 g_signal_connect(G_OBJECT(text), "button_press_event",
7808 G_CALLBACK(text_clicked), compose);
7809 g_signal_connect(G_OBJECT(text), "popup-menu",
7810 G_CALLBACK(compose_popup_menu), compose);
7811 g_signal_connect(G_OBJECT(subject_entry), "changed",
7812 G_CALLBACK(compose_changed_cb), compose);
7813 g_signal_connect(G_OBJECT(subject_entry), "activate",
7814 G_CALLBACK(compose_subject_entry_activated), compose);
7817 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7818 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7819 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7820 g_signal_connect(G_OBJECT(text), "drag_data_received",
7821 G_CALLBACK(compose_insert_drag_received_cb),
7823 g_signal_connect(G_OBJECT(text), "drag-drop",
7824 G_CALLBACK(compose_drag_drop),
7826 g_signal_connect(G_OBJECT(text), "key-press-event",
7827 G_CALLBACK(completion_set_focus_to_subject),
7829 gtk_widget_show_all(vbox);
7831 /* pane between attach clist and text */
7832 paned = gtk_vpaned_new();
7833 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7834 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7835 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7836 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7837 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7838 G_CALLBACK(compose_notebook_size_alloc), paned);
7840 gtk_widget_show_all(paned);
7843 if (prefs_common.textfont) {
7844 PangoFontDescription *font_desc;
7846 font_desc = pango_font_description_from_string
7847 (prefs_common.textfont);
7849 gtk_widget_modify_font(text, font_desc);
7850 pango_font_description_free(font_desc);
7854 gtk_action_group_add_actions(action_group, compose_popup_entries,
7855 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7856 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7863 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7865 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7866 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7867 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7869 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7871 undostruct = undo_init(text);
7872 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7875 address_completion_start(window);
7877 compose->window = window;
7878 compose->vbox = vbox;
7879 compose->menubar = menubar;
7880 compose->handlebox = handlebox;
7882 compose->vbox2 = vbox2;
7884 compose->paned = paned;
7886 compose->attach_label = attach_lab2;
7888 compose->notebook = notebook;
7889 compose->edit_vbox = edit_vbox;
7890 compose->ruler_hbox = ruler_hbox;
7891 compose->ruler = ruler;
7892 compose->scrolledwin = scrolledwin;
7893 compose->text = text;
7895 compose->focused_editable = NULL;
7897 compose->popupmenu = popupmenu;
7899 compose->tmpl_menu = tmpl_menu;
7901 compose->mode = mode;
7902 compose->rmode = mode;
7904 compose->targetinfo = NULL;
7905 compose->replyinfo = NULL;
7906 compose->fwdinfo = NULL;
7908 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7909 g_str_equal, (GDestroyNotify) g_free, NULL);
7911 compose->replyto = NULL;
7913 compose->bcc = NULL;
7914 compose->followup_to = NULL;
7916 compose->ml_post = NULL;
7918 compose->inreplyto = NULL;
7919 compose->references = NULL;
7920 compose->msgid = NULL;
7921 compose->boundary = NULL;
7923 compose->autowrap = prefs_common.autowrap;
7924 compose->autoindent = prefs_common.auto_indent;
7925 compose->use_signing = FALSE;
7926 compose->use_encryption = FALSE;
7927 compose->privacy_system = NULL;
7929 compose->modified = FALSE;
7931 compose->return_receipt = FALSE;
7933 compose->to_list = NULL;
7934 compose->newsgroup_list = NULL;
7936 compose->undostruct = undostruct;
7938 compose->sig_str = NULL;
7940 compose->exteditor_file = NULL;
7941 compose->exteditor_pid = -1;
7942 compose->exteditor_tag = -1;
7943 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7945 compose->folder_update_callback_id =
7946 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
7947 compose_update_folder_hook,
7948 (gpointer) compose);
7951 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7952 if (mode != COMPOSE_REDIRECT) {
7953 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7954 strcmp(prefs_common.dictionary, "")) {
7955 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7956 prefs_common.alt_dictionary,
7957 conv_get_locale_charset_str(),
7958 prefs_common.misspelled_col,
7959 prefs_common.check_while_typing,
7960 prefs_common.recheck_when_changing_dict,
7961 prefs_common.use_alternate,
7962 prefs_common.use_both_dicts,
7963 GTK_TEXT_VIEW(text),
7964 GTK_WINDOW(compose->window),
7965 compose_dict_changed,
7966 compose_spell_menu_changed,
7969 alertpanel_error(_("Spell checker could not "
7971 gtkaspell_checkers_strerror());
7972 gtkaspell_checkers_reset_error();
7974 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7978 compose->gtkaspell = gtkaspell;
7979 compose_spell_menu_changed(compose);
7980 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7983 compose_select_account(compose, account, TRUE);
7985 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7986 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7988 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7989 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7991 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7992 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7994 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7995 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7997 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7998 if (account->protocol != A_NNTP)
7999 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8000 prefs_common_translated_header_name("To:"));
8002 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8003 prefs_common_translated_header_name("Newsgroups:"));
8005 #ifndef USE_NEW_ADDRBOOK
8006 addressbook_set_target_compose(compose);
8008 if (mode != COMPOSE_REDIRECT)
8009 compose_set_template_menu(compose);
8011 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8014 compose_list = g_list_append(compose_list, compose);
8016 if (!prefs_common.show_ruler)
8017 gtk_widget_hide(ruler_hbox);
8019 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8022 compose->priority = PRIORITY_NORMAL;
8023 compose_update_priority_menu_item(compose);
8025 compose_set_out_encoding(compose);
8028 compose_update_actions_menu(compose);
8030 /* Privacy Systems menu */
8031 compose_update_privacy_systems_menu(compose);
8033 activate_privacy_system(compose, account, TRUE);
8034 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8036 gtk_widget_realize(window);
8038 gtk_widget_show(window);
8044 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8049 GtkWidget *optmenubox;
8052 GtkWidget *from_name = NULL;
8054 gint num = 0, def_menu = 0;
8056 accounts = account_get_list();
8057 cm_return_val_if_fail(accounts != NULL, NULL);
8059 optmenubox = gtk_event_box_new();
8060 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8061 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8063 hbox = gtk_hbox_new(FALSE, 6);
8064 from_name = gtk_entry_new();
8066 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8067 G_CALLBACK(compose_grab_focus_cb), compose);
8069 for (; accounts != NULL; accounts = accounts->next, num++) {
8070 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8071 gchar *name, *from = NULL;
8073 if (ac == compose->account) def_menu = num;
8075 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8078 if (ac == compose->account) {
8079 if (ac->name && *ac->name) {
8081 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8082 from = g_strdup_printf("%s <%s>",
8084 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8086 from = g_strdup_printf("%s",
8088 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8091 COMBOBOX_ADD(menu, name, ac->account_id);
8096 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8098 g_signal_connect(G_OBJECT(optmenu), "changed",
8099 G_CALLBACK(account_activated),
8101 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8102 G_CALLBACK(compose_entry_popup_extend),
8105 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8106 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8108 /* Putting only the GtkEntry into focus chain of parent hbox causes
8109 * the account selector combobox next to it to be unreachable when
8110 * navigating widgets in GtkTable with up/down arrow keys.
8111 * Note: gtk_widget_set_can_focus() was not enough. */
8113 l = g_list_prepend(l, from_name);
8114 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8117 CLAWS_SET_TIP(optmenubox,
8118 _("Account to use for this email"));
8119 CLAWS_SET_TIP(from_name,
8120 _("Sender address to be used"));
8122 compose->account_combo = optmenu;
8123 compose->from_name = from_name;
8128 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8130 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8131 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8132 Compose *compose = (Compose *) data;
8134 compose->priority = value;
8138 static void compose_reply_change_mode(Compose *compose,
8141 gboolean was_modified = compose->modified;
8143 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8145 cm_return_if_fail(compose->replyinfo != NULL);
8147 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8149 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8151 if (action == COMPOSE_REPLY_TO_ALL)
8153 if (action == COMPOSE_REPLY_TO_SENDER)
8155 if (action == COMPOSE_REPLY_TO_LIST)
8158 compose_remove_header_entries(compose);
8159 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8160 if (compose->account->set_autocc && compose->account->auto_cc)
8161 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8163 if (compose->account->set_autobcc && compose->account->auto_bcc)
8164 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8166 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8167 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8168 compose_show_first_last_header(compose, TRUE);
8169 compose->modified = was_modified;
8170 compose_set_title(compose);
8173 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8175 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8176 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8177 Compose *compose = (Compose *) data;
8180 compose_reply_change_mode(compose, value);
8183 static void compose_update_priority_menu_item(Compose * compose)
8185 GtkWidget *menuitem = NULL;
8186 switch (compose->priority) {
8187 case PRIORITY_HIGHEST:
8188 menuitem = gtk_ui_manager_get_widget
8189 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8192 menuitem = gtk_ui_manager_get_widget
8193 (compose->ui_manager, "/Menu/Options/Priority/High");
8195 case PRIORITY_NORMAL:
8196 menuitem = gtk_ui_manager_get_widget
8197 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8200 menuitem = gtk_ui_manager_get_widget
8201 (compose->ui_manager, "/Menu/Options/Priority/Low");
8203 case PRIORITY_LOWEST:
8204 menuitem = gtk_ui_manager_get_widget
8205 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8208 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8211 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8213 Compose *compose = (Compose *) data;
8215 gboolean can_sign = FALSE, can_encrypt = FALSE;
8217 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8219 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8222 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8223 g_free(compose->privacy_system);
8224 compose->privacy_system = NULL;
8225 if (systemid != NULL) {
8226 compose->privacy_system = g_strdup(systemid);
8228 can_sign = privacy_system_can_sign(systemid);
8229 can_encrypt = privacy_system_can_encrypt(systemid);
8232 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8234 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8235 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8238 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8240 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8241 GtkWidget *menuitem = NULL;
8242 GList *children, *amenu;
8243 gboolean can_sign = FALSE, can_encrypt = FALSE;
8244 gboolean found = FALSE;
8246 if (compose->privacy_system != NULL) {
8248 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8249 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8250 cm_return_if_fail(menuitem != NULL);
8252 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8255 while (amenu != NULL) {
8256 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8257 if (systemid != NULL) {
8258 if (strcmp(systemid, compose->privacy_system) == 0 &&
8259 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8260 menuitem = GTK_WIDGET(amenu->data);
8262 can_sign = privacy_system_can_sign(systemid);
8263 can_encrypt = privacy_system_can_encrypt(systemid);
8267 } else if (strlen(compose->privacy_system) == 0 &&
8268 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8269 menuitem = GTK_WIDGET(amenu->data);
8272 can_encrypt = FALSE;
8277 amenu = amenu->next;
8279 g_list_free(children);
8280 if (menuitem != NULL)
8281 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8283 if (warn && !found && strlen(compose->privacy_system)) {
8284 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8285 "will not be able to sign or encrypt this message."),
8286 compose->privacy_system);
8290 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8294 static void compose_set_out_encoding(Compose *compose)
8296 CharSet out_encoding;
8297 const gchar *branch = NULL;
8298 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8300 switch(out_encoding) {
8301 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8302 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8303 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8304 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8305 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8306 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8307 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8308 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8309 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8310 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8311 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8312 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8313 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8314 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8315 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8316 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8317 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8318 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8319 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8320 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8321 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8322 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8323 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8324 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8325 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8326 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8327 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8328 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8329 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8330 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8331 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8332 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8333 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8335 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8338 static void compose_set_template_menu(Compose *compose)
8340 GSList *tmpl_list, *cur;
8344 tmpl_list = template_get_config();
8346 menu = gtk_menu_new();
8348 gtk_menu_set_accel_group (GTK_MENU (menu),
8349 gtk_ui_manager_get_accel_group(compose->ui_manager));
8350 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8351 Template *tmpl = (Template *)cur->data;
8352 gchar *accel_path = NULL;
8353 item = gtk_menu_item_new_with_label(tmpl->name);
8354 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8355 g_signal_connect(G_OBJECT(item), "activate",
8356 G_CALLBACK(compose_template_activate_cb),
8358 g_object_set_data(G_OBJECT(item), "template", tmpl);
8359 gtk_widget_show(item);
8360 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8361 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8365 gtk_widget_show(menu);
8366 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8369 void compose_update_actions_menu(Compose *compose)
8371 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8374 static void compose_update_privacy_systems_menu(Compose *compose)
8376 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8377 GSList *systems, *cur;
8379 GtkWidget *system_none;
8381 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8382 GtkWidget *privacy_menu = gtk_menu_new();
8384 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8385 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8387 g_signal_connect(G_OBJECT(system_none), "activate",
8388 G_CALLBACK(compose_set_privacy_system_cb), compose);
8390 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8391 gtk_widget_show(system_none);
8393 systems = privacy_get_system_ids();
8394 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8395 gchar *systemid = cur->data;
8397 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8398 widget = gtk_radio_menu_item_new_with_label(group,
8399 privacy_system_get_name(systemid));
8400 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8401 g_strdup(systemid), g_free);
8402 g_signal_connect(G_OBJECT(widget), "activate",
8403 G_CALLBACK(compose_set_privacy_system_cb), compose);
8405 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8406 gtk_widget_show(widget);
8409 g_slist_free(systems);
8410 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8411 gtk_widget_show_all(privacy_menu);
8412 gtk_widget_show_all(privacy_menuitem);
8415 void compose_reflect_prefs_all(void)
8420 for (cur = compose_list; cur != NULL; cur = cur->next) {
8421 compose = (Compose *)cur->data;
8422 compose_set_template_menu(compose);
8426 void compose_reflect_prefs_pixmap_theme(void)
8431 for (cur = compose_list; cur != NULL; cur = cur->next) {
8432 compose = (Compose *)cur->data;
8433 toolbar_update(TOOLBAR_COMPOSE, compose);
8437 static const gchar *compose_quote_char_from_context(Compose *compose)
8439 const gchar *qmark = NULL;
8441 cm_return_val_if_fail(compose != NULL, NULL);
8443 switch (compose->mode) {
8444 /* use forward-specific quote char */
8445 case COMPOSE_FORWARD:
8446 case COMPOSE_FORWARD_AS_ATTACH:
8447 case COMPOSE_FORWARD_INLINE:
8448 if (compose->folder && compose->folder->prefs &&
8449 compose->folder->prefs->forward_with_format)
8450 qmark = compose->folder->prefs->forward_quotemark;
8451 else if (compose->account->forward_with_format)
8452 qmark = compose->account->forward_quotemark;
8454 qmark = prefs_common.fw_quotemark;
8457 /* use reply-specific quote char in all other modes */
8459 if (compose->folder && compose->folder->prefs &&
8460 compose->folder->prefs->reply_with_format)
8461 qmark = compose->folder->prefs->reply_quotemark;
8462 else if (compose->account->reply_with_format)
8463 qmark = compose->account->reply_quotemark;
8465 qmark = prefs_common.quotemark;
8469 if (qmark == NULL || *qmark == '\0')
8475 static void compose_template_apply(Compose *compose, Template *tmpl,
8479 GtkTextBuffer *buffer;
8483 gchar *parsed_str = NULL;
8484 gint cursor_pos = 0;
8485 const gchar *err_msg = _("The body of the template has an error at line %d.");
8488 /* process the body */
8490 text = GTK_TEXT_VIEW(compose->text);
8491 buffer = gtk_text_view_get_buffer(text);
8494 qmark = compose_quote_char_from_context(compose);
8496 if (compose->replyinfo != NULL) {
8499 gtk_text_buffer_set_text(buffer, "", -1);
8500 mark = gtk_text_buffer_get_insert(buffer);
8501 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8503 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8504 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8506 } else if (compose->fwdinfo != NULL) {
8509 gtk_text_buffer_set_text(buffer, "", -1);
8510 mark = gtk_text_buffer_get_insert(buffer);
8511 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8513 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8514 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8517 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8519 GtkTextIter start, end;
8522 gtk_text_buffer_get_start_iter(buffer, &start);
8523 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8524 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8526 /* clear the buffer now */
8528 gtk_text_buffer_set_text(buffer, "", -1);
8530 parsed_str = compose_quote_fmt(compose, dummyinfo,
8531 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8532 procmsg_msginfo_free( dummyinfo );
8538 gtk_text_buffer_set_text(buffer, "", -1);
8539 mark = gtk_text_buffer_get_insert(buffer);
8540 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8543 if (replace && parsed_str && compose->account->auto_sig)
8544 compose_insert_sig(compose, FALSE);
8546 if (replace && parsed_str) {
8547 gtk_text_buffer_get_start_iter(buffer, &iter);
8548 gtk_text_buffer_place_cursor(buffer, &iter);
8552 cursor_pos = quote_fmt_get_cursor_pos();
8553 compose->set_cursor_pos = cursor_pos;
8554 if (cursor_pos == -1)
8556 gtk_text_buffer_get_start_iter(buffer, &iter);
8557 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8558 gtk_text_buffer_place_cursor(buffer, &iter);
8561 /* process the other fields */
8563 compose_template_apply_fields(compose, tmpl);
8564 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8565 quote_fmt_reset_vartable();
8566 compose_changed_cb(NULL, compose);
8569 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8570 gtkaspell_highlight_all(compose->gtkaspell);
8574 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8576 MsgInfo* dummyinfo = NULL;
8577 MsgInfo *msginfo = NULL;
8580 if (compose->replyinfo != NULL)
8581 msginfo = compose->replyinfo;
8582 else if (compose->fwdinfo != NULL)
8583 msginfo = compose->fwdinfo;
8585 dummyinfo = compose_msginfo_new_from_compose(compose);
8586 msginfo = dummyinfo;
8589 if (tmpl->from && *tmpl->from != '\0') {
8591 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8592 compose->gtkaspell);
8594 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8596 quote_fmt_scan_string(tmpl->from);
8599 buf = quote_fmt_get_buffer();
8601 alertpanel_error(_("Template From format error."));
8603 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8607 if (tmpl->to && *tmpl->to != '\0') {
8609 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8610 compose->gtkaspell);
8612 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8614 quote_fmt_scan_string(tmpl->to);
8617 buf = quote_fmt_get_buffer();
8619 alertpanel_error(_("Template To format error."));
8621 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8625 if (tmpl->cc && *tmpl->cc != '\0') {
8627 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8628 compose->gtkaspell);
8630 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8632 quote_fmt_scan_string(tmpl->cc);
8635 buf = quote_fmt_get_buffer();
8637 alertpanel_error(_("Template Cc format error."));
8639 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8643 if (tmpl->bcc && *tmpl->bcc != '\0') {
8645 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8646 compose->gtkaspell);
8648 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8650 quote_fmt_scan_string(tmpl->bcc);
8653 buf = quote_fmt_get_buffer();
8655 alertpanel_error(_("Template Bcc format error."));
8657 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8661 if (tmpl->replyto && *tmpl->replyto != '\0') {
8663 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8664 compose->gtkaspell);
8666 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8668 quote_fmt_scan_string(tmpl->replyto);
8671 buf = quote_fmt_get_buffer();
8673 alertpanel_error(_("Template Reply-To format error."));
8675 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8679 /* process the subject */
8680 if (tmpl->subject && *tmpl->subject != '\0') {
8682 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8683 compose->gtkaspell);
8685 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8687 quote_fmt_scan_string(tmpl->subject);
8690 buf = quote_fmt_get_buffer();
8692 alertpanel_error(_("Template subject format error."));
8694 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8698 procmsg_msginfo_free( dummyinfo );
8701 static void compose_destroy(Compose *compose)
8703 GtkAllocation allocation;
8704 GtkTextBuffer *buffer;
8705 GtkClipboard *clipboard;
8707 compose_list = g_list_remove(compose_list, compose);
8709 if (compose->updating) {
8710 debug_print("danger, not destroying anything now\n");
8711 compose->deferred_destroy = TRUE;
8715 /* NOTE: address_completion_end() does nothing with the window
8716 * however this may change. */
8717 address_completion_end(compose->window);
8719 slist_free_strings_full(compose->to_list);
8720 slist_free_strings_full(compose->newsgroup_list);
8721 slist_free_strings_full(compose->header_list);
8723 slist_free_strings_full(extra_headers);
8724 extra_headers = NULL;
8726 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8728 g_hash_table_destroy(compose->email_hashtable);
8730 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8731 compose->folder_update_callback_id);
8733 procmsg_msginfo_free(compose->targetinfo);
8734 procmsg_msginfo_free(compose->replyinfo);
8735 procmsg_msginfo_free(compose->fwdinfo);
8737 g_free(compose->replyto);
8738 g_free(compose->cc);
8739 g_free(compose->bcc);
8740 g_free(compose->newsgroups);
8741 g_free(compose->followup_to);
8743 g_free(compose->ml_post);
8745 g_free(compose->inreplyto);
8746 g_free(compose->references);
8747 g_free(compose->msgid);
8748 g_free(compose->boundary);
8750 g_free(compose->redirect_filename);
8751 if (compose->undostruct)
8752 undo_destroy(compose->undostruct);
8754 g_free(compose->sig_str);
8756 g_free(compose->exteditor_file);
8758 g_free(compose->orig_charset);
8760 g_free(compose->privacy_system);
8762 #ifndef USE_NEW_ADDRBOOK
8763 if (addressbook_get_target_compose() == compose)
8764 addressbook_set_target_compose(NULL);
8767 if (compose->gtkaspell) {
8768 gtkaspell_delete(compose->gtkaspell);
8769 compose->gtkaspell = NULL;
8773 if (!compose->batch) {
8774 gtk_widget_get_allocation(compose->window, &allocation);
8775 prefs_common.compose_width = allocation.width;
8776 prefs_common.compose_height = allocation.height;
8779 if (!gtk_widget_get_parent(compose->paned))
8780 gtk_widget_destroy(compose->paned);
8781 gtk_widget_destroy(compose->popupmenu);
8783 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8784 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8785 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8787 gtk_widget_destroy(compose->window);
8788 toolbar_destroy(compose->toolbar);
8789 g_free(compose->toolbar);
8790 cm_mutex_free(compose->mutex);
8794 static void compose_attach_info_free(AttachInfo *ainfo)
8796 g_free(ainfo->file);
8797 g_free(ainfo->content_type);
8798 g_free(ainfo->name);
8799 g_free(ainfo->charset);
8803 static void compose_attach_update_label(Compose *compose)
8808 GtkTreeModel *model;
8813 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8814 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8815 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8819 while(gtk_tree_model_iter_next(model, &iter))
8822 text = g_strdup_printf("(%d)", i);
8823 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8827 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8829 Compose *compose = (Compose *)data;
8830 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8831 GtkTreeSelection *selection;
8833 GtkTreeModel *model;
8835 selection = gtk_tree_view_get_selection(tree_view);
8836 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8841 for (cur = sel; cur != NULL; cur = cur->next) {
8842 GtkTreePath *path = cur->data;
8843 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8846 gtk_tree_path_free(path);
8849 for (cur = sel; cur != NULL; cur = cur->next) {
8850 GtkTreeRowReference *ref = cur->data;
8851 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8854 if (gtk_tree_model_get_iter(model, &iter, path))
8855 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8857 gtk_tree_path_free(path);
8858 gtk_tree_row_reference_free(ref);
8862 compose_attach_update_label(compose);
8865 static struct _AttachProperty
8868 GtkWidget *mimetype_entry;
8869 GtkWidget *encoding_optmenu;
8870 GtkWidget *path_entry;
8871 GtkWidget *filename_entry;
8873 GtkWidget *cancel_btn;
8876 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8878 gtk_tree_path_free((GtkTreePath *)ptr);
8881 static void compose_attach_property(GtkAction *action, gpointer data)
8883 Compose *compose = (Compose *)data;
8884 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8886 GtkComboBox *optmenu;
8887 GtkTreeSelection *selection;
8889 GtkTreeModel *model;
8892 static gboolean cancelled;
8894 /* only if one selected */
8895 selection = gtk_tree_view_get_selection(tree_view);
8896 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8899 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8903 path = (GtkTreePath *) sel->data;
8904 gtk_tree_model_get_iter(model, &iter, path);
8905 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8908 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8914 if (!attach_prop.window)
8915 compose_attach_property_create(&cancelled);
8916 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8917 gtk_widget_grab_focus(attach_prop.ok_btn);
8918 gtk_widget_show(attach_prop.window);
8919 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8920 GTK_WINDOW(compose->window));
8922 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8923 if (ainfo->encoding == ENC_UNKNOWN)
8924 combobox_select_by_data(optmenu, ENC_BASE64);
8926 combobox_select_by_data(optmenu, ainfo->encoding);
8928 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8929 ainfo->content_type ? ainfo->content_type : "");
8930 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8931 ainfo->file ? ainfo->file : "");
8932 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8933 ainfo->name ? ainfo->name : "");
8936 const gchar *entry_text;
8938 gchar *cnttype = NULL;
8945 gtk_widget_hide(attach_prop.window);
8946 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8951 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8952 if (*entry_text != '\0') {
8955 text = g_strstrip(g_strdup(entry_text));
8956 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8957 cnttype = g_strdup(text);
8960 alertpanel_error(_("Invalid MIME type."));
8966 ainfo->encoding = combobox_get_active_data(optmenu);
8968 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8969 if (*entry_text != '\0') {
8970 if (is_file_exist(entry_text) &&
8971 (size = get_file_size(entry_text)) > 0)
8972 file = g_strdup(entry_text);
8975 (_("File doesn't exist or is empty."));
8981 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8982 if (*entry_text != '\0') {
8983 g_free(ainfo->name);
8984 ainfo->name = g_strdup(entry_text);
8988 g_free(ainfo->content_type);
8989 ainfo->content_type = cnttype;
8992 g_free(ainfo->file);
8996 ainfo->size = (goffset)size;
8998 /* update tree store */
8999 text = to_human_readable(ainfo->size);
9000 gtk_tree_model_get_iter(model, &iter, path);
9001 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9002 COL_MIMETYPE, ainfo->content_type,
9004 COL_NAME, ainfo->name,
9005 COL_CHARSET, ainfo->charset,
9011 gtk_tree_path_free(path);
9014 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9016 label = gtk_label_new(str); \
9017 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9018 GTK_FILL, 0, 0, 0); \
9019 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9021 entry = gtk_entry_new(); \
9022 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9023 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9026 static void compose_attach_property_create(gboolean *cancelled)
9032 GtkWidget *mimetype_entry;
9035 GtkListStore *optmenu_menu;
9036 GtkWidget *path_entry;
9037 GtkWidget *filename_entry;
9040 GtkWidget *cancel_btn;
9041 GList *mime_type_list, *strlist;
9044 debug_print("Creating attach_property window...\n");
9046 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9047 gtk_widget_set_size_request(window, 480, -1);
9048 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9049 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9050 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9051 g_signal_connect(G_OBJECT(window), "delete_event",
9052 G_CALLBACK(attach_property_delete_event),
9054 g_signal_connect(G_OBJECT(window), "key_press_event",
9055 G_CALLBACK(attach_property_key_pressed),
9058 vbox = gtk_vbox_new(FALSE, 8);
9059 gtk_container_add(GTK_CONTAINER(window), vbox);
9061 table = gtk_table_new(4, 2, FALSE);
9062 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9063 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9064 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9066 label = gtk_label_new(_("MIME type"));
9067 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9069 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9070 #if !GTK_CHECK_VERSION(2, 24, 0)
9071 mimetype_entry = gtk_combo_box_entry_new_text();
9073 mimetype_entry = gtk_combo_box_text_new_with_entry();
9075 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9076 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9078 /* stuff with list */
9079 mime_type_list = procmime_get_mime_type_list();
9081 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9082 MimeType *type = (MimeType *) mime_type_list->data;
9085 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9087 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9090 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9091 (GCompareFunc)strcmp2);
9094 for (mime_type_list = strlist; mime_type_list != NULL;
9095 mime_type_list = mime_type_list->next) {
9096 #if !GTK_CHECK_VERSION(2, 24, 0)
9097 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9099 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9101 g_free(mime_type_list->data);
9103 g_list_free(strlist);
9104 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9105 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9107 label = gtk_label_new(_("Encoding"));
9108 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9110 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9112 hbox = gtk_hbox_new(FALSE, 0);
9113 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9114 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9116 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9117 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9119 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9120 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9121 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9122 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9123 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9125 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9127 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9128 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9130 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9131 &ok_btn, GTK_STOCK_OK,
9133 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9134 gtk_widget_grab_default(ok_btn);
9136 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9137 G_CALLBACK(attach_property_ok),
9139 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9140 G_CALLBACK(attach_property_cancel),
9143 gtk_widget_show_all(vbox);
9145 attach_prop.window = window;
9146 attach_prop.mimetype_entry = mimetype_entry;
9147 attach_prop.encoding_optmenu = optmenu;
9148 attach_prop.path_entry = path_entry;
9149 attach_prop.filename_entry = filename_entry;
9150 attach_prop.ok_btn = ok_btn;
9151 attach_prop.cancel_btn = cancel_btn;
9154 #undef SET_LABEL_AND_ENTRY
9156 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9162 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9168 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9169 gboolean *cancelled)
9177 static gboolean attach_property_key_pressed(GtkWidget *widget,
9179 gboolean *cancelled)
9181 if (event && event->keyval == GDK_KEY_Escape) {
9185 if (event && event->keyval == GDK_KEY_Return) {
9193 static void compose_exec_ext_editor(Compose *compose)
9200 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9201 G_DIR_SEPARATOR, compose);
9203 if (pipe(pipe_fds) < 0) {
9209 if ((pid = fork()) < 0) {
9216 /* close the write side of the pipe */
9219 compose->exteditor_file = g_strdup(tmp);
9220 compose->exteditor_pid = pid;
9222 compose_set_ext_editor_sensitive(compose, FALSE);
9225 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9227 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9229 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9233 } else { /* process-monitoring process */
9239 /* close the read side of the pipe */
9242 if (compose_write_body_to_file(compose, tmp) < 0) {
9243 fd_write_all(pipe_fds[1], "2\n", 2);
9247 pid_ed = compose_exec_ext_editor_real(tmp);
9249 fd_write_all(pipe_fds[1], "1\n", 2);
9253 /* wait until editor is terminated */
9254 waitpid(pid_ed, NULL, 0);
9256 fd_write_all(pipe_fds[1], "0\n", 2);
9263 #endif /* G_OS_UNIX */
9267 static gint compose_exec_ext_editor_real(const gchar *file)
9274 cm_return_val_if_fail(file != NULL, -1);
9276 if ((pid = fork()) < 0) {
9281 if (pid != 0) return pid;
9283 /* grandchild process */
9285 if (setpgid(0, getppid()))
9288 if (prefs_common_get_ext_editor_cmd() &&
9289 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9290 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9291 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9293 if (prefs_common_get_ext_editor_cmd())
9294 g_warning("External editor command-line is invalid: '%s'\n",
9295 prefs_common_get_ext_editor_cmd());
9296 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9299 cmdline = strsplit_with_quote(buf, " ", 1024);
9300 execvp(cmdline[0], cmdline);
9303 g_strfreev(cmdline);
9308 static gboolean compose_ext_editor_kill(Compose *compose)
9310 pid_t pgid = compose->exteditor_pid * -1;
9313 ret = kill(pgid, 0);
9315 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9319 msg = g_strdup_printf
9320 (_("The external editor is still working.\n"
9321 "Force terminating the process?\n"
9322 "process group id: %d"), -pgid);
9323 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9324 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9328 if (val == G_ALERTALTERNATE) {
9329 g_source_remove(compose->exteditor_tag);
9330 g_io_channel_shutdown(compose->exteditor_ch,
9332 g_io_channel_unref(compose->exteditor_ch);
9334 if (kill(pgid, SIGTERM) < 0) perror("kill");
9335 waitpid(compose->exteditor_pid, NULL, 0);
9337 g_warning("Terminated process group id: %d", -pgid);
9338 g_warning("Temporary file: %s",
9339 compose->exteditor_file);
9341 compose_set_ext_editor_sensitive(compose, TRUE);
9343 g_free(compose->exteditor_file);
9344 compose->exteditor_file = NULL;
9345 compose->exteditor_pid = -1;
9346 compose->exteditor_ch = NULL;
9347 compose->exteditor_tag = -1;
9355 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9359 Compose *compose = (Compose *)data;
9362 debug_print("Compose: input from monitoring process\n");
9364 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9366 g_io_channel_shutdown(source, FALSE, NULL);
9367 g_io_channel_unref(source);
9369 waitpid(compose->exteditor_pid, NULL, 0);
9371 if (buf[0] == '0') { /* success */
9372 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9373 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9375 gtk_text_buffer_set_text(buffer, "", -1);
9376 compose_insert_file(compose, compose->exteditor_file);
9377 compose_changed_cb(NULL, compose);
9378 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9380 if (claws_unlink(compose->exteditor_file) < 0)
9381 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9382 } else if (buf[0] == '1') { /* failed */
9383 g_warning("Couldn't exec external editor\n");
9384 if (claws_unlink(compose->exteditor_file) < 0)
9385 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9386 } else if (buf[0] == '2') {
9387 g_warning("Couldn't write to file\n");
9388 } else if (buf[0] == '3') {
9389 g_warning("Pipe read failed\n");
9392 compose_set_ext_editor_sensitive(compose, TRUE);
9394 g_free(compose->exteditor_file);
9395 compose->exteditor_file = NULL;
9396 compose->exteditor_pid = -1;
9397 compose->exteditor_ch = NULL;
9398 compose->exteditor_tag = -1;
9403 static void compose_set_ext_editor_sensitive(Compose *compose,
9406 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9407 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9408 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9409 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9410 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9411 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9412 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9413 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9415 gtk_widget_set_sensitive(compose->text, sensitive);
9416 if (compose->toolbar->send_btn)
9417 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9418 if (compose->toolbar->sendl_btn)
9419 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9420 if (compose->toolbar->draft_btn)
9421 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9422 if (compose->toolbar->insert_btn)
9423 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9424 if (compose->toolbar->sig_btn)
9425 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9426 if (compose->toolbar->exteditor_btn)
9427 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9428 if (compose->toolbar->linewrap_current_btn)
9429 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9430 if (compose->toolbar->linewrap_all_btn)
9431 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9433 #endif /* G_OS_UNIX */
9436 * compose_undo_state_changed:
9438 * Change the sensivity of the menuentries undo and redo
9440 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9441 gint redo_state, gpointer data)
9443 Compose *compose = (Compose *)data;
9445 switch (undo_state) {
9446 case UNDO_STATE_TRUE:
9447 if (!undostruct->undo_state) {
9448 undostruct->undo_state = TRUE;
9449 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9452 case UNDO_STATE_FALSE:
9453 if (undostruct->undo_state) {
9454 undostruct->undo_state = FALSE;
9455 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9458 case UNDO_STATE_UNCHANGED:
9460 case UNDO_STATE_REFRESH:
9461 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9464 g_warning("Undo state not recognized");
9468 switch (redo_state) {
9469 case UNDO_STATE_TRUE:
9470 if (!undostruct->redo_state) {
9471 undostruct->redo_state = TRUE;
9472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9475 case UNDO_STATE_FALSE:
9476 if (undostruct->redo_state) {
9477 undostruct->redo_state = FALSE;
9478 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9481 case UNDO_STATE_UNCHANGED:
9483 case UNDO_STATE_REFRESH:
9484 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9487 g_warning("Redo state not recognized");
9492 /* callback functions */
9494 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9495 GtkAllocation *allocation,
9498 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9501 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9502 * includes "non-client" (windows-izm) in calculation, so this calculation
9503 * may not be accurate.
9505 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9506 GtkAllocation *allocation,
9507 GtkSHRuler *shruler)
9509 if (prefs_common.show_ruler) {
9510 gint char_width = 0, char_height = 0;
9511 gint line_width_in_chars;
9513 gtkut_get_font_size(GTK_WIDGET(widget),
9514 &char_width, &char_height);
9515 line_width_in_chars =
9516 (allocation->width - allocation->x) / char_width;
9518 /* got the maximum */
9519 gtk_shruler_set_range(GTK_SHRULER(shruler),
9520 0.0, line_width_in_chars, 0);
9529 ComposePrefType type;
9530 gboolean entry_marked;
9533 static void account_activated(GtkComboBox *optmenu, gpointer data)
9535 Compose *compose = (Compose *)data;
9538 gchar *folderidentifier;
9539 gint account_id = 0;
9542 GSList *list, *saved_list = NULL;
9543 HeaderEntryState *state;
9544 GtkRcStyle *style = NULL;
9545 #if !GTK_CHECK_VERSION(3, 0, 0)
9546 static GdkColor yellow;
9547 static gboolean color_set = FALSE;
9549 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9552 /* Get ID of active account in the combo box */
9553 menu = gtk_combo_box_get_model(optmenu);
9554 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9555 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9557 ac = account_find_from_id(account_id);
9558 cm_return_if_fail(ac != NULL);
9560 if (ac != compose->account) {
9561 compose_select_account(compose, ac, FALSE);
9563 for (list = compose->header_list; list; list = list->next) {
9564 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9566 if (hentry->type == PREF_ACCOUNT || !list->next) {
9567 compose_destroy_headerentry(compose, hentry);
9571 state = g_malloc0(sizeof(HeaderEntryState));
9572 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9573 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9574 state->entry = gtk_editable_get_chars(
9575 GTK_EDITABLE(hentry->entry), 0, -1);
9576 state->type = hentry->type;
9578 #if !GTK_CHECK_VERSION(3, 0, 0)
9580 gdk_color_parse("#f5f6be", &yellow);
9581 color_set = gdk_colormap_alloc_color(
9582 gdk_colormap_get_system(),
9583 &yellow, FALSE, TRUE);
9587 style = gtk_widget_get_modifier_style(hentry->entry);
9588 state->entry_marked = gdk_color_equal(&yellow,
9589 &style->base[GTK_STATE_NORMAL]);
9591 saved_list = g_slist_append(saved_list, state);
9592 compose_destroy_headerentry(compose, hentry);
9595 compose->header_last = NULL;
9596 g_slist_free(compose->header_list);
9597 compose->header_list = NULL;
9598 compose->header_nextrow = 1;
9599 compose_create_header_entry(compose);
9601 if (ac->set_autocc && ac->auto_cc)
9602 compose_entry_append(compose, ac->auto_cc,
9603 COMPOSE_CC, PREF_ACCOUNT);
9605 if (ac->set_autobcc && ac->auto_bcc)
9606 compose_entry_append(compose, ac->auto_bcc,
9607 COMPOSE_BCC, PREF_ACCOUNT);
9609 if (ac->set_autoreplyto && ac->auto_replyto)
9610 compose_entry_append(compose, ac->auto_replyto,
9611 COMPOSE_REPLYTO, PREF_ACCOUNT);
9613 for (list = saved_list; list; list = list->next) {
9614 state = (HeaderEntryState *) list->data;
9616 compose_add_header_entry(compose, state->header,
9617 state->entry, state->type);
9618 if (state->entry_marked)
9619 compose_entry_mark_default_to(compose, state->entry);
9621 g_free(state->header);
9622 g_free(state->entry);
9625 g_slist_free(saved_list);
9627 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9628 (ac->protocol == A_NNTP) ?
9629 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9632 /* Set message save folder */
9633 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9634 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9636 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9637 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9639 compose_set_save_to(compose, NULL);
9640 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9641 folderidentifier = folder_item_get_identifier(account_get_special_folder
9642 (compose->account, F_OUTBOX));
9643 compose_set_save_to(compose, folderidentifier);
9644 g_free(folderidentifier);
9648 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9649 GtkTreeViewColumn *column, Compose *compose)
9651 compose_attach_property(NULL, compose);
9654 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9657 Compose *compose = (Compose *)data;
9658 GtkTreeSelection *attach_selection;
9659 gint attach_nr_selected;
9661 if (!event) return FALSE;
9663 if (event->button == 3) {
9664 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9665 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9667 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9668 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9670 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9671 NULL, NULL, event->button, event->time);
9678 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9681 Compose *compose = (Compose *)data;
9683 if (!event) return FALSE;
9685 switch (event->keyval) {
9686 case GDK_KEY_Delete:
9687 compose_attach_remove_selected(NULL, compose);
9693 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9695 toolbar_comp_set_sensitive(compose, allow);
9696 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9697 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9699 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9701 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9702 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9703 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9705 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9709 static void compose_send_cb(GtkAction *action, gpointer data)
9711 Compose *compose = (Compose *)data;
9713 if (prefs_common.work_offline &&
9714 !inc_offline_should_override(TRUE,
9715 _("Claws Mail needs network access in order "
9716 "to send this email.")))
9719 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9720 g_source_remove(compose->draft_timeout_tag);
9721 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9724 compose_send(compose);
9727 static void compose_send_later_cb(GtkAction *action, gpointer data)
9729 Compose *compose = (Compose *)data;
9733 compose_allow_user_actions(compose, FALSE);
9734 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9735 compose_allow_user_actions(compose, TRUE);
9739 compose_close(compose);
9740 } else if (val == -1) {
9741 alertpanel_error(_("Could not queue message."));
9742 } else if (val == -2) {
9743 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9744 } else if (val == -3) {
9745 if (privacy_peek_error())
9746 alertpanel_error(_("Could not queue message for sending:\n\n"
9747 "Signature failed: %s"), privacy_get_error());
9748 } else if (val == -4) {
9749 alertpanel_error(_("Could not queue message for sending:\n\n"
9750 "Charset conversion failed."));
9751 } else if (val == -5) {
9752 alertpanel_error(_("Could not queue message for sending:\n\n"
9753 "Couldn't get recipient encryption key."));
9754 } else if (val == -6) {
9757 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9760 #define DRAFTED_AT_EXIT "drafted_at_exit"
9761 static void compose_register_draft(MsgInfo *info)
9763 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9764 DRAFTED_AT_EXIT, NULL);
9765 FILE *fp = g_fopen(filepath, "ab");
9768 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9776 gboolean compose_draft (gpointer data, guint action)
9778 Compose *compose = (Compose *)data;
9783 MsgFlags flag = {0, 0};
9784 static gboolean lock = FALSE;
9785 MsgInfo *newmsginfo;
9787 gboolean target_locked = FALSE;
9788 gboolean err = FALSE;
9790 if (lock) return FALSE;
9792 if (compose->sending)
9795 draft = account_get_special_folder(compose->account, F_DRAFT);
9796 cm_return_val_if_fail(draft != NULL, FALSE);
9798 if (!g_mutex_trylock(compose->mutex)) {
9799 /* we don't want to lock the mutex once it's available,
9800 * because as the only other part of compose.c locking
9801 * it is compose_close - which means once unlocked,
9802 * the compose struct will be freed */
9803 debug_print("couldn't lock mutex, probably sending\n");
9809 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9810 G_DIR_SEPARATOR, compose);
9811 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9812 FILE_OP_ERROR(tmp, "fopen");
9816 /* chmod for security */
9817 if (change_file_mode_rw(fp, tmp) < 0) {
9818 FILE_OP_ERROR(tmp, "chmod");
9819 g_warning("can't change file mode\n");
9822 /* Save draft infos */
9823 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9824 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9826 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9827 gchar *savefolderid;
9829 savefolderid = compose_get_save_to(compose);
9830 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9831 g_free(savefolderid);
9833 if (compose->return_receipt) {
9834 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9836 if (compose->privacy_system) {
9837 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9838 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9839 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9842 /* Message-ID of message replying to */
9843 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9844 gchar *folderid = NULL;
9846 if (compose->replyinfo->folder)
9847 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9848 if (folderid == NULL)
9849 folderid = g_strdup("NULL");
9851 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9854 /* Message-ID of message forwarding to */
9855 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9856 gchar *folderid = NULL;
9858 if (compose->fwdinfo->folder)
9859 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9860 if (folderid == NULL)
9861 folderid = g_strdup("NULL");
9863 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9867 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9868 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9870 sheaders = compose_get_manual_headers_info(compose);
9871 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9874 /* end of headers */
9875 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9882 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9886 if (fclose(fp) == EOF) {
9890 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9891 if (compose->targetinfo) {
9892 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9894 flag.perm_flags |= MSG_LOCKED;
9896 flag.tmp_flags = MSG_DRAFT;
9898 folder_item_scan(draft);
9899 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9900 MsgInfo *tmpinfo = NULL;
9901 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9902 if (compose->msgid) {
9903 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9906 msgnum = tmpinfo->msgnum;
9907 procmsg_msginfo_free(tmpinfo);
9908 debug_print("got draft msgnum %d from scanning\n", msgnum);
9910 debug_print("didn't get draft msgnum after scanning\n");
9913 debug_print("got draft msgnum %d from adding\n", msgnum);
9919 if (action != COMPOSE_AUTO_SAVE) {
9920 if (action != COMPOSE_DRAFT_FOR_EXIT)
9921 alertpanel_error(_("Could not save draft."));
9924 gtkut_window_popup(compose->window);
9925 val = alertpanel_full(_("Could not save draft"),
9926 _("Could not save draft.\n"
9927 "Do you want to cancel exit or discard this email?"),
9928 _("_Cancel exit"), _("_Discard email"), NULL,
9929 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9930 if (val == G_ALERTALTERNATE) {
9932 g_mutex_unlock(compose->mutex); /* must be done before closing */
9933 compose_close(compose);
9937 g_mutex_unlock(compose->mutex); /* must be done before closing */
9946 if (compose->mode == COMPOSE_REEDIT) {
9947 compose_remove_reedit_target(compose, TRUE);
9950 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9953 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9955 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9957 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9958 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9959 procmsg_msginfo_set_flags(newmsginfo, 0,
9960 MSG_HAS_ATTACHMENT);
9962 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9963 compose_register_draft(newmsginfo);
9965 procmsg_msginfo_free(newmsginfo);
9968 folder_item_scan(draft);
9970 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9972 g_mutex_unlock(compose->mutex); /* must be done before closing */
9973 compose_close(compose);
9979 path = folder_item_fetch_msg(draft, msgnum);
9981 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9984 if (g_stat(path, &s) < 0) {
9985 FILE_OP_ERROR(path, "stat");
9991 procmsg_msginfo_free(compose->targetinfo);
9992 compose->targetinfo = procmsg_msginfo_new();
9993 compose->targetinfo->msgnum = msgnum;
9994 compose->targetinfo->size = (goffset)s.st_size;
9995 compose->targetinfo->mtime = s.st_mtime;
9996 compose->targetinfo->folder = draft;
9998 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9999 compose->mode = COMPOSE_REEDIT;
10001 if (action == COMPOSE_AUTO_SAVE) {
10002 compose->autosaved_draft = compose->targetinfo;
10004 compose->modified = FALSE;
10005 compose_set_title(compose);
10009 g_mutex_unlock(compose->mutex);
10013 void compose_clear_exit_drafts(void)
10015 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10016 DRAFTED_AT_EXIT, NULL);
10017 if (is_file_exist(filepath))
10018 claws_unlink(filepath);
10023 void compose_reopen_exit_drafts(void)
10025 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10026 DRAFTED_AT_EXIT, NULL);
10027 FILE *fp = g_fopen(filepath, "rb");
10031 while (fgets(buf, sizeof(buf), fp)) {
10032 gchar **parts = g_strsplit(buf, "\t", 2);
10033 const gchar *folder = parts[0];
10034 int msgnum = parts[1] ? atoi(parts[1]):-1;
10036 if (folder && *folder && msgnum > -1) {
10037 FolderItem *item = folder_find_item_from_identifier(folder);
10038 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10040 compose_reedit(info, FALSE);
10047 compose_clear_exit_drafts();
10050 static void compose_save_cb(GtkAction *action, gpointer data)
10052 Compose *compose = (Compose *)data;
10053 compose_draft(compose, COMPOSE_KEEP_EDITING);
10054 compose->rmode = COMPOSE_REEDIT;
10057 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10059 if (compose && file_list) {
10062 for ( tmp = file_list; tmp; tmp = tmp->next) {
10063 gchar *file = (gchar *) tmp->data;
10064 gchar *utf8_filename = conv_filename_to_utf8(file);
10065 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10066 compose_changed_cb(NULL, compose);
10071 g_free(utf8_filename);
10076 static void compose_attach_cb(GtkAction *action, gpointer data)
10078 Compose *compose = (Compose *)data;
10081 if (compose->redirect_filename != NULL)
10084 /* Set focus_window properly, in case we were called via popup menu,
10085 * which unsets it (via focus_out_event callback on compose window). */
10086 manage_window_focus_in(compose->window, NULL, NULL);
10088 file_list = filesel_select_multiple_files_open(_("Select file"));
10091 compose_attach_from_list(compose, file_list, TRUE);
10092 g_list_free(file_list);
10096 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10098 Compose *compose = (Compose *)data;
10100 gint files_inserted = 0;
10102 file_list = filesel_select_multiple_files_open(_("Select file"));
10107 for ( tmp = file_list; tmp; tmp = tmp->next) {
10108 gchar *file = (gchar *) tmp->data;
10109 gchar *filedup = g_strdup(file);
10110 gchar *shortfile = g_path_get_basename(filedup);
10111 ComposeInsertResult res;
10112 /* insert the file if the file is short or if the user confirmed that
10113 he/she wants to insert the large file */
10114 res = compose_insert_file(compose, file);
10115 if (res == COMPOSE_INSERT_READ_ERROR) {
10116 alertpanel_error(_("File '%s' could not be read."), shortfile);
10117 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10118 alertpanel_error(_("File '%s' contained invalid characters\n"
10119 "for the current encoding, insertion may be incorrect."),
10121 } else if (res == COMPOSE_INSERT_SUCCESS)
10128 g_list_free(file_list);
10132 if (files_inserted > 0 && compose->gtkaspell &&
10133 compose->gtkaspell->check_while_typing)
10134 gtkaspell_highlight_all(compose->gtkaspell);
10138 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10140 Compose *compose = (Compose *)data;
10142 compose_insert_sig(compose, FALSE);
10145 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10147 Compose *compose = (Compose *)data;
10149 compose_insert_sig(compose, TRUE);
10152 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10156 Compose *compose = (Compose *)data;
10158 gtkut_widget_get_uposition(widget, &x, &y);
10159 if (!compose->batch) {
10160 prefs_common.compose_x = x;
10161 prefs_common.compose_y = y;
10163 if (compose->sending || compose->updating)
10165 compose_close_cb(NULL, compose);
10169 void compose_close_toolbar(Compose *compose)
10171 compose_close_cb(NULL, compose);
10174 static gboolean compose_can_autosave(Compose *compose)
10176 if (compose->privacy_system && compose->use_encryption)
10177 return prefs_common.autosave && prefs_common.autosave_encrypted;
10179 return prefs_common.autosave;
10182 static void compose_close_cb(GtkAction *action, gpointer data)
10184 Compose *compose = (Compose *)data;
10188 if (compose->exteditor_tag != -1) {
10189 if (!compose_ext_editor_kill(compose))
10194 if (compose->modified) {
10195 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10196 if (!g_mutex_trylock(compose->mutex)) {
10197 /* we don't want to lock the mutex once it's available,
10198 * because as the only other part of compose.c locking
10199 * it is compose_close - which means once unlocked,
10200 * the compose struct will be freed */
10201 debug_print("couldn't lock mutex, probably sending\n");
10205 val = alertpanel(_("Discard message"),
10206 _("This message has been modified. Discard it?"),
10207 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10209 val = alertpanel(_("Save changes"),
10210 _("This message has been modified. Save the latest changes?"),
10211 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10213 g_mutex_unlock(compose->mutex);
10215 case G_ALERTDEFAULT:
10216 if (compose_can_autosave(compose) && !reedit)
10217 compose_remove_draft(compose);
10219 case G_ALERTALTERNATE:
10220 compose_draft(data, COMPOSE_QUIT_EDITING);
10227 compose_close(compose);
10230 static void compose_print_cb(GtkAction *action, gpointer data)
10232 Compose *compose = (Compose *) data;
10234 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10235 if (compose->targetinfo)
10236 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10239 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10241 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10242 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10243 Compose *compose = (Compose *) data;
10246 compose->out_encoding = (CharSet)value;
10249 static void compose_address_cb(GtkAction *action, gpointer data)
10251 Compose *compose = (Compose *)data;
10253 #ifndef USE_NEW_ADDRBOOK
10254 addressbook_open(compose);
10256 GError* error = NULL;
10257 addressbook_connect_signals(compose);
10258 addressbook_dbus_open(TRUE, &error);
10260 g_warning("%s", error->message);
10261 g_error_free(error);
10266 static void about_show_cb(GtkAction *action, gpointer data)
10271 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10273 Compose *compose = (Compose *)data;
10278 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10279 cm_return_if_fail(tmpl != NULL);
10281 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10283 val = alertpanel(_("Apply template"), msg,
10284 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10287 if (val == G_ALERTDEFAULT)
10288 compose_template_apply(compose, tmpl, TRUE);
10289 else if (val == G_ALERTALTERNATE)
10290 compose_template_apply(compose, tmpl, FALSE);
10293 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10295 Compose *compose = (Compose *)data;
10297 compose_exec_ext_editor(compose);
10300 static void compose_undo_cb(GtkAction *action, gpointer data)
10302 Compose *compose = (Compose *)data;
10303 gboolean prev_autowrap = compose->autowrap;
10305 compose->autowrap = FALSE;
10306 undo_undo(compose->undostruct);
10307 compose->autowrap = prev_autowrap;
10310 static void compose_redo_cb(GtkAction *action, gpointer data)
10312 Compose *compose = (Compose *)data;
10313 gboolean prev_autowrap = compose->autowrap;
10315 compose->autowrap = FALSE;
10316 undo_redo(compose->undostruct);
10317 compose->autowrap = prev_autowrap;
10320 static void entry_cut_clipboard(GtkWidget *entry)
10322 if (GTK_IS_EDITABLE(entry))
10323 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10324 else if (GTK_IS_TEXT_VIEW(entry))
10325 gtk_text_buffer_cut_clipboard(
10326 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10327 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10331 static void entry_copy_clipboard(GtkWidget *entry)
10333 if (GTK_IS_EDITABLE(entry))
10334 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10335 else if (GTK_IS_TEXT_VIEW(entry))
10336 gtk_text_buffer_copy_clipboard(
10337 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10338 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10341 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10342 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10344 if (GTK_IS_TEXT_VIEW(entry)) {
10345 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10346 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10347 GtkTextIter start_iter, end_iter;
10349 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10351 if (contents == NULL)
10354 /* we shouldn't delete the selection when middle-click-pasting, or we
10355 * can't mid-click-paste our own selection */
10356 if (clip != GDK_SELECTION_PRIMARY) {
10357 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10358 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10361 if (insert_place == NULL) {
10362 /* if insert_place isn't specified, insert at the cursor.
10363 * used for Ctrl-V pasting */
10364 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10365 start = gtk_text_iter_get_offset(&start_iter);
10366 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10368 /* if insert_place is specified, paste here.
10369 * used for mid-click-pasting */
10370 start = gtk_text_iter_get_offset(insert_place);
10371 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10372 if (prefs_common.primary_paste_unselects)
10373 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10377 /* paste unwrapped: mark the paste so it's not wrapped later */
10378 end = start + strlen(contents);
10379 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10380 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10381 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10382 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10383 /* rewrap paragraph now (after a mid-click-paste) */
10384 mark_start = gtk_text_buffer_get_insert(buffer);
10385 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10386 gtk_text_iter_backward_char(&start_iter);
10387 compose_beautify_paragraph(compose, &start_iter, TRUE);
10389 } else if (GTK_IS_EDITABLE(entry))
10390 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10392 compose->modified = TRUE;
10395 static void entry_allsel(GtkWidget *entry)
10397 if (GTK_IS_EDITABLE(entry))
10398 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10399 else if (GTK_IS_TEXT_VIEW(entry)) {
10400 GtkTextIter startiter, enditer;
10401 GtkTextBuffer *textbuf;
10403 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10404 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10405 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10407 gtk_text_buffer_move_mark_by_name(textbuf,
10408 "selection_bound", &startiter);
10409 gtk_text_buffer_move_mark_by_name(textbuf,
10410 "insert", &enditer);
10414 static void compose_cut_cb(GtkAction *action, gpointer data)
10416 Compose *compose = (Compose *)data;
10417 if (compose->focused_editable
10418 #ifndef GENERIC_UMPC
10419 && gtk_widget_has_focus(compose->focused_editable)
10422 entry_cut_clipboard(compose->focused_editable);
10425 static void compose_copy_cb(GtkAction *action, gpointer data)
10427 Compose *compose = (Compose *)data;
10428 if (compose->focused_editable
10429 #ifndef GENERIC_UMPC
10430 && gtk_widget_has_focus(compose->focused_editable)
10433 entry_copy_clipboard(compose->focused_editable);
10436 static void compose_paste_cb(GtkAction *action, gpointer data)
10438 Compose *compose = (Compose *)data;
10439 gint prev_autowrap;
10440 GtkTextBuffer *buffer;
10442 if (compose->focused_editable &&
10443 #ifndef GENERIC_UMPC
10444 gtk_widget_has_focus(compose->focused_editable)
10447 entry_paste_clipboard(compose, compose->focused_editable,
10448 prefs_common.linewrap_pastes,
10449 GDK_SELECTION_CLIPBOARD, NULL);
10454 #ifndef GENERIC_UMPC
10455 gtk_widget_has_focus(compose->text) &&
10457 compose->gtkaspell &&
10458 compose->gtkaspell->check_while_typing)
10459 gtkaspell_highlight_all(compose->gtkaspell);
10463 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10465 Compose *compose = (Compose *)data;
10466 gint wrap_quote = prefs_common.linewrap_quote;
10467 if (compose->focused_editable
10468 #ifndef GENERIC_UMPC
10469 && gtk_widget_has_focus(compose->focused_editable)
10472 /* let text_insert() (called directly or at a later time
10473 * after the gtk_editable_paste_clipboard) know that
10474 * text is to be inserted as a quotation. implemented
10475 * by using a simple refcount... */
10476 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10477 G_OBJECT(compose->focused_editable),
10478 "paste_as_quotation"));
10479 g_object_set_data(G_OBJECT(compose->focused_editable),
10480 "paste_as_quotation",
10481 GINT_TO_POINTER(paste_as_quotation + 1));
10482 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10483 entry_paste_clipboard(compose, compose->focused_editable,
10484 prefs_common.linewrap_pastes,
10485 GDK_SELECTION_CLIPBOARD, NULL);
10486 prefs_common.linewrap_quote = wrap_quote;
10490 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10492 Compose *compose = (Compose *)data;
10493 gint prev_autowrap;
10494 GtkTextBuffer *buffer;
10496 if (compose->focused_editable
10497 #ifndef GENERIC_UMPC
10498 && gtk_widget_has_focus(compose->focused_editable)
10501 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10502 GDK_SELECTION_CLIPBOARD, NULL);
10507 #ifndef GENERIC_UMPC
10508 gtk_widget_has_focus(compose->text) &&
10510 compose->gtkaspell &&
10511 compose->gtkaspell->check_while_typing)
10512 gtkaspell_highlight_all(compose->gtkaspell);
10516 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10518 Compose *compose = (Compose *)data;
10519 gint prev_autowrap;
10520 GtkTextBuffer *buffer;
10522 if (compose->focused_editable
10523 #ifndef GENERIC_UMPC
10524 && gtk_widget_has_focus(compose->focused_editable)
10527 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10528 GDK_SELECTION_CLIPBOARD, NULL);
10533 #ifndef GENERIC_UMPC
10534 gtk_widget_has_focus(compose->text) &&
10536 compose->gtkaspell &&
10537 compose->gtkaspell->check_while_typing)
10538 gtkaspell_highlight_all(compose->gtkaspell);
10542 static void compose_allsel_cb(GtkAction *action, gpointer data)
10544 Compose *compose = (Compose *)data;
10545 if (compose->focused_editable
10546 #ifndef GENERIC_UMPC
10547 && gtk_widget_has_focus(compose->focused_editable)
10550 entry_allsel(compose->focused_editable);
10553 static void textview_move_beginning_of_line (GtkTextView *text)
10555 GtkTextBuffer *buffer;
10559 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10561 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10562 mark = gtk_text_buffer_get_insert(buffer);
10563 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10564 gtk_text_iter_set_line_offset(&ins, 0);
10565 gtk_text_buffer_place_cursor(buffer, &ins);
10568 static void textview_move_forward_character (GtkTextView *text)
10570 GtkTextBuffer *buffer;
10574 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10576 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10577 mark = gtk_text_buffer_get_insert(buffer);
10578 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10579 if (gtk_text_iter_forward_cursor_position(&ins))
10580 gtk_text_buffer_place_cursor(buffer, &ins);
10583 static void textview_move_backward_character (GtkTextView *text)
10585 GtkTextBuffer *buffer;
10589 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10591 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10592 mark = gtk_text_buffer_get_insert(buffer);
10593 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10594 if (gtk_text_iter_backward_cursor_position(&ins))
10595 gtk_text_buffer_place_cursor(buffer, &ins);
10598 static void textview_move_forward_word (GtkTextView *text)
10600 GtkTextBuffer *buffer;
10605 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10607 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10608 mark = gtk_text_buffer_get_insert(buffer);
10609 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10610 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10611 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10612 gtk_text_iter_backward_word_start(&ins);
10613 gtk_text_buffer_place_cursor(buffer, &ins);
10617 static void textview_move_backward_word (GtkTextView *text)
10619 GtkTextBuffer *buffer;
10623 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10625 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10626 mark = gtk_text_buffer_get_insert(buffer);
10627 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10628 if (gtk_text_iter_backward_word_starts(&ins, 1))
10629 gtk_text_buffer_place_cursor(buffer, &ins);
10632 static void textview_move_end_of_line (GtkTextView *text)
10634 GtkTextBuffer *buffer;
10638 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10640 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10641 mark = gtk_text_buffer_get_insert(buffer);
10642 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10643 if (gtk_text_iter_forward_to_line_end(&ins))
10644 gtk_text_buffer_place_cursor(buffer, &ins);
10647 static void textview_move_next_line (GtkTextView *text)
10649 GtkTextBuffer *buffer;
10654 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10656 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10657 mark = gtk_text_buffer_get_insert(buffer);
10658 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10659 offset = gtk_text_iter_get_line_offset(&ins);
10660 if (gtk_text_iter_forward_line(&ins)) {
10661 gtk_text_iter_set_line_offset(&ins, offset);
10662 gtk_text_buffer_place_cursor(buffer, &ins);
10666 static void textview_move_previous_line (GtkTextView *text)
10668 GtkTextBuffer *buffer;
10673 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10675 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10676 mark = gtk_text_buffer_get_insert(buffer);
10677 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10678 offset = gtk_text_iter_get_line_offset(&ins);
10679 if (gtk_text_iter_backward_line(&ins)) {
10680 gtk_text_iter_set_line_offset(&ins, offset);
10681 gtk_text_buffer_place_cursor(buffer, &ins);
10685 static void textview_delete_forward_character (GtkTextView *text)
10687 GtkTextBuffer *buffer;
10689 GtkTextIter ins, end_iter;
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);
10697 if (gtk_text_iter_forward_char(&end_iter)) {
10698 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10702 static void textview_delete_backward_character (GtkTextView *text)
10704 GtkTextBuffer *buffer;
10706 GtkTextIter ins, end_iter;
10708 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10710 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10711 mark = gtk_text_buffer_get_insert(buffer);
10712 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10714 if (gtk_text_iter_backward_char(&end_iter)) {
10715 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10719 static void textview_delete_forward_word (GtkTextView *text)
10721 GtkTextBuffer *buffer;
10723 GtkTextIter ins, end_iter;
10725 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10727 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10728 mark = gtk_text_buffer_get_insert(buffer);
10729 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10731 if (gtk_text_iter_forward_word_end(&end_iter)) {
10732 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10736 static void textview_delete_backward_word (GtkTextView *text)
10738 GtkTextBuffer *buffer;
10740 GtkTextIter ins, end_iter;
10742 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10744 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10745 mark = gtk_text_buffer_get_insert(buffer);
10746 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10748 if (gtk_text_iter_backward_word_start(&end_iter)) {
10749 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10753 static void textview_delete_line (GtkTextView *text)
10755 GtkTextBuffer *buffer;
10757 GtkTextIter ins, start_iter, end_iter;
10759 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10761 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10762 mark = gtk_text_buffer_get_insert(buffer);
10763 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10766 gtk_text_iter_set_line_offset(&start_iter, 0);
10769 if (gtk_text_iter_ends_line(&end_iter)){
10770 if (!gtk_text_iter_forward_char(&end_iter))
10771 gtk_text_iter_backward_char(&start_iter);
10774 gtk_text_iter_forward_to_line_end(&end_iter);
10775 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10778 static void textview_delete_to_line_end (GtkTextView *text)
10780 GtkTextBuffer *buffer;
10782 GtkTextIter ins, end_iter;
10784 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10786 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10787 mark = gtk_text_buffer_get_insert(buffer);
10788 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10790 if (gtk_text_iter_ends_line(&end_iter))
10791 gtk_text_iter_forward_char(&end_iter);
10793 gtk_text_iter_forward_to_line_end(&end_iter);
10794 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10797 #define DO_ACTION(name, act) { \
10798 if(!strcmp(name, a_name)) { \
10802 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10804 const gchar *a_name = gtk_action_get_name(action);
10805 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10806 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10807 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10808 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10809 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10810 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10811 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10812 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10813 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10814 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10815 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10816 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10817 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10818 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10822 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10824 Compose *compose = (Compose *)data;
10825 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10826 ComposeCallAdvancedAction action = -1;
10828 action = compose_call_advanced_action_from_path(gaction);
10831 void (*do_action) (GtkTextView *text);
10832 } action_table[] = {
10833 {textview_move_beginning_of_line},
10834 {textview_move_forward_character},
10835 {textview_move_backward_character},
10836 {textview_move_forward_word},
10837 {textview_move_backward_word},
10838 {textview_move_end_of_line},
10839 {textview_move_next_line},
10840 {textview_move_previous_line},
10841 {textview_delete_forward_character},
10842 {textview_delete_backward_character},
10843 {textview_delete_forward_word},
10844 {textview_delete_backward_word},
10845 {textview_delete_line},
10846 {textview_delete_to_line_end}
10849 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10851 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10852 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10853 if (action_table[action].do_action)
10854 action_table[action].do_action(text);
10856 g_warning("Not implemented yet.");
10860 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10862 GtkAllocation allocation;
10866 if (GTK_IS_EDITABLE(widget)) {
10867 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10868 gtk_editable_set_position(GTK_EDITABLE(widget),
10871 if ((parent = gtk_widget_get_parent(widget))
10872 && (parent = gtk_widget_get_parent(parent))
10873 && (parent = gtk_widget_get_parent(parent))) {
10874 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10875 gtk_widget_get_allocation(widget, &allocation);
10876 gint y = allocation.y;
10877 gint height = allocation.height;
10878 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10879 (GTK_SCROLLED_WINDOW(parent));
10881 gfloat value = gtk_adjustment_get_value(shown);
10882 gfloat upper = gtk_adjustment_get_upper(shown);
10883 gfloat page_size = gtk_adjustment_get_page_size(shown);
10884 if (y < (int)value) {
10885 gtk_adjustment_set_value(shown, y - 1);
10887 if ((y + height) > ((int)value + (int)page_size)) {
10888 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10889 gtk_adjustment_set_value(shown,
10890 y + height - (int)page_size - 1);
10892 gtk_adjustment_set_value(shown,
10893 (int)upper - (int)page_size - 1);
10900 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10901 compose->focused_editable = widget;
10903 #ifdef GENERIC_UMPC
10904 if (GTK_IS_TEXT_VIEW(widget)
10905 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10906 g_object_ref(compose->notebook);
10907 g_object_ref(compose->edit_vbox);
10908 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10909 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10910 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10911 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10912 g_object_unref(compose->notebook);
10913 g_object_unref(compose->edit_vbox);
10914 g_signal_handlers_block_by_func(G_OBJECT(widget),
10915 G_CALLBACK(compose_grab_focus_cb),
10917 gtk_widget_grab_focus(widget);
10918 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10919 G_CALLBACK(compose_grab_focus_cb),
10921 } else if (!GTK_IS_TEXT_VIEW(widget)
10922 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10923 g_object_ref(compose->notebook);
10924 g_object_ref(compose->edit_vbox);
10925 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10926 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10927 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10928 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10929 g_object_unref(compose->notebook);
10930 g_object_unref(compose->edit_vbox);
10931 g_signal_handlers_block_by_func(G_OBJECT(widget),
10932 G_CALLBACK(compose_grab_focus_cb),
10934 gtk_widget_grab_focus(widget);
10935 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10936 G_CALLBACK(compose_grab_focus_cb),
10942 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10944 compose->modified = TRUE;
10945 // compose_beautify_paragraph(compose, NULL, TRUE);
10946 #ifndef GENERIC_UMPC
10947 compose_set_title(compose);
10951 static void compose_wrap_cb(GtkAction *action, gpointer data)
10953 Compose *compose = (Compose *)data;
10954 compose_beautify_paragraph(compose, NULL, TRUE);
10957 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10959 Compose *compose = (Compose *)data;
10960 compose_wrap_all_full(compose, TRUE);
10963 static void compose_find_cb(GtkAction *action, gpointer data)
10965 Compose *compose = (Compose *)data;
10967 message_search_compose(compose);
10970 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10973 Compose *compose = (Compose *)data;
10974 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10975 if (compose->autowrap)
10976 compose_wrap_all_full(compose, TRUE);
10977 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10980 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10983 Compose *compose = (Compose *)data;
10984 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10987 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10989 Compose *compose = (Compose *)data;
10991 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10994 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10996 Compose *compose = (Compose *)data;
10998 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11001 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11003 g_free(compose->privacy_system);
11005 compose->privacy_system = g_strdup(account->default_privacy_system);
11006 compose_update_privacy_system_menu_item(compose, warn);
11009 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11011 Compose *compose = (Compose *)data;
11013 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11014 gtk_widget_show(compose->ruler_hbox);
11015 prefs_common.show_ruler = TRUE;
11017 gtk_widget_hide(compose->ruler_hbox);
11018 gtk_widget_queue_resize(compose->edit_vbox);
11019 prefs_common.show_ruler = FALSE;
11023 static void compose_attach_drag_received_cb (GtkWidget *widget,
11024 GdkDragContext *context,
11027 GtkSelectionData *data,
11030 gpointer user_data)
11032 Compose *compose = (Compose *)user_data;
11036 type = gtk_selection_data_get_data_type(data);
11037 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11039 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11041 ) && gtk_drag_get_source_widget(context) !=
11042 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11043 list = uri_list_extract_filenames(
11044 (const gchar *)gtk_selection_data_get_data(data));
11045 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11046 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11047 compose_attach_append
11048 (compose, (const gchar *)tmp->data,
11049 utf8_filename, NULL, NULL);
11050 g_free(utf8_filename);
11052 if (list) compose_changed_cb(NULL, compose);
11053 list_free_strings(list);
11055 } else if (gtk_drag_get_source_widget(context)
11056 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11057 /* comes from our summaryview */
11058 SummaryView * summaryview = NULL;
11059 GSList * list = NULL, *cur = NULL;
11061 if (mainwindow_get_mainwindow())
11062 summaryview = mainwindow_get_mainwindow()->summaryview;
11065 list = summary_get_selected_msg_list(summaryview);
11067 for (cur = list; cur; cur = cur->next) {
11068 MsgInfo *msginfo = (MsgInfo *)cur->data;
11069 gchar *file = NULL;
11071 file = procmsg_get_message_file_full(msginfo,
11074 compose_attach_append(compose, (const gchar *)file,
11075 (const gchar *)file, "message/rfc822", NULL);
11079 g_slist_free(list);
11083 static gboolean compose_drag_drop(GtkWidget *widget,
11084 GdkDragContext *drag_context,
11086 guint time, gpointer user_data)
11088 /* not handling this signal makes compose_insert_drag_received_cb
11093 static gboolean completion_set_focus_to_subject
11094 (GtkWidget *widget,
11095 GdkEventKey *event,
11098 cm_return_val_if_fail(compose != NULL, FALSE);
11100 /* make backtab move to subject field */
11101 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11102 gtk_widget_grab_focus(compose->subject_entry);
11108 static void compose_insert_drag_received_cb (GtkWidget *widget,
11109 GdkDragContext *drag_context,
11112 GtkSelectionData *data,
11115 gpointer user_data)
11117 Compose *compose = (Compose *)user_data;
11121 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11123 type = gtk_selection_data_get_data_type(data);
11125 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11127 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11129 AlertValue val = G_ALERTDEFAULT;
11130 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11132 list = uri_list_extract_filenames(ddata);
11133 if (list == NULL && strstr(ddata, "://")) {
11134 /* Assume a list of no files, and data has ://, is a remote link */
11135 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11136 gchar *tmpfile = get_tmp_file();
11137 str_write_to_file(tmpdata, tmpfile);
11139 compose_insert_file(compose, tmpfile);
11140 claws_unlink(tmpfile);
11142 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11143 compose_beautify_paragraph(compose, NULL, TRUE);
11146 switch (prefs_common.compose_dnd_mode) {
11147 case COMPOSE_DND_ASK:
11148 val = alertpanel_full(_("Insert or attach?"),
11149 _("Do you want to insert the contents of the file(s) "
11150 "into the message body, or attach it to the email?"),
11151 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11152 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11154 case COMPOSE_DND_INSERT:
11155 val = G_ALERTALTERNATE;
11157 case COMPOSE_DND_ATTACH:
11158 val = G_ALERTOTHER;
11161 /* unexpected case */
11162 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11165 if (val & G_ALERTDISABLE) {
11166 val &= ~G_ALERTDISABLE;
11167 /* remember what action to perform by default, only if we don't click Cancel */
11168 if (val == G_ALERTALTERNATE)
11169 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11170 else if (val == G_ALERTOTHER)
11171 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11174 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11175 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11176 list_free_strings(list);
11179 } else if (val == G_ALERTOTHER) {
11180 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11181 list_free_strings(list);
11186 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11187 compose_insert_file(compose, (const gchar *)tmp->data);
11189 list_free_strings(list);
11191 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11196 static void compose_header_drag_received_cb (GtkWidget *widget,
11197 GdkDragContext *drag_context,
11200 GtkSelectionData *data,
11203 gpointer user_data)
11205 GtkEditable *entry = (GtkEditable *)user_data;
11206 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11208 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11211 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11212 gchar *decoded=g_new(gchar, strlen(email));
11215 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11216 gtk_editable_delete_text(entry, 0, -1);
11217 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11218 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11222 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11225 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11227 Compose *compose = (Compose *)data;
11229 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11230 compose->return_receipt = TRUE;
11232 compose->return_receipt = FALSE;
11235 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11237 Compose *compose = (Compose *)data;
11239 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11240 compose->remove_references = TRUE;
11242 compose->remove_references = FALSE;
11245 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11246 ComposeHeaderEntry *headerentry)
11248 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11252 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11253 GdkEventKey *event,
11254 ComposeHeaderEntry *headerentry)
11256 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11257 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11258 !(event->state & GDK_MODIFIER_MASK) &&
11259 (event->keyval == GDK_KEY_BackSpace) &&
11260 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11261 gtk_container_remove
11262 (GTK_CONTAINER(headerentry->compose->header_table),
11263 headerentry->combo);
11264 gtk_container_remove
11265 (GTK_CONTAINER(headerentry->compose->header_table),
11266 headerentry->entry);
11267 headerentry->compose->header_list =
11268 g_slist_remove(headerentry->compose->header_list,
11270 g_free(headerentry);
11271 } else if (event->keyval == GDK_KEY_Tab) {
11272 if (headerentry->compose->header_last == headerentry) {
11273 /* Override default next focus, and give it to subject_entry
11274 * instead of notebook tabs
11276 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11277 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11284 static gboolean scroll_postpone(gpointer data)
11286 Compose *compose = (Compose *)data;
11288 if (compose->batch)
11291 GTK_EVENTS_FLUSH();
11292 compose_show_first_last_header(compose, FALSE);
11296 static void compose_headerentry_changed_cb(GtkWidget *entry,
11297 ComposeHeaderEntry *headerentry)
11299 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11300 compose_create_header_entry(headerentry->compose);
11301 g_signal_handlers_disconnect_matched
11302 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11303 0, 0, NULL, NULL, headerentry);
11305 if (!headerentry->compose->batch)
11306 g_timeout_add(0, scroll_postpone, headerentry->compose);
11310 static gboolean compose_defer_auto_save_draft(Compose *compose)
11312 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11313 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11317 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11319 GtkAdjustment *vadj;
11321 cm_return_if_fail(compose);
11326 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11327 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11328 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11329 gtk_widget_get_parent(compose->header_table)));
11330 gtk_adjustment_set_value(vadj, (show_first ?
11331 gtk_adjustment_get_lower(vadj) :
11332 (gtk_adjustment_get_upper(vadj) -
11333 gtk_adjustment_get_page_size(vadj))));
11334 gtk_adjustment_changed(vadj);
11337 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11338 const gchar *text, gint len, Compose *compose)
11340 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11341 (G_OBJECT(compose->text), "paste_as_quotation"));
11344 cm_return_if_fail(text != NULL);
11346 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11347 G_CALLBACK(text_inserted),
11349 if (paste_as_quotation) {
11351 const gchar *qmark;
11353 GtkTextIter start_iter;
11356 len = strlen(text);
11358 new_text = g_strndup(text, len);
11360 qmark = compose_quote_char_from_context(compose);
11362 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11363 gtk_text_buffer_place_cursor(buffer, iter);
11365 pos = gtk_text_iter_get_offset(iter);
11367 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11368 _("Quote format error at line %d."));
11369 quote_fmt_reset_vartable();
11371 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11372 GINT_TO_POINTER(paste_as_quotation - 1));
11374 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11375 gtk_text_buffer_place_cursor(buffer, iter);
11376 gtk_text_buffer_delete_mark(buffer, mark);
11378 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11379 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11380 compose_beautify_paragraph(compose, &start_iter, FALSE);
11381 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11382 gtk_text_buffer_delete_mark(buffer, mark);
11384 if (strcmp(text, "\n") || compose->automatic_break
11385 || gtk_text_iter_starts_line(iter)) {
11386 GtkTextIter before_ins;
11387 gtk_text_buffer_insert(buffer, iter, text, len);
11388 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11389 before_ins = *iter;
11390 gtk_text_iter_backward_chars(&before_ins, len);
11391 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11394 /* check if the preceding is just whitespace or quote */
11395 GtkTextIter start_line;
11396 gchar *tmp = NULL, *quote = NULL;
11397 gint quote_len = 0, is_normal = 0;
11398 start_line = *iter;
11399 gtk_text_iter_set_line_offset(&start_line, 0);
11400 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11403 if (*tmp == '\0') {
11406 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11414 gtk_text_buffer_insert(buffer, iter, text, len);
11416 gtk_text_buffer_insert_with_tags_by_name(buffer,
11417 iter, text, len, "no_join", NULL);
11422 if (!paste_as_quotation) {
11423 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11424 compose_beautify_paragraph(compose, iter, FALSE);
11425 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11426 gtk_text_buffer_delete_mark(buffer, mark);
11429 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11430 G_CALLBACK(text_inserted),
11432 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11434 if (compose_can_autosave(compose) &&
11435 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11436 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11437 compose->draft_timeout_tag = g_timeout_add
11438 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11442 static void compose_check_all(GtkAction *action, gpointer data)
11444 Compose *compose = (Compose *)data;
11445 if (!compose->gtkaspell)
11448 if (gtk_widget_has_focus(compose->subject_entry))
11449 claws_spell_entry_check_all(
11450 CLAWS_SPELL_ENTRY(compose->subject_entry));
11452 gtkaspell_check_all(compose->gtkaspell);
11455 static void compose_highlight_all(GtkAction *action, gpointer data)
11457 Compose *compose = (Compose *)data;
11458 if (compose->gtkaspell) {
11459 claws_spell_entry_recheck_all(
11460 CLAWS_SPELL_ENTRY(compose->subject_entry));
11461 gtkaspell_highlight_all(compose->gtkaspell);
11465 static void compose_check_backwards(GtkAction *action, gpointer data)
11467 Compose *compose = (Compose *)data;
11468 if (!compose->gtkaspell) {
11469 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11473 if (gtk_widget_has_focus(compose->subject_entry))
11474 claws_spell_entry_check_backwards(
11475 CLAWS_SPELL_ENTRY(compose->subject_entry));
11477 gtkaspell_check_backwards(compose->gtkaspell);
11480 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11482 Compose *compose = (Compose *)data;
11483 if (!compose->gtkaspell) {
11484 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11488 if (gtk_widget_has_focus(compose->subject_entry))
11489 claws_spell_entry_check_forwards_go(
11490 CLAWS_SPELL_ENTRY(compose->subject_entry));
11492 gtkaspell_check_forwards_go(compose->gtkaspell);
11497 *\brief Guess originating forward account from MsgInfo and several
11498 * "common preference" settings. Return NULL if no guess.
11500 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11502 PrefsAccount *account = NULL;
11504 cm_return_val_if_fail(msginfo, NULL);
11505 cm_return_val_if_fail(msginfo->folder, NULL);
11506 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11508 if (msginfo->folder->prefs->enable_default_account)
11509 account = account_find_from_id(msginfo->folder->prefs->default_account);
11512 account = msginfo->folder->folder->account;
11514 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11516 Xstrdup_a(to, msginfo->to, return NULL);
11517 extract_address(to);
11518 account = account_find_from_address(to, FALSE);
11521 if (!account && prefs_common.forward_account_autosel) {
11522 gchar cc[BUFFSIZE];
11523 if (!procheader_get_header_from_msginfo
11524 (msginfo, cc,sizeof cc , "Cc:")) {
11525 gchar *buf = cc + strlen("Cc:");
11526 extract_address(buf);
11527 account = account_find_from_address(buf, FALSE);
11531 if (!account && prefs_common.forward_account_autosel) {
11532 gchar deliveredto[BUFFSIZE];
11533 if (!procheader_get_header_from_msginfo
11534 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11535 gchar *buf = deliveredto + strlen("Delivered-To:");
11536 extract_address(buf);
11537 account = account_find_from_address(buf, FALSE);
11544 gboolean compose_close(Compose *compose)
11548 cm_return_val_if_fail(compose, FALSE);
11550 if (!g_mutex_trylock(compose->mutex)) {
11551 /* we have to wait for the (possibly deferred by auto-save)
11552 * drafting to be done, before destroying the compose under
11554 debug_print("waiting for drafting to finish...\n");
11555 compose_allow_user_actions(compose, FALSE);
11556 if (compose->close_timeout_tag == 0) {
11557 compose->close_timeout_tag =
11558 g_timeout_add (500, (GSourceFunc) compose_close,
11564 if (compose->draft_timeout_tag >= 0) {
11565 g_source_remove(compose->draft_timeout_tag);
11566 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11569 gtkut_widget_get_uposition(compose->window, &x, &y);
11570 if (!compose->batch) {
11571 prefs_common.compose_x = x;
11572 prefs_common.compose_y = y;
11574 g_mutex_unlock(compose->mutex);
11575 compose_destroy(compose);
11580 * Add entry field for each address in list.
11581 * \param compose E-Mail composition object.
11582 * \param listAddress List of (formatted) E-Mail addresses.
11584 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11587 node = listAddress;
11589 addr = ( gchar * ) node->data;
11590 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11591 node = g_list_next( node );
11595 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11596 guint action, gboolean opening_multiple)
11598 gchar *body = NULL;
11599 GSList *new_msglist = NULL;
11600 MsgInfo *tmp_msginfo = NULL;
11601 gboolean originally_enc = FALSE;
11602 gboolean originally_sig = FALSE;
11603 Compose *compose = NULL;
11604 gchar *s_system = NULL;
11606 cm_return_if_fail(msgview != NULL);
11608 cm_return_if_fail(msginfo_list != NULL);
11610 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11611 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11612 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11614 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11615 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11616 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11617 orig_msginfo, mimeinfo);
11618 if (tmp_msginfo != NULL) {
11619 new_msglist = g_slist_append(NULL, tmp_msginfo);
11621 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11622 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11623 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11625 tmp_msginfo->folder = orig_msginfo->folder;
11626 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11627 if (orig_msginfo->tags) {
11628 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11629 tmp_msginfo->folder->tags_dirty = TRUE;
11635 if (!opening_multiple)
11636 body = messageview_get_selection(msgview);
11639 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11640 procmsg_msginfo_free(tmp_msginfo);
11641 g_slist_free(new_msglist);
11643 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11645 if (compose && originally_enc) {
11646 compose_force_encryption(compose, compose->account, FALSE, s_system);
11649 if (compose && originally_sig && compose->account->default_sign_reply) {
11650 compose_force_signing(compose, compose->account, s_system);
11654 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11657 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11660 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11661 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11662 GSList *cur = msginfo_list;
11663 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11664 "messages. Opening the windows "
11665 "could take some time. Do you "
11666 "want to continue?"),
11667 g_slist_length(msginfo_list));
11668 if (g_slist_length(msginfo_list) > 9
11669 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11670 != G_ALERTALTERNATE) {
11675 /* We'll open multiple compose windows */
11676 /* let the WM place the next windows */
11677 compose_force_window_origin = FALSE;
11678 for (; cur; cur = cur->next) {
11680 tmplist.data = cur->data;
11681 tmplist.next = NULL;
11682 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11684 compose_force_window_origin = TRUE;
11686 /* forwarding multiple mails as attachments is done via a
11687 * single compose window */
11688 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11692 void compose_check_for_email_account(Compose *compose)
11694 PrefsAccount *ac = NULL, *curr = NULL;
11700 if (compose->account && compose->account->protocol == A_NNTP) {
11701 ac = account_get_cur_account();
11702 if (ac->protocol == A_NNTP) {
11703 list = account_get_list();
11705 for( ; list != NULL ; list = g_list_next(list)) {
11706 curr = (PrefsAccount *) list->data;
11707 if (curr->protocol != A_NNTP) {
11713 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11718 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11719 const gchar *address)
11721 GSList *msginfo_list = NULL;
11722 gchar *body = messageview_get_selection(msgview);
11725 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11727 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11728 compose_check_for_email_account(compose);
11729 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11730 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11731 compose_reply_set_subject(compose, msginfo);
11734 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11737 void compose_set_position(Compose *compose, gint pos)
11739 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11741 gtkut_text_view_set_position(text, pos);
11744 gboolean compose_search_string(Compose *compose,
11745 const gchar *str, gboolean case_sens)
11747 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11749 return gtkut_text_view_search_string(text, str, case_sens);
11752 gboolean compose_search_string_backward(Compose *compose,
11753 const gchar *str, gboolean case_sens)
11755 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11757 return gtkut_text_view_search_string_backward(text, str, case_sens);
11760 /* allocate a msginfo structure and populate its data from a compose data structure */
11761 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11763 MsgInfo *newmsginfo;
11765 gchar buf[BUFFSIZE];
11767 cm_return_val_if_fail( compose != NULL, NULL );
11769 newmsginfo = procmsg_msginfo_new();
11772 get_rfc822_date(buf, sizeof(buf));
11773 newmsginfo->date = g_strdup(buf);
11776 if (compose->from_name) {
11777 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11778 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11782 if (compose->subject_entry)
11783 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11785 /* to, cc, reply-to, newsgroups */
11786 for (list = compose->header_list; list; list = list->next) {
11787 gchar *header = gtk_editable_get_chars(
11789 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11790 gchar *entry = gtk_editable_get_chars(
11791 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11793 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11794 if ( newmsginfo->to == NULL ) {
11795 newmsginfo->to = g_strdup(entry);
11796 } else if (entry && *entry) {
11797 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11798 g_free(newmsginfo->to);
11799 newmsginfo->to = tmp;
11802 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11803 if ( newmsginfo->cc == NULL ) {
11804 newmsginfo->cc = g_strdup(entry);
11805 } else if (entry && *entry) {
11806 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11807 g_free(newmsginfo->cc);
11808 newmsginfo->cc = tmp;
11811 if ( strcasecmp(header,
11812 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11813 if ( newmsginfo->newsgroups == NULL ) {
11814 newmsginfo->newsgroups = g_strdup(entry);
11815 } else if (entry && *entry) {
11816 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11817 g_free(newmsginfo->newsgroups);
11818 newmsginfo->newsgroups = tmp;
11826 /* other data is unset */
11832 /* update compose's dictionaries from folder dict settings */
11833 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11834 FolderItem *folder_item)
11836 cm_return_if_fail(compose != NULL);
11838 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11839 FolderItemPrefs *prefs = folder_item->prefs;
11841 if (prefs->enable_default_dictionary)
11842 gtkaspell_change_dict(compose->gtkaspell,
11843 prefs->default_dictionary, FALSE);
11844 if (folder_item->prefs->enable_default_alt_dictionary)
11845 gtkaspell_change_alt_dict(compose->gtkaspell,
11846 prefs->default_alt_dictionary);
11847 if (prefs->enable_default_dictionary
11848 || prefs->enable_default_alt_dictionary)
11849 compose_spell_menu_changed(compose);
11854 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11856 Compose *compose = (Compose *)data;
11858 cm_return_if_fail(compose != NULL);
11860 gtk_widget_grab_focus(compose->text);