2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 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/>.
21 #include "claws-features.h"
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
121 #define N_ATTACH_COLS (N_COL_COLUMNS)
125 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
139 } ComposeCallAdvancedAction;
143 PRIORITY_HIGHEST = 1,
152 COMPOSE_INSERT_SUCCESS,
153 COMPOSE_INSERT_READ_ERROR,
154 COMPOSE_INSERT_INVALID_CHARACTER,
155 COMPOSE_INSERT_NO_FILE
156 } ComposeInsertResult;
160 COMPOSE_WRITE_FOR_SEND,
161 COMPOSE_WRITE_FOR_STORE
166 COMPOSE_QUOTE_FORCED,
173 SUBJECT_FIELD_PRESENT,
178 #define B64_LINE_SIZE 57
179 #define B64_BUFFSIZE 77
181 #define MAX_REFERENCES_LEN 999
183 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
184 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
186 static GdkColor default_to_bgcolor = {
193 static GdkColor default_to_color = {
200 static GList *compose_list = NULL;
201 static GSList *extra_headers = NULL;
203 static Compose *compose_generic_new (PrefsAccount *account,
207 GList *listAddress );
209 static Compose *compose_create (PrefsAccount *account,
214 static void compose_entry_mark_default_to (Compose *compose,
215 const gchar *address);
216 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
221 static Compose *compose_forward_multiple (PrefsAccount *account,
222 GSList *msginfo_list);
223 static Compose *compose_reply (MsgInfo *msginfo,
224 ComposeQuoteMode quote_mode,
229 static Compose *compose_reply_mode (ComposeMode mode,
230 GSList *msginfo_list,
232 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
233 static void compose_update_privacy_systems_menu(Compose *compose);
235 static GtkWidget *compose_account_option_menu_create
237 static void compose_set_out_encoding (Compose *compose);
238 static void compose_set_template_menu (Compose *compose);
239 static void compose_destroy (Compose *compose);
241 static MailField compose_entries_set (Compose *compose,
243 ComposeEntryType to_type);
244 static gint compose_parse_header (Compose *compose,
246 static gint compose_parse_manual_headers (Compose *compose,
248 HeaderEntry *entries);
249 static gchar *compose_parse_references (const gchar *ref,
252 static gchar *compose_quote_fmt (Compose *compose,
258 gboolean need_unescape,
259 const gchar *err_msg);
261 static void compose_reply_set_entry (Compose *compose,
267 followup_and_reply_to);
268 static void compose_reedit_set_entry (Compose *compose,
271 static void compose_insert_sig (Compose *compose,
273 static ComposeInsertResult compose_insert_file (Compose *compose,
276 static gboolean compose_attach_append (Compose *compose,
279 const gchar *content_type,
280 const gchar *charset);
281 static void compose_attach_parts (Compose *compose,
284 static gboolean compose_beautify_paragraph (Compose *compose,
285 GtkTextIter *par_iter,
287 static void compose_wrap_all (Compose *compose);
288 static void compose_wrap_all_full (Compose *compose,
291 static void compose_set_title (Compose *compose);
292 static void compose_select_account (Compose *compose,
293 PrefsAccount *account,
296 static PrefsAccount *compose_current_mail_account(void);
297 /* static gint compose_send (Compose *compose); */
298 static gboolean compose_check_for_valid_recipient
300 static gboolean compose_check_entries (Compose *compose,
301 gboolean check_everything);
302 static gint compose_write_to_file (Compose *compose,
305 gboolean attach_parts);
306 static gint compose_write_body_to_file (Compose *compose,
308 static gint compose_remove_reedit_target (Compose *compose,
310 static void compose_remove_draft (Compose *compose);
311 static gint compose_queue_sub (Compose *compose,
315 gboolean check_subject,
316 gboolean remove_reedit_target);
317 static int compose_add_attachments (Compose *compose,
319 static gchar *compose_get_header (Compose *compose);
320 static gchar *compose_get_manual_headers_info (Compose *compose);
322 static void compose_convert_header (Compose *compose,
327 gboolean addr_field);
329 static void compose_attach_info_free (AttachInfo *ainfo);
330 static void compose_attach_remove_selected (GtkAction *action,
333 static void compose_template_apply (Compose *compose,
336 static void compose_attach_property (GtkAction *action,
338 static void compose_attach_property_create (gboolean *cancelled);
339 static void attach_property_ok (GtkWidget *widget,
340 gboolean *cancelled);
341 static void attach_property_cancel (GtkWidget *widget,
342 gboolean *cancelled);
343 static gint attach_property_delete_event (GtkWidget *widget,
345 gboolean *cancelled);
346 static gboolean attach_property_key_pressed (GtkWidget *widget,
348 gboolean *cancelled);
350 static void compose_exec_ext_editor (Compose *compose);
352 static gint compose_exec_ext_editor_real (const gchar *file,
353 GdkNativeWindow socket_wid);
354 static gboolean compose_ext_editor_kill (Compose *compose);
355 static gboolean compose_input_cb (GIOChannel *source,
356 GIOCondition condition,
358 static void compose_set_ext_editor_sensitive (Compose *compose,
360 static gboolean compose_get_ext_editor_cmd_valid();
361 static gboolean compose_get_ext_editor_uses_socket();
362 static gboolean compose_ext_editor_plug_removed_cb
365 #endif /* G_OS_UNIX */
367 static void compose_undo_state_changed (UndoMain *undostruct,
372 static void compose_create_header_entry (Compose *compose);
373 static void compose_add_header_entry (Compose *compose, const gchar *header,
374 gchar *text, ComposePrefType pref_type);
375 static void compose_remove_header_entries(Compose *compose);
377 static void compose_update_priority_menu_item(Compose * compose);
379 static void compose_spell_menu_changed (void *data);
380 static void compose_dict_changed (void *data);
382 static void compose_add_field_list ( Compose *compose,
383 GList *listAddress );
385 /* callback functions */
387 static void compose_notebook_size_alloc (GtkNotebook *notebook,
388 GtkAllocation *allocation,
390 static gboolean compose_edit_size_alloc (GtkEditable *widget,
391 GtkAllocation *allocation,
392 GtkSHRuler *shruler);
393 static void account_activated (GtkComboBox *optmenu,
395 static void attach_selected (GtkTreeView *tree_view,
396 GtkTreePath *tree_path,
397 GtkTreeViewColumn *column,
399 static gboolean attach_button_pressed (GtkWidget *widget,
400 GdkEventButton *event,
402 static gboolean attach_key_pressed (GtkWidget *widget,
405 static void compose_send_cb (GtkAction *action, gpointer data);
406 static void compose_send_later_cb (GtkAction *action, gpointer data);
408 static void compose_save_cb (GtkAction *action,
411 static void compose_attach_cb (GtkAction *action,
413 static void compose_insert_file_cb (GtkAction *action,
415 static void compose_insert_sig_cb (GtkAction *action,
417 static void compose_replace_sig_cb (GtkAction *action,
420 static void compose_close_cb (GtkAction *action,
422 static void compose_print_cb (GtkAction *action,
425 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
427 static void compose_address_cb (GtkAction *action,
429 static void about_show_cb (GtkAction *action,
431 static void compose_template_activate_cb(GtkWidget *widget,
434 static void compose_ext_editor_cb (GtkAction *action,
437 static gint compose_delete_cb (GtkWidget *widget,
441 static void compose_undo_cb (GtkAction *action,
443 static void compose_redo_cb (GtkAction *action,
445 static void compose_cut_cb (GtkAction *action,
447 static void compose_copy_cb (GtkAction *action,
449 static void compose_paste_cb (GtkAction *action,
451 static void compose_paste_as_quote_cb (GtkAction *action,
453 static void compose_paste_no_wrap_cb (GtkAction *action,
455 static void compose_paste_wrap_cb (GtkAction *action,
457 static void compose_allsel_cb (GtkAction *action,
460 static void compose_advanced_action_cb (GtkAction *action,
463 static void compose_grab_focus_cb (GtkWidget *widget,
466 static void compose_changed_cb (GtkTextBuffer *textbuf,
469 static void compose_wrap_cb (GtkAction *action,
471 static void compose_wrap_all_cb (GtkAction *action,
473 static void compose_find_cb (GtkAction *action,
475 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
477 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
480 static void compose_toggle_ruler_cb (GtkToggleAction *action,
482 static void compose_toggle_sign_cb (GtkToggleAction *action,
484 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
486 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
487 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
488 static void activate_privacy_system (Compose *compose,
489 PrefsAccount *account,
491 static void compose_use_signing(Compose *compose, gboolean use_signing);
492 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
493 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
495 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
497 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
498 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
499 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
501 static void compose_attach_drag_received_cb (GtkWidget *widget,
502 GdkDragContext *drag_context,
505 GtkSelectionData *data,
509 static void compose_insert_drag_received_cb (GtkWidget *widget,
510 GdkDragContext *drag_context,
513 GtkSelectionData *data,
517 static void compose_header_drag_received_cb (GtkWidget *widget,
518 GdkDragContext *drag_context,
521 GtkSelectionData *data,
526 static gboolean compose_drag_drop (GtkWidget *widget,
527 GdkDragContext *drag_context,
529 guint time, gpointer user_data);
530 static gboolean completion_set_focus_to_subject
535 static void text_inserted (GtkTextBuffer *buffer,
540 static Compose *compose_generic_reply(MsgInfo *msginfo,
541 ComposeQuoteMode quote_mode,
545 gboolean followup_and_reply_to,
548 static void compose_headerentry_changed_cb (GtkWidget *entry,
549 ComposeHeaderEntry *headerentry);
550 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
552 ComposeHeaderEntry *headerentry);
553 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
554 ComposeHeaderEntry *headerentry);
556 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
558 static void compose_allow_user_actions (Compose *compose, gboolean allow);
560 static void compose_nothing_cb (GtkAction *action, gpointer data)
566 static void compose_check_all (GtkAction *action, gpointer data);
567 static void compose_highlight_all (GtkAction *action, gpointer data);
568 static void compose_check_backwards (GtkAction *action, gpointer data);
569 static void compose_check_forwards_go (GtkAction *action, gpointer data);
572 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
574 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
577 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
578 FolderItem *folder_item);
580 static void compose_attach_update_label(Compose *compose);
581 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
582 gboolean respect_default_to);
583 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
584 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
586 static GtkActionEntry compose_popup_entries[] =
588 {"Compose", NULL, "Compose" },
589 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
590 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
591 {"Compose/---", NULL, "---", NULL, NULL, NULL },
592 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
595 static GtkActionEntry compose_entries[] =
597 {"Menu", NULL, "Menu" },
599 {"Message", NULL, N_("_Message") },
600 {"Edit", NULL, N_("_Edit") },
602 {"Spelling", NULL, N_("_Spelling") },
604 {"Options", NULL, N_("_Options") },
605 {"Tools", NULL, N_("_Tools") },
606 {"Help", NULL, N_("_Help") },
608 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
609 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
610 {"Message/---", NULL, "---" },
612 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
613 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
614 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
615 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
616 /* {"Message/---", NULL, "---" }, */
617 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
618 /* {"Message/---", NULL, "---" }, */
619 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
620 /* {"Message/---", NULL, "---" }, */
621 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
624 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
625 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
626 {"Edit/---", NULL, "---" },
628 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
629 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
630 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
632 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
633 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
634 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
635 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
637 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
639 {"Edit/Advanced", NULL, N_("A_dvanced") },
640 {"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*/
641 {"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*/
642 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
643 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
644 {"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*/
645 {"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*/
646 {"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*/
647 {"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*/
648 {"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*/
649 {"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*/
650 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
651 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
652 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
653 {"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*/
655 /* {"Edit/---", NULL, "---" }, */
656 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
658 /* {"Edit/---", NULL, "---" }, */
659 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
660 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
661 /* {"Edit/---", NULL, "---" }, */
662 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
665 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
666 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
667 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
668 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
670 {"Spelling/---", NULL, "---" },
671 {"Spelling/Options", NULL, N_("_Options") },
676 {"Options/ReplyMode", NULL, N_("Reply _mode") },
677 {"Options/---", NULL, "---" },
678 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
679 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
681 /* {"Options/---", NULL, "---" }, */
683 {"Options/Priority", NULL, N_("_Priority") },
685 {"Options/Encoding", NULL, N_("Character _encoding") },
686 {"Options/Encoding/---", NULL, "---" },
687 #define ENC_ACTION(cs_char,c_char,string) \
688 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
690 {"Options/Encoding/Western", NULL, N_("Western European") },
691 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
692 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
693 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
694 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
695 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
696 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
697 {"Options/Encoding/Korean", NULL, N_("Korean") },
698 {"Options/Encoding/Thai", NULL, N_("Thai") },
701 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
703 {"Tools/Template", NULL, N_("_Template") },
704 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
705 {"Tools/Actions", NULL, N_("Actio_ns") },
706 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
709 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
712 static GtkToggleActionEntry compose_toggle_entries[] =
714 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
715 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
716 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
717 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
718 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
719 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
720 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
723 static GtkRadioActionEntry compose_radio_rm_entries[] =
725 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
726 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
727 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
728 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
731 static GtkRadioActionEntry compose_radio_prio_entries[] =
733 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
734 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
735 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
736 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
737 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
740 static GtkRadioActionEntry compose_radio_enc_entries[] =
742 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
762 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
763 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
764 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
765 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
766 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
767 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
768 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
769 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
770 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
771 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
772 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
773 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
774 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
777 static GtkTargetEntry compose_mime_types[] =
779 {"text/uri-list", 0, 0},
780 {"UTF8_STRING", 0, 0},
784 static gboolean compose_put_existing_to_front(MsgInfo *info)
786 const GList *compose_list = compose_get_compose_list();
787 const GList *elem = NULL;
790 for (elem = compose_list; elem != NULL && elem->data != NULL;
792 Compose *c = (Compose*)elem->data;
794 if (!c->targetinfo || !c->targetinfo->msgid ||
798 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
799 gtkut_window_popup(c->window);
807 static GdkColor quote_color1 =
808 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
809 static GdkColor quote_color2 =
810 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
811 static GdkColor quote_color3 =
812 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
814 static GdkColor quote_bgcolor1 =
815 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 static GdkColor quote_bgcolor2 =
817 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
818 static GdkColor quote_bgcolor3 =
819 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
821 static GdkColor signature_color = {
828 static GdkColor uri_color = {
835 static void compose_create_tags(GtkTextView *text, Compose *compose)
837 GtkTextBuffer *buffer;
838 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
839 #if !GTK_CHECK_VERSION(2, 24, 0)
846 buffer = gtk_text_view_get_buffer(text);
848 if (prefs_common.enable_color) {
849 /* grab the quote colors, converting from an int to a GdkColor */
850 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
852 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
854 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
856 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
858 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
860 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
862 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
864 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
867 signature_color = quote_color1 = quote_color2 = quote_color3 =
868 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
871 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
872 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
873 "foreground-gdk", "e_color1,
874 "paragraph-background-gdk", "e_bgcolor1,
876 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
877 "foreground-gdk", "e_color2,
878 "paragraph-background-gdk", "e_bgcolor2,
880 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
881 "foreground-gdk", "e_color3,
882 "paragraph-background-gdk", "e_bgcolor3,
885 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
886 "foreground-gdk", "e_color1,
888 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
889 "foreground-gdk", "e_color2,
891 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
892 "foreground-gdk", "e_color3,
896 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
897 "foreground-gdk", &signature_color,
900 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
901 "foreground-gdk", &uri_color,
903 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
904 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
906 #if !GTK_CHECK_VERSION(2, 24, 0)
907 color[0] = quote_color1;
908 color[1] = quote_color2;
909 color[2] = quote_color3;
910 color[3] = quote_bgcolor1;
911 color[4] = quote_bgcolor2;
912 color[5] = quote_bgcolor3;
913 color[6] = signature_color;
914 color[7] = uri_color;
916 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
917 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
919 for (i = 0; i < 8; i++) {
920 if (success[i] == FALSE) {
921 g_warning("Compose: color allocation failed.");
922 quote_color1 = quote_color2 = quote_color3 =
923 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
924 signature_color = uri_color = black;
930 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
933 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
936 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
938 return compose_generic_new(account, mailto, item, NULL, NULL);
941 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
943 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
946 #define SCROLL_TO_CURSOR(compose) { \
947 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
948 gtk_text_view_get_buffer( \
949 GTK_TEXT_VIEW(compose->text))); \
950 gtk_text_view_scroll_mark_onscreen( \
951 GTK_TEXT_VIEW(compose->text), \
955 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
958 if (folderidentifier) {
959 #if !GTK_CHECK_VERSION(2, 24, 0)
960 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
962 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
964 prefs_common.compose_save_to_history = add_history(
965 prefs_common.compose_save_to_history, folderidentifier);
966 #if !GTK_CHECK_VERSION(2, 24, 0)
967 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
968 prefs_common.compose_save_to_history);
970 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
971 prefs_common.compose_save_to_history);
975 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
976 if (folderidentifier)
977 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
979 gtk_entry_set_text(GTK_ENTRY(entry), "");
982 static gchar *compose_get_save_to(Compose *compose)
985 gchar *result = NULL;
986 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
987 result = gtk_editable_get_chars(entry, 0, -1);
990 #if !GTK_CHECK_VERSION(2, 24, 0)
991 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
993 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
995 prefs_common.compose_save_to_history = add_history(
996 prefs_common.compose_save_to_history, result);
997 #if !GTK_CHECK_VERSION(2, 24, 0)
998 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
999 prefs_common.compose_save_to_history);
1001 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
1002 prefs_common.compose_save_to_history);
1008 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
1009 GList *attach_files, GList *listAddress )
1012 GtkTextView *textview;
1013 GtkTextBuffer *textbuf;
1015 const gchar *subject_format = NULL;
1016 const gchar *body_format = NULL;
1017 gchar *mailto_from = NULL;
1018 PrefsAccount *mailto_account = NULL;
1019 MsgInfo* dummyinfo = NULL;
1020 gint cursor_pos = -1;
1021 MailField mfield = NO_FIELD_PRESENT;
1025 /* check if mailto defines a from */
1026 if (mailto && *mailto != '\0') {
1027 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1028 /* mailto defines a from, check if we can get account prefs from it,
1029 if not, the account prefs will be guessed using other ways, but we'll keep
1032 mailto_account = account_find_from_address(mailto_from, TRUE);
1033 if (mailto_account == NULL) {
1035 Xstrdup_a(tmp_from, mailto_from, return NULL);
1036 extract_address(tmp_from);
1037 mailto_account = account_find_from_address(tmp_from, TRUE);
1041 account = mailto_account;
1044 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1045 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1046 account = account_find_from_id(item->prefs->default_account);
1048 /* if no account prefs set, fallback to the current one */
1049 if (!account) account = cur_account;
1050 cm_return_val_if_fail(account != NULL, NULL);
1052 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1054 /* override from name if mailto asked for it */
1056 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1057 g_free(mailto_from);
1059 /* override from name according to folder properties */
1060 if (item && item->prefs &&
1061 item->prefs->compose_with_format &&
1062 item->prefs->compose_override_from_format &&
1063 *item->prefs->compose_override_from_format != '\0') {
1068 dummyinfo = compose_msginfo_new_from_compose(compose);
1070 /* decode \-escape sequences in the internal representation of the quote format */
1071 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1072 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1075 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1076 compose->gtkaspell);
1078 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1080 quote_fmt_scan_string(tmp);
1083 buf = quote_fmt_get_buffer();
1085 alertpanel_error(_("New message From format error."));
1087 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1088 quote_fmt_reset_vartable();
1093 compose->replyinfo = NULL;
1094 compose->fwdinfo = NULL;
1096 textview = GTK_TEXT_VIEW(compose->text);
1097 textbuf = gtk_text_view_get_buffer(textview);
1098 compose_create_tags(textview, compose);
1100 undo_block(compose->undostruct);
1102 compose_set_dictionaries_from_folder_prefs(compose, item);
1105 if (account->auto_sig)
1106 compose_insert_sig(compose, FALSE);
1107 gtk_text_buffer_get_start_iter(textbuf, &iter);
1108 gtk_text_buffer_place_cursor(textbuf, &iter);
1110 if (account->protocol != A_NNTP) {
1111 if (mailto && *mailto != '\0') {
1112 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1115 compose_set_folder_prefs(compose, item, TRUE);
1117 if (item && item->ret_rcpt) {
1118 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1121 if (mailto && *mailto != '\0') {
1122 if (!strchr(mailto, '@'))
1123 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1125 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1126 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1127 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1128 mfield = TO_FIELD_PRESENT;
1131 * CLAWS: just don't allow return receipt request, even if the user
1132 * may want to send an email. simple but foolproof.
1134 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1136 compose_add_field_list( compose, listAddress );
1138 if (item && item->prefs && item->prefs->compose_with_format) {
1139 subject_format = item->prefs->compose_subject_format;
1140 body_format = item->prefs->compose_body_format;
1141 } else if (account->compose_with_format) {
1142 subject_format = account->compose_subject_format;
1143 body_format = account->compose_body_format;
1144 } else if (prefs_common.compose_with_format) {
1145 subject_format = prefs_common.compose_subject_format;
1146 body_format = prefs_common.compose_body_format;
1149 if (subject_format || body_format) {
1152 && *subject_format != '\0' )
1154 gchar *subject = NULL;
1159 dummyinfo = compose_msginfo_new_from_compose(compose);
1161 /* decode \-escape sequences in the internal representation of the quote format */
1162 tmp = g_malloc(strlen(subject_format)+1);
1163 pref_get_unescaped_pref(tmp, subject_format);
1165 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1167 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1168 compose->gtkaspell);
1170 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1172 quote_fmt_scan_string(tmp);
1175 buf = quote_fmt_get_buffer();
1177 alertpanel_error(_("New message subject format error."));
1179 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1180 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1181 quote_fmt_reset_vartable();
1185 mfield = SUBJECT_FIELD_PRESENT;
1189 && *body_format != '\0' )
1192 GtkTextBuffer *buffer;
1193 GtkTextIter start, end;
1197 dummyinfo = compose_msginfo_new_from_compose(compose);
1199 text = GTK_TEXT_VIEW(compose->text);
1200 buffer = gtk_text_view_get_buffer(text);
1201 gtk_text_buffer_get_start_iter(buffer, &start);
1202 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1203 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1205 compose_quote_fmt(compose, dummyinfo,
1207 NULL, tmp, FALSE, TRUE,
1208 _("The body of the \"New message\" template has an error at line %d."));
1209 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1210 quote_fmt_reset_vartable();
1214 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1215 gtkaspell_highlight_all(compose->gtkaspell);
1217 mfield = BODY_FIELD_PRESENT;
1221 procmsg_msginfo_free( &dummyinfo );
1227 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1228 ainfo = (AttachInfo *) curr->data;
1229 compose_attach_append(compose, ainfo->file, ainfo->file,
1230 ainfo->content_type, ainfo->charset);
1234 compose_show_first_last_header(compose, TRUE);
1236 /* Set save folder */
1237 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1238 gchar *folderidentifier;
1240 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1241 folderidentifier = folder_item_get_identifier(item);
1242 compose_set_save_to(compose, folderidentifier);
1243 g_free(folderidentifier);
1246 /* Place cursor according to provided input (mfield) */
1248 case NO_FIELD_PRESENT:
1249 if (compose->header_last)
1250 gtk_widget_grab_focus(compose->header_last->entry);
1252 case TO_FIELD_PRESENT:
1253 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1255 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1258 gtk_widget_grab_focus(compose->subject_entry);
1260 case SUBJECT_FIELD_PRESENT:
1261 textview = GTK_TEXT_VIEW(compose->text);
1264 textbuf = gtk_text_view_get_buffer(textview);
1267 mark = gtk_text_buffer_get_insert(textbuf);
1268 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1269 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1271 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1272 * only defers where it comes to the variable body
1273 * is not null. If no body is present compose->text
1274 * will be null in which case you cannot place the
1275 * cursor inside the component so. An empty component
1276 * is therefore created before placing the cursor
1278 case BODY_FIELD_PRESENT:
1279 cursor_pos = quote_fmt_get_cursor_pos();
1280 if (cursor_pos == -1)
1281 gtk_widget_grab_focus(compose->header_last->entry);
1283 gtk_widget_grab_focus(compose->text);
1287 undo_unblock(compose->undostruct);
1289 if (prefs_common.auto_exteditor)
1290 compose_exec_ext_editor(compose);
1292 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1294 SCROLL_TO_CURSOR(compose);
1296 compose->modified = FALSE;
1297 compose_set_title(compose);
1299 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1304 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1305 gboolean override_pref, const gchar *system)
1307 const gchar *privacy = NULL;
1309 cm_return_if_fail(compose != NULL);
1310 cm_return_if_fail(account != NULL);
1312 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1315 if (account->default_privacy_system && strlen(account->default_privacy_system))
1316 privacy = account->default_privacy_system;
1320 GSList *privacy_avail = privacy_get_system_ids();
1321 if (privacy_avail && g_slist_length(privacy_avail)) {
1322 privacy = (gchar *)(privacy_avail->data);
1325 if (privacy != NULL) {
1327 g_free(compose->privacy_system);
1328 compose->privacy_system = NULL;
1329 g_free(compose->encdata);
1330 compose->encdata = NULL;
1332 if (compose->privacy_system == NULL)
1333 compose->privacy_system = g_strdup(privacy);
1334 else if (*(compose->privacy_system) == '\0') {
1335 g_free(compose->privacy_system);
1336 g_free(compose->encdata);
1337 compose->encdata = NULL;
1338 compose->privacy_system = g_strdup(privacy);
1340 compose_update_privacy_system_menu_item(compose, FALSE);
1341 compose_use_encryption(compose, TRUE);
1345 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1347 const gchar *privacy = NULL;
1349 if (account->default_privacy_system && strlen(account->default_privacy_system))
1350 privacy = account->default_privacy_system;
1354 GSList *privacy_avail = privacy_get_system_ids();
1355 if (privacy_avail && g_slist_length(privacy_avail)) {
1356 privacy = (gchar *)(privacy_avail->data);
1360 if (privacy != NULL) {
1362 g_free(compose->privacy_system);
1363 compose->privacy_system = NULL;
1364 g_free(compose->encdata);
1365 compose->encdata = NULL;
1367 if (compose->privacy_system == NULL)
1368 compose->privacy_system = g_strdup(privacy);
1369 compose_update_privacy_system_menu_item(compose, FALSE);
1370 compose_use_signing(compose, TRUE);
1374 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1378 Compose *compose = NULL;
1380 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1382 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1383 cm_return_val_if_fail(msginfo != NULL, NULL);
1385 list_len = g_slist_length(msginfo_list);
1389 case COMPOSE_REPLY_TO_ADDRESS:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1391 FALSE, prefs_common.default_reply_list, FALSE, body);
1393 case COMPOSE_REPLY_WITH_QUOTE:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1395 FALSE, prefs_common.default_reply_list, FALSE, body);
1397 case COMPOSE_REPLY_WITHOUT_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1399 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1401 case COMPOSE_REPLY_TO_SENDER:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1403 FALSE, FALSE, TRUE, body);
1405 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1406 compose = compose_followup_and_reply_to(msginfo,
1407 COMPOSE_QUOTE_CHECK,
1408 FALSE, FALSE, body);
1410 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1411 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1412 FALSE, FALSE, TRUE, body);
1414 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1415 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1416 FALSE, FALSE, TRUE, NULL);
1418 case COMPOSE_REPLY_TO_ALL:
1419 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1420 TRUE, FALSE, FALSE, body);
1422 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1423 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1424 TRUE, FALSE, FALSE, body);
1426 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1427 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1428 TRUE, FALSE, FALSE, NULL);
1430 case COMPOSE_REPLY_TO_LIST:
1431 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1432 FALSE, TRUE, FALSE, body);
1434 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1435 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1436 FALSE, TRUE, FALSE, body);
1438 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1439 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1440 FALSE, TRUE, FALSE, NULL);
1442 case COMPOSE_FORWARD:
1443 if (prefs_common.forward_as_attachment) {
1444 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1447 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1451 case COMPOSE_FORWARD_INLINE:
1452 /* check if we reply to more than one Message */
1453 if (list_len == 1) {
1454 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1457 /* more messages FALL THROUGH */
1458 case COMPOSE_FORWARD_AS_ATTACH:
1459 compose = compose_forward_multiple(NULL, msginfo_list);
1461 case COMPOSE_REDIRECT:
1462 compose = compose_redirect(NULL, msginfo, FALSE);
1465 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1468 if (compose == NULL) {
1469 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1473 compose->rmode = mode;
1474 switch (compose->rmode) {
1476 case COMPOSE_REPLY_WITH_QUOTE:
1477 case COMPOSE_REPLY_WITHOUT_QUOTE:
1478 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1479 debug_print("reply mode Normal\n");
1480 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1481 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1483 case COMPOSE_REPLY_TO_SENDER:
1484 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1485 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1486 debug_print("reply mode Sender\n");
1487 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1489 case COMPOSE_REPLY_TO_ALL:
1490 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1491 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1492 debug_print("reply mode All\n");
1493 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1495 case COMPOSE_REPLY_TO_LIST:
1496 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1497 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1498 debug_print("reply mode List\n");
1499 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1501 case COMPOSE_REPLY_TO_ADDRESS:
1502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1510 static Compose *compose_reply(MsgInfo *msginfo,
1511 ComposeQuoteMode quote_mode,
1517 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1518 to_sender, FALSE, body);
1521 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1522 ComposeQuoteMode quote_mode,
1527 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1528 to_sender, TRUE, body);
1531 static void compose_extract_original_charset(Compose *compose)
1533 MsgInfo *info = NULL;
1534 if (compose->replyinfo) {
1535 info = compose->replyinfo;
1536 } else if (compose->fwdinfo) {
1537 info = compose->fwdinfo;
1538 } else if (compose->targetinfo) {
1539 info = compose->targetinfo;
1542 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1543 MimeInfo *partinfo = mimeinfo;
1544 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1545 partinfo = procmime_mimeinfo_next(partinfo);
1547 compose->orig_charset =
1548 g_strdup(procmime_mimeinfo_get_parameter(
1549 partinfo, "charset"));
1551 procmime_mimeinfo_free_all(&mimeinfo);
1555 #define SIGNAL_BLOCK(buffer) { \
1556 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1557 G_CALLBACK(compose_changed_cb), \
1559 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1560 G_CALLBACK(text_inserted), \
1564 #define SIGNAL_UNBLOCK(buffer) { \
1565 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1566 G_CALLBACK(compose_changed_cb), \
1568 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1569 G_CALLBACK(text_inserted), \
1573 static Compose *compose_generic_reply(MsgInfo *msginfo,
1574 ComposeQuoteMode quote_mode,
1575 gboolean to_all, gboolean to_ml,
1577 gboolean followup_and_reply_to,
1581 PrefsAccount *account = NULL;
1582 GtkTextView *textview;
1583 GtkTextBuffer *textbuf;
1584 gboolean quote = FALSE;
1585 const gchar *qmark = NULL;
1586 const gchar *body_fmt = NULL;
1587 gchar *s_system = NULL;
1589 cm_return_val_if_fail(msginfo != NULL, NULL);
1590 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1592 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1594 cm_return_val_if_fail(account != NULL, NULL);
1596 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1598 compose->updating = TRUE;
1600 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1601 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1603 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1604 if (!compose->replyinfo)
1605 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1607 compose_extract_original_charset(compose);
1609 if (msginfo->folder && msginfo->folder->ret_rcpt)
1610 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1612 /* Set save folder */
1613 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1614 gchar *folderidentifier;
1616 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1617 folderidentifier = folder_item_get_identifier(msginfo->folder);
1618 compose_set_save_to(compose, folderidentifier);
1619 g_free(folderidentifier);
1622 if (compose_parse_header(compose, msginfo) < 0) {
1623 compose->updating = FALSE;
1624 compose_destroy(compose);
1628 /* override from name according to folder properties */
1629 if (msginfo->folder && msginfo->folder->prefs &&
1630 msginfo->folder->prefs->reply_with_format &&
1631 msginfo->folder->prefs->reply_override_from_format &&
1632 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1637 /* decode \-escape sequences in the internal representation of the quote format */
1638 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1639 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1642 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1643 compose->gtkaspell);
1645 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1647 quote_fmt_scan_string(tmp);
1650 buf = quote_fmt_get_buffer();
1652 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1654 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1655 quote_fmt_reset_vartable();
1660 textview = (GTK_TEXT_VIEW(compose->text));
1661 textbuf = gtk_text_view_get_buffer(textview);
1662 compose_create_tags(textview, compose);
1664 undo_block(compose->undostruct);
1666 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1667 gtkaspell_block_check(compose->gtkaspell);
1670 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1671 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1672 /* use the reply format of folder (if enabled), or the account's one
1673 (if enabled) or fallback to the global reply format, which is always
1674 enabled (even if empty), and use the relevant quotemark */
1676 if (msginfo->folder && msginfo->folder->prefs &&
1677 msginfo->folder->prefs->reply_with_format) {
1678 qmark = msginfo->folder->prefs->reply_quotemark;
1679 body_fmt = msginfo->folder->prefs->reply_body_format;
1681 } else if (account->reply_with_format) {
1682 qmark = account->reply_quotemark;
1683 body_fmt = account->reply_body_format;
1686 qmark = prefs_common.quotemark;
1687 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1688 body_fmt = gettext(prefs_common.quotefmt);
1695 /* empty quotemark is not allowed */
1696 if (qmark == NULL || *qmark == '\0')
1698 compose_quote_fmt(compose, compose->replyinfo,
1699 body_fmt, qmark, body, FALSE, TRUE,
1700 _("The body of the \"Reply\" template has an error at line %d."));
1701 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1702 quote_fmt_reset_vartable();
1705 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1706 compose_force_encryption(compose, account, FALSE, s_system);
1709 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1710 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1711 compose_force_signing(compose, account, s_system);
1715 SIGNAL_BLOCK(textbuf);
1717 if (account->auto_sig)
1718 compose_insert_sig(compose, FALSE);
1720 compose_wrap_all(compose);
1723 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1724 gtkaspell_highlight_all(compose->gtkaspell);
1725 gtkaspell_unblock_check(compose->gtkaspell);
1727 SIGNAL_UNBLOCK(textbuf);
1729 gtk_widget_grab_focus(compose->text);
1731 undo_unblock(compose->undostruct);
1733 if (prefs_common.auto_exteditor)
1734 compose_exec_ext_editor(compose);
1736 compose->modified = FALSE;
1737 compose_set_title(compose);
1739 compose->updating = FALSE;
1740 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1741 SCROLL_TO_CURSOR(compose);
1743 if (compose->deferred_destroy) {
1744 compose_destroy(compose);
1752 #define INSERT_FW_HEADER(var, hdr) \
1753 if (msginfo->var && *msginfo->var) { \
1754 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1755 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1756 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1759 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1760 gboolean as_attach, const gchar *body,
1761 gboolean no_extedit,
1765 GtkTextView *textview;
1766 GtkTextBuffer *textbuf;
1767 gint cursor_pos = -1;
1770 cm_return_val_if_fail(msginfo != NULL, NULL);
1771 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1774 !(account = compose_guess_forward_account_from_msginfo
1776 account = cur_account;
1778 if (!prefs_common.forward_as_attachment)
1779 mode = COMPOSE_FORWARD_INLINE;
1781 mode = COMPOSE_FORWARD;
1782 compose = compose_create(account, msginfo->folder, mode, batch);
1784 compose->updating = TRUE;
1785 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1786 if (!compose->fwdinfo)
1787 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1789 compose_extract_original_charset(compose);
1791 if (msginfo->subject && *msginfo->subject) {
1792 gchar *buf, *buf2, *p;
1794 buf = p = g_strdup(msginfo->subject);
1795 p += subject_get_prefix_length(p);
1796 memmove(buf, p, strlen(p) + 1);
1798 buf2 = g_strdup_printf("Fw: %s", buf);
1799 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1805 /* override from name according to folder properties */
1806 if (msginfo->folder && msginfo->folder->prefs &&
1807 msginfo->folder->prefs->forward_with_format &&
1808 msginfo->folder->prefs->forward_override_from_format &&
1809 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1813 MsgInfo *full_msginfo = NULL;
1816 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1818 full_msginfo = procmsg_msginfo_copy(msginfo);
1820 /* decode \-escape sequences in the internal representation of the quote format */
1821 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1822 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1825 gtkaspell_block_check(compose->gtkaspell);
1826 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1827 compose->gtkaspell);
1829 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1831 quote_fmt_scan_string(tmp);
1834 buf = quote_fmt_get_buffer();
1836 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1838 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1839 quote_fmt_reset_vartable();
1842 procmsg_msginfo_free(&full_msginfo);
1845 textview = GTK_TEXT_VIEW(compose->text);
1846 textbuf = gtk_text_view_get_buffer(textview);
1847 compose_create_tags(textview, compose);
1849 undo_block(compose->undostruct);
1853 msgfile = procmsg_get_message_file(msginfo);
1854 if (!is_file_exist(msgfile))
1855 g_warning("%s: file does not exist", msgfile);
1857 compose_attach_append(compose, msgfile, msgfile,
1858 "message/rfc822", NULL);
1862 const gchar *qmark = NULL;
1863 const gchar *body_fmt = NULL;
1864 MsgInfo *full_msginfo;
1866 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1868 full_msginfo = procmsg_msginfo_copy(msginfo);
1870 /* use the forward format of folder (if enabled), or the account's one
1871 (if enabled) or fallback to the global forward format, which is always
1872 enabled (even if empty), and use the relevant quotemark */
1873 if (msginfo->folder && msginfo->folder->prefs &&
1874 msginfo->folder->prefs->forward_with_format) {
1875 qmark = msginfo->folder->prefs->forward_quotemark;
1876 body_fmt = msginfo->folder->prefs->forward_body_format;
1878 } else if (account->forward_with_format) {
1879 qmark = account->forward_quotemark;
1880 body_fmt = account->forward_body_format;
1883 qmark = prefs_common.fw_quotemark;
1884 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1885 body_fmt = gettext(prefs_common.fw_quotefmt);
1890 /* empty quotemark is not allowed */
1891 if (qmark == NULL || *qmark == '\0')
1894 compose_quote_fmt(compose, full_msginfo,
1895 body_fmt, qmark, body, FALSE, TRUE,
1896 _("The body of the \"Forward\" template has an error at line %d."));
1897 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1898 quote_fmt_reset_vartable();
1899 compose_attach_parts(compose, msginfo);
1901 procmsg_msginfo_free(&full_msginfo);
1904 SIGNAL_BLOCK(textbuf);
1906 if (account->auto_sig)
1907 compose_insert_sig(compose, FALSE);
1909 compose_wrap_all(compose);
1912 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1913 gtkaspell_highlight_all(compose->gtkaspell);
1914 gtkaspell_unblock_check(compose->gtkaspell);
1916 SIGNAL_UNBLOCK(textbuf);
1918 cursor_pos = quote_fmt_get_cursor_pos();
1919 if (cursor_pos == -1)
1920 gtk_widget_grab_focus(compose->header_last->entry);
1922 gtk_widget_grab_focus(compose->text);
1924 if (!no_extedit && prefs_common.auto_exteditor)
1925 compose_exec_ext_editor(compose);
1928 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1929 gchar *folderidentifier;
1931 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1932 folderidentifier = folder_item_get_identifier(msginfo->folder);
1933 compose_set_save_to(compose, folderidentifier);
1934 g_free(folderidentifier);
1937 undo_unblock(compose->undostruct);
1939 compose->modified = FALSE;
1940 compose_set_title(compose);
1942 compose->updating = FALSE;
1943 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1944 SCROLL_TO_CURSOR(compose);
1946 if (compose->deferred_destroy) {
1947 compose_destroy(compose);
1951 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1956 #undef INSERT_FW_HEADER
1958 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1961 GtkTextView *textview;
1962 GtkTextBuffer *textbuf;
1966 gboolean single_mail = TRUE;
1968 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1970 if (g_slist_length(msginfo_list) > 1)
1971 single_mail = FALSE;
1973 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1974 if (((MsgInfo *)msginfo->data)->folder == NULL)
1977 /* guess account from first selected message */
1979 !(account = compose_guess_forward_account_from_msginfo
1980 (msginfo_list->data)))
1981 account = cur_account;
1983 cm_return_val_if_fail(account != NULL, NULL);
1985 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1986 if (msginfo->data) {
1987 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1988 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1992 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1993 g_warning("no msginfo_list");
1997 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1999 compose->updating = TRUE;
2001 /* override from name according to folder properties */
2002 if (msginfo_list->data) {
2003 MsgInfo *msginfo = msginfo_list->data;
2005 if (msginfo->folder && msginfo->folder->prefs &&
2006 msginfo->folder->prefs->forward_with_format &&
2007 msginfo->folder->prefs->forward_override_from_format &&
2008 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2013 /* decode \-escape sequences in the internal representation of the quote format */
2014 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2015 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2019 compose->gtkaspell);
2021 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2023 quote_fmt_scan_string(tmp);
2026 buf = quote_fmt_get_buffer();
2028 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2031 quote_fmt_reset_vartable();
2037 textview = GTK_TEXT_VIEW(compose->text);
2038 textbuf = gtk_text_view_get_buffer(textview);
2039 compose_create_tags(textview, compose);
2041 undo_block(compose->undostruct);
2042 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2043 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2045 if (!is_file_exist(msgfile))
2046 g_warning("%s: file does not exist", msgfile);
2048 compose_attach_append(compose, msgfile, msgfile,
2049 "message/rfc822", NULL);
2054 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2055 if (info->subject && *info->subject) {
2056 gchar *buf, *buf2, *p;
2058 buf = p = g_strdup(info->subject);
2059 p += subject_get_prefix_length(p);
2060 memmove(buf, p, strlen(p) + 1);
2062 buf2 = g_strdup_printf("Fw: %s", buf);
2063 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2069 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2070 _("Fw: multiple emails"));
2073 SIGNAL_BLOCK(textbuf);
2075 if (account->auto_sig)
2076 compose_insert_sig(compose, FALSE);
2078 compose_wrap_all(compose);
2080 SIGNAL_UNBLOCK(textbuf);
2082 gtk_text_buffer_get_start_iter(textbuf, &iter);
2083 gtk_text_buffer_place_cursor(textbuf, &iter);
2085 if (prefs_common.auto_exteditor)
2086 compose_exec_ext_editor(compose);
2088 gtk_widget_grab_focus(compose->header_last->entry);
2089 undo_unblock(compose->undostruct);
2090 compose->modified = FALSE;
2091 compose_set_title(compose);
2093 compose->updating = FALSE;
2094 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2095 SCROLL_TO_CURSOR(compose);
2097 if (compose->deferred_destroy) {
2098 compose_destroy(compose);
2102 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2107 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2109 GtkTextIter start = *iter;
2110 GtkTextIter end_iter;
2111 int start_pos = gtk_text_iter_get_offset(&start);
2113 if (!compose->account->sig_sep)
2116 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2117 start_pos+strlen(compose->account->sig_sep));
2119 /* check sig separator */
2120 str = gtk_text_iter_get_text(&start, &end_iter);
2121 if (!strcmp(str, compose->account->sig_sep)) {
2123 /* check end of line (\n) */
2124 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2125 start_pos+strlen(compose->account->sig_sep));
2126 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2127 start_pos+strlen(compose->account->sig_sep)+1);
2128 tmp = gtk_text_iter_get_text(&start, &end_iter);
2129 if (!strcmp(tmp,"\n")) {
2141 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2143 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2144 Compose *compose = (Compose *)data;
2145 FolderItem *old_item = NULL;
2146 FolderItem *new_item = NULL;
2147 gchar *old_id, *new_id;
2149 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2150 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2153 old_item = hookdata->item;
2154 new_item = hookdata->item2;
2156 old_id = folder_item_get_identifier(old_item);
2157 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2159 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2160 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2161 compose->targetinfo->folder = new_item;
2164 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2165 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2166 compose->replyinfo->folder = new_item;
2169 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2170 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2171 compose->fwdinfo->folder = new_item;
2179 static void compose_colorize_signature(Compose *compose)
2181 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2183 GtkTextIter end_iter;
2184 gtk_text_buffer_get_start_iter(buffer, &iter);
2185 while (gtk_text_iter_forward_line(&iter))
2186 if (compose_is_sig_separator(compose, buffer, &iter)) {
2187 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2188 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2192 #define BLOCK_WRAP() { \
2193 prev_autowrap = compose->autowrap; \
2194 buffer = gtk_text_view_get_buffer( \
2195 GTK_TEXT_VIEW(compose->text)); \
2196 compose->autowrap = FALSE; \
2198 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2199 G_CALLBACK(compose_changed_cb), \
2201 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2202 G_CALLBACK(text_inserted), \
2205 #define UNBLOCK_WRAP() { \
2206 compose->autowrap = prev_autowrap; \
2207 if (compose->autowrap) { \
2208 gint old = compose->draft_timeout_tag; \
2209 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2210 compose_wrap_all(compose); \
2211 compose->draft_timeout_tag = old; \
2214 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2215 G_CALLBACK(compose_changed_cb), \
2217 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2218 G_CALLBACK(text_inserted), \
2222 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2224 Compose *compose = NULL;
2225 PrefsAccount *account = NULL;
2226 GtkTextView *textview;
2227 GtkTextBuffer *textbuf;
2231 gchar buf[BUFFSIZE];
2232 gboolean use_signing = FALSE;
2233 gboolean use_encryption = FALSE;
2234 gchar *privacy_system = NULL;
2235 int priority = PRIORITY_NORMAL;
2236 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2237 gboolean autowrap = prefs_common.autowrap;
2238 gboolean autoindent = prefs_common.auto_indent;
2239 HeaderEntry *manual_headers = NULL;
2241 cm_return_val_if_fail(msginfo != NULL, NULL);
2242 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2244 if (compose_put_existing_to_front(msginfo)) {
2248 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2249 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2250 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2251 gchar queueheader_buf[BUFFSIZE];
2254 /* Select Account from queue headers */
2255 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2256 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2257 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2258 account = account_find_from_id(id);
2260 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2261 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2262 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2263 account = account_find_from_id(id);
2265 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2266 sizeof(queueheader_buf), "NAID:")) {
2267 id = atoi(&queueheader_buf[strlen("NAID:")]);
2268 account = account_find_from_id(id);
2270 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2271 sizeof(queueheader_buf), "MAID:")) {
2272 id = atoi(&queueheader_buf[strlen("MAID:")]);
2273 account = account_find_from_id(id);
2275 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2276 sizeof(queueheader_buf), "S:")) {
2277 account = account_find_from_address(queueheader_buf, FALSE);
2279 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2280 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2281 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2282 use_signing = param;
2285 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2286 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2287 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2288 use_signing = param;
2291 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2292 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2293 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2294 use_encryption = param;
2296 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2297 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2298 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2299 use_encryption = param;
2301 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2302 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2303 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2308 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2311 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2312 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2313 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2315 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2316 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2317 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2319 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2320 sizeof(queueheader_buf), "X-Priority: ")) {
2321 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2324 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2325 sizeof(queueheader_buf), "RMID:")) {
2326 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2327 if (tokens[0] && tokens[1] && tokens[2]) {
2328 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2329 if (orig_item != NULL) {
2330 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2335 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2336 sizeof(queueheader_buf), "FMID:")) {
2337 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2338 if (tokens[0] && tokens[1] && tokens[2]) {
2339 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2340 if (orig_item != NULL) {
2341 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2346 /* Get manual headers */
2347 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2348 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2349 if (*listmh != '\0') {
2350 debug_print("Got manual headers: %s\n", listmh);
2351 manual_headers = procheader_entries_from_str(listmh);
2356 account = msginfo->folder->folder->account;
2359 if (!account && prefs_common.reedit_account_autosel) {
2360 gchar from[BUFFSIZE];
2361 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2362 extract_address(from);
2363 account = account_find_from_address(from, FALSE);
2367 account = cur_account;
2369 cm_return_val_if_fail(account != NULL, NULL);
2371 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2373 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2374 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2375 compose->autowrap = autowrap;
2376 compose->replyinfo = replyinfo;
2377 compose->fwdinfo = fwdinfo;
2379 compose->updating = TRUE;
2380 compose->priority = priority;
2382 if (privacy_system != NULL) {
2383 compose->privacy_system = privacy_system;
2384 compose_use_signing(compose, use_signing);
2385 compose_use_encryption(compose, use_encryption);
2386 compose_update_privacy_system_menu_item(compose, FALSE);
2388 activate_privacy_system(compose, account, FALSE);
2391 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2393 compose_extract_original_charset(compose);
2395 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2396 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2397 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2398 gchar queueheader_buf[BUFFSIZE];
2400 /* Set message save folder */
2401 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2402 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2403 compose_set_save_to(compose, &queueheader_buf[4]);
2405 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2406 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2408 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2413 if (compose_parse_header(compose, msginfo) < 0) {
2414 compose->updating = FALSE;
2415 compose_destroy(compose);
2418 compose_reedit_set_entry(compose, msginfo);
2420 textview = GTK_TEXT_VIEW(compose->text);
2421 textbuf = gtk_text_view_get_buffer(textview);
2422 compose_create_tags(textview, compose);
2424 mark = gtk_text_buffer_get_insert(textbuf);
2425 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2427 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2428 G_CALLBACK(compose_changed_cb),
2431 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2432 fp = procmime_get_first_encrypted_text_content(msginfo);
2434 compose_force_encryption(compose, account, TRUE, NULL);
2437 fp = procmime_get_first_text_content(msginfo);
2440 g_warning("Can't get text part");
2444 gboolean prev_autowrap;
2445 GtkTextBuffer *buffer;
2447 while (fgets(buf, sizeof(buf), fp) != NULL) {
2449 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2455 compose_attach_parts(compose, msginfo);
2457 compose_colorize_signature(compose);
2459 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2460 G_CALLBACK(compose_changed_cb),
2463 if (manual_headers != NULL) {
2464 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2465 procheader_entries_free(manual_headers);
2466 compose->updating = FALSE;
2467 compose_destroy(compose);
2470 procheader_entries_free(manual_headers);
2473 gtk_widget_grab_focus(compose->text);
2475 if (prefs_common.auto_exteditor) {
2476 compose_exec_ext_editor(compose);
2478 compose->modified = FALSE;
2479 compose_set_title(compose);
2481 compose->updating = FALSE;
2482 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2483 SCROLL_TO_CURSOR(compose);
2485 if (compose->deferred_destroy) {
2486 compose_destroy(compose);
2490 compose->sig_str = account_get_signature_str(compose->account);
2492 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2497 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2504 cm_return_val_if_fail(msginfo != NULL, NULL);
2507 account = account_get_reply_account(msginfo,
2508 prefs_common.reply_account_autosel);
2509 cm_return_val_if_fail(account != NULL, NULL);
2511 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2513 compose->updating = TRUE;
2515 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2516 compose->replyinfo = NULL;
2517 compose->fwdinfo = NULL;
2519 compose_show_first_last_header(compose, TRUE);
2521 gtk_widget_grab_focus(compose->header_last->entry);
2523 filename = procmsg_get_message_file(msginfo);
2525 if (filename == NULL) {
2526 compose->updating = FALSE;
2527 compose_destroy(compose);
2532 compose->redirect_filename = filename;
2534 /* Set save folder */
2535 item = msginfo->folder;
2536 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2537 gchar *folderidentifier;
2539 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2540 folderidentifier = folder_item_get_identifier(item);
2541 compose_set_save_to(compose, folderidentifier);
2542 g_free(folderidentifier);
2545 compose_attach_parts(compose, msginfo);
2547 if (msginfo->subject)
2548 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2550 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2552 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2553 _("The body of the \"Redirect\" template has an error at line %d."));
2554 quote_fmt_reset_vartable();
2555 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2557 compose_colorize_signature(compose);
2560 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2561 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2562 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2564 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2566 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2569 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2570 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2571 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2572 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2574 if (compose->toolbar->draft_btn)
2575 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2576 if (compose->toolbar->insert_btn)
2577 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2578 if (compose->toolbar->attach_btn)
2579 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2580 if (compose->toolbar->sig_btn)
2581 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2582 if (compose->toolbar->exteditor_btn)
2583 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2584 if (compose->toolbar->linewrap_current_btn)
2585 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2586 if (compose->toolbar->linewrap_all_btn)
2587 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2589 compose->modified = FALSE;
2590 compose_set_title(compose);
2591 compose->updating = FALSE;
2592 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2593 SCROLL_TO_CURSOR(compose);
2595 if (compose->deferred_destroy) {
2596 compose_destroy(compose);
2600 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2605 const GList *compose_get_compose_list(void)
2607 return compose_list;
2610 void compose_entry_append(Compose *compose, const gchar *address,
2611 ComposeEntryType type, ComposePrefType pref_type)
2613 const gchar *header;
2615 gboolean in_quote = FALSE;
2616 if (!address || *address == '\0') return;
2623 header = N_("Bcc:");
2625 case COMPOSE_REPLYTO:
2626 header = N_("Reply-To:");
2628 case COMPOSE_NEWSGROUPS:
2629 header = N_("Newsgroups:");
2631 case COMPOSE_FOLLOWUPTO:
2632 header = N_( "Followup-To:");
2634 case COMPOSE_INREPLYTO:
2635 header = N_( "In-Reply-To:");
2642 header = prefs_common_translated_header_name(header);
2644 cur = begin = (gchar *)address;
2646 /* we separate the line by commas, but not if we're inside a quoted
2648 while (*cur != '\0') {
2650 in_quote = !in_quote;
2651 if (*cur == ',' && !in_quote) {
2652 gchar *tmp = g_strdup(begin);
2654 tmp[cur-begin]='\0';
2657 while (*tmp == ' ' || *tmp == '\t')
2659 compose_add_header_entry(compose, header, tmp, pref_type);
2666 gchar *tmp = g_strdup(begin);
2668 tmp[cur-begin]='\0';
2669 while (*tmp == ' ' || *tmp == '\t')
2671 compose_add_header_entry(compose, header, tmp, pref_type);
2676 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2681 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2682 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2683 if (gtk_entry_get_text(entry) &&
2684 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2685 gtk_widget_modify_base(
2686 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2687 GTK_STATE_NORMAL, &default_to_bgcolor);
2688 gtk_widget_modify_text(
2689 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2690 GTK_STATE_NORMAL, &default_to_color);
2695 void compose_toolbar_cb(gint action, gpointer data)
2697 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2698 Compose *compose = (Compose*)toolbar_item->parent;
2700 cm_return_if_fail(compose != NULL);
2704 compose_send_cb(NULL, compose);
2707 compose_send_later_cb(NULL, compose);
2710 compose_draft(compose, COMPOSE_QUIT_EDITING);
2713 compose_insert_file_cb(NULL, compose);
2716 compose_attach_cb(NULL, compose);
2719 compose_insert_sig(compose, FALSE);
2722 compose_insert_sig(compose, TRUE);
2725 compose_ext_editor_cb(NULL, compose);
2727 case A_LINEWRAP_CURRENT:
2728 compose_beautify_paragraph(compose, NULL, TRUE);
2730 case A_LINEWRAP_ALL:
2731 compose_wrap_all_full(compose, TRUE);
2734 compose_address_cb(NULL, compose);
2737 case A_CHECK_SPELLING:
2738 compose_check_all(NULL, compose);
2746 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2751 gchar *subject = NULL;
2755 gchar **attach = NULL;
2756 gchar *inreplyto = NULL;
2757 MailField mfield = NO_FIELD_PRESENT;
2759 /* get mailto parts but skip from */
2760 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2763 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2764 mfield = TO_FIELD_PRESENT;
2767 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2769 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2771 if (!g_utf8_validate (subject, -1, NULL)) {
2772 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2776 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2778 mfield = SUBJECT_FIELD_PRESENT;
2781 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2782 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2785 gboolean prev_autowrap = compose->autowrap;
2787 compose->autowrap = FALSE;
2789 mark = gtk_text_buffer_get_insert(buffer);
2790 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2792 if (!g_utf8_validate (body, -1, NULL)) {
2793 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2794 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2797 gtk_text_buffer_insert(buffer, &iter, body, -1);
2799 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2801 compose->autowrap = prev_autowrap;
2802 if (compose->autowrap)
2803 compose_wrap_all(compose);
2804 mfield = BODY_FIELD_PRESENT;
2808 gint i = 0, att = 0;
2809 gchar *warn_files = NULL;
2810 while (attach[i] != NULL) {
2811 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2812 if (utf8_filename) {
2813 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2814 gchar *tmp = g_strdup_printf("%s%s\n",
2815 warn_files?warn_files:"",
2821 g_free(utf8_filename);
2823 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2828 alertpanel_notice(ngettext(
2829 "The following file has been attached: \n%s",
2830 "The following files have been attached: \n%s", att), warn_files);
2835 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2848 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2850 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2851 {"Cc:", NULL, TRUE},
2852 {"References:", NULL, FALSE},
2853 {"Bcc:", NULL, TRUE},
2854 {"Newsgroups:", NULL, TRUE},
2855 {"Followup-To:", NULL, TRUE},
2856 {"List-Post:", NULL, FALSE},
2857 {"X-Priority:", NULL, FALSE},
2858 {NULL, NULL, FALSE}};
2874 cm_return_val_if_fail(msginfo != NULL, -1);
2876 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2877 procheader_get_header_fields(fp, hentry);
2880 if (hentry[H_REPLY_TO].body != NULL) {
2881 if (hentry[H_REPLY_TO].body[0] != '\0') {
2883 conv_unmime_header(hentry[H_REPLY_TO].body,
2886 g_free(hentry[H_REPLY_TO].body);
2887 hentry[H_REPLY_TO].body = NULL;
2889 if (hentry[H_CC].body != NULL) {
2890 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2891 g_free(hentry[H_CC].body);
2892 hentry[H_CC].body = NULL;
2894 if (hentry[H_REFERENCES].body != NULL) {
2895 if (compose->mode == COMPOSE_REEDIT)
2896 compose->references = hentry[H_REFERENCES].body;
2898 compose->references = compose_parse_references
2899 (hentry[H_REFERENCES].body, msginfo->msgid);
2900 g_free(hentry[H_REFERENCES].body);
2902 hentry[H_REFERENCES].body = NULL;
2904 if (hentry[H_BCC].body != NULL) {
2905 if (compose->mode == COMPOSE_REEDIT)
2907 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2908 g_free(hentry[H_BCC].body);
2909 hentry[H_BCC].body = NULL;
2911 if (hentry[H_NEWSGROUPS].body != NULL) {
2912 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2913 hentry[H_NEWSGROUPS].body = NULL;
2915 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2916 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2917 compose->followup_to =
2918 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2921 g_free(hentry[H_FOLLOWUP_TO].body);
2922 hentry[H_FOLLOWUP_TO].body = NULL;
2924 if (hentry[H_LIST_POST].body != NULL) {
2925 gchar *to = NULL, *start = NULL;
2927 extract_address(hentry[H_LIST_POST].body);
2928 if (hentry[H_LIST_POST].body[0] != '\0') {
2929 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2931 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2932 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2935 g_free(compose->ml_post);
2936 compose->ml_post = to;
2939 g_free(hentry[H_LIST_POST].body);
2940 hentry[H_LIST_POST].body = NULL;
2943 /* CLAWS - X-Priority */
2944 if (compose->mode == COMPOSE_REEDIT)
2945 if (hentry[H_X_PRIORITY].body != NULL) {
2948 priority = atoi(hentry[H_X_PRIORITY].body);
2949 g_free(hentry[H_X_PRIORITY].body);
2951 hentry[H_X_PRIORITY].body = NULL;
2953 if (priority < PRIORITY_HIGHEST ||
2954 priority > PRIORITY_LOWEST)
2955 priority = PRIORITY_NORMAL;
2957 compose->priority = priority;
2960 if (compose->mode == COMPOSE_REEDIT) {
2961 if (msginfo->inreplyto && *msginfo->inreplyto)
2962 compose->inreplyto = g_strdup(msginfo->inreplyto);
2964 if (msginfo->msgid && *msginfo->msgid)
2965 compose->msgid = g_strdup(msginfo->msgid);
2967 if (msginfo->msgid && *msginfo->msgid)
2968 compose->inreplyto = g_strdup(msginfo->msgid);
2970 if (!compose->references) {
2971 if (msginfo->msgid && *msginfo->msgid) {
2972 if (msginfo->inreplyto && *msginfo->inreplyto)
2973 compose->references =
2974 g_strdup_printf("<%s>\n\t<%s>",
2978 compose->references =
2979 g_strconcat("<", msginfo->msgid, ">",
2981 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2982 compose->references =
2983 g_strconcat("<", msginfo->inreplyto, ">",
2992 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2997 cm_return_val_if_fail(msginfo != NULL, -1);
2999 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3000 procheader_get_header_fields(fp, entries);
3004 while (he != NULL && he->name != NULL) {
3006 GtkListStore *model = NULL;
3008 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3009 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3010 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3011 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3012 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3019 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3021 GSList *ref_id_list, *cur;
3025 ref_id_list = references_list_append(NULL, ref);
3026 if (!ref_id_list) return NULL;
3027 if (msgid && *msgid)
3028 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3033 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3034 /* "<" + Message-ID + ">" + CR+LF+TAB */
3035 len += strlen((gchar *)cur->data) + 5;
3037 if (len > MAX_REFERENCES_LEN) {
3038 /* remove second message-ID */
3039 if (ref_id_list && ref_id_list->next &&
3040 ref_id_list->next->next) {
3041 g_free(ref_id_list->next->data);
3042 ref_id_list = g_slist_remove
3043 (ref_id_list, ref_id_list->next->data);
3045 slist_free_strings_full(ref_id_list);
3052 new_ref = g_string_new("");
3053 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3054 if (new_ref->len > 0)
3055 g_string_append(new_ref, "\n\t");
3056 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3059 slist_free_strings_full(ref_id_list);
3061 new_ref_str = new_ref->str;
3062 g_string_free(new_ref, FALSE);
3067 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3068 const gchar *fmt, const gchar *qmark,
3069 const gchar *body, gboolean rewrap,
3070 gboolean need_unescape,
3071 const gchar *err_msg)
3073 MsgInfo* dummyinfo = NULL;
3074 gchar *quote_str = NULL;
3076 gboolean prev_autowrap;
3077 const gchar *trimmed_body = body;
3078 gint cursor_pos = -1;
3079 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3080 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3085 SIGNAL_BLOCK(buffer);
3088 dummyinfo = compose_msginfo_new_from_compose(compose);
3089 msginfo = dummyinfo;
3092 if (qmark != NULL) {
3094 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3095 compose->gtkaspell);
3097 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3099 quote_fmt_scan_string(qmark);
3102 buf = quote_fmt_get_buffer();
3104 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3106 Xstrdup_a(quote_str, buf, goto error)
3109 if (fmt && *fmt != '\0') {
3112 while (*trimmed_body == '\n')
3116 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3117 compose->gtkaspell);
3119 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3121 if (need_unescape) {
3124 /* decode \-escape sequences in the internal representation of the quote format */
3125 tmp = g_malloc(strlen(fmt)+1);
3126 pref_get_unescaped_pref(tmp, fmt);
3127 quote_fmt_scan_string(tmp);
3131 quote_fmt_scan_string(fmt);
3135 buf = quote_fmt_get_buffer();
3137 gint line = quote_fmt_get_line();
3138 alertpanel_error(err_msg, line);
3144 prev_autowrap = compose->autowrap;
3145 compose->autowrap = FALSE;
3147 mark = gtk_text_buffer_get_insert(buffer);
3148 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3149 if (g_utf8_validate(buf, -1, NULL)) {
3150 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3152 gchar *tmpout = NULL;
3153 tmpout = conv_codeset_strdup
3154 (buf, conv_get_locale_charset_str_no_utf8(),
3156 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3158 tmpout = g_malloc(strlen(buf)*2+1);
3159 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3161 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3165 cursor_pos = quote_fmt_get_cursor_pos();
3166 if (cursor_pos == -1)
3167 cursor_pos = gtk_text_iter_get_offset(&iter);
3168 compose->set_cursor_pos = cursor_pos;
3170 gtk_text_buffer_get_start_iter(buffer, &iter);
3171 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3172 gtk_text_buffer_place_cursor(buffer, &iter);
3174 compose->autowrap = prev_autowrap;
3175 if (compose->autowrap && rewrap)
3176 compose_wrap_all(compose);
3183 SIGNAL_UNBLOCK(buffer);
3185 procmsg_msginfo_free( &dummyinfo );
3190 /* if ml_post is of type addr@host and from is of type
3191 * addr-anything@host, return TRUE
3193 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3195 gchar *left_ml = NULL;
3196 gchar *right_ml = NULL;
3197 gchar *left_from = NULL;
3198 gchar *right_from = NULL;
3199 gboolean result = FALSE;
3201 if (!ml_post || !from)
3204 left_ml = g_strdup(ml_post);
3205 if (strstr(left_ml, "@")) {
3206 right_ml = strstr(left_ml, "@")+1;
3207 *(strstr(left_ml, "@")) = '\0';
3210 left_from = g_strdup(from);
3211 if (strstr(left_from, "@")) {
3212 right_from = strstr(left_from, "@")+1;
3213 *(strstr(left_from, "@")) = '\0';
3216 if (right_ml && right_from
3217 && !strncmp(left_from, left_ml, strlen(left_ml))
3218 && !strcmp(right_from, right_ml)) {
3227 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3228 gboolean respect_default_to)
3232 if (!folder || !folder->prefs)
3235 if (respect_default_to && folder->prefs->enable_default_to) {
3236 compose_entry_append(compose, folder->prefs->default_to,
3237 COMPOSE_TO, PREF_FOLDER);
3238 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3240 if (folder->prefs->enable_default_cc) {
3241 compose_entry_append(compose, folder->prefs->default_cc,
3242 COMPOSE_CC, PREF_FOLDER);
3243 compose_entry_mark_default_to(compose, folder->prefs->default_cc);
3245 if (folder->prefs->enable_default_bcc) {
3246 compose_entry_append(compose, folder->prefs->default_bcc,
3247 COMPOSE_BCC, PREF_FOLDER);
3248 compose_entry_mark_default_to(compose, folder->prefs->default_bcc);
3250 if (folder->prefs->enable_default_replyto) {
3251 compose_entry_append(compose, folder->prefs->default_replyto,
3252 COMPOSE_REPLYTO, PREF_FOLDER);
3253 compose_entry_mark_default_to(compose, folder->prefs->default_replyto);
3257 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3262 if (!compose || !msginfo)
3265 if (msginfo->subject && *msginfo->subject) {
3266 buf = p = g_strdup(msginfo->subject);
3267 p += subject_get_prefix_length(p);
3268 memmove(buf, p, strlen(p) + 1);
3270 buf2 = g_strdup_printf("Re: %s", buf);
3271 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3276 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3279 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3280 gboolean to_all, gboolean to_ml,
3282 gboolean followup_and_reply_to)
3284 GSList *cc_list = NULL;
3287 gchar *replyto = NULL;
3288 gchar *ac_email = NULL;
3290 gboolean reply_to_ml = FALSE;
3291 gboolean default_reply_to = FALSE;
3293 cm_return_if_fail(compose->account != NULL);
3294 cm_return_if_fail(msginfo != NULL);
3296 reply_to_ml = to_ml && compose->ml_post;
3298 default_reply_to = msginfo->folder &&
3299 msginfo->folder->prefs->enable_default_reply_to;
3301 if (compose->account->protocol != A_NNTP) {
3302 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3304 if (reply_to_ml && !default_reply_to) {
3306 gboolean is_subscr = is_subscription(compose->ml_post,
3309 /* normal answer to ml post with a reply-to */
3310 compose_entry_append(compose,
3312 COMPOSE_TO, PREF_ML);
3313 if (compose->replyto)
3314 compose_entry_append(compose,
3316 COMPOSE_CC, PREF_ML);
3318 /* answer to subscription confirmation */
3319 if (compose->replyto)
3320 compose_entry_append(compose,
3322 COMPOSE_TO, PREF_ML);
3323 else if (msginfo->from)
3324 compose_entry_append(compose,
3326 COMPOSE_TO, PREF_ML);
3329 else if (!(to_all || to_sender) && default_reply_to) {
3330 compose_entry_append(compose,
3331 msginfo->folder->prefs->default_reply_to,
3332 COMPOSE_TO, PREF_FOLDER);
3333 compose_entry_mark_default_to(compose,
3334 msginfo->folder->prefs->default_reply_to);
3340 compose_entry_append(compose, msginfo->from,
3341 COMPOSE_TO, PREF_NONE);
3343 Xstrdup_a(tmp1, msginfo->from, return);
3344 extract_address(tmp1);
3345 compose_entry_append(compose,
3346 (!account_find_from_address(tmp1, FALSE))
3349 COMPOSE_TO, PREF_NONE);
3350 if (compose->replyto)
3351 compose_entry_append(compose,
3353 COMPOSE_CC, PREF_NONE);
3355 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3356 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3357 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3358 if (compose->replyto) {
3359 compose_entry_append(compose,
3361 COMPOSE_TO, PREF_NONE);
3363 compose_entry_append(compose,
3364 msginfo->from ? msginfo->from : "",
3365 COMPOSE_TO, PREF_NONE);
3368 /* replying to own mail, use original recp */
3369 compose_entry_append(compose,
3370 msginfo->to ? msginfo->to : "",
3371 COMPOSE_TO, PREF_NONE);
3372 compose_entry_append(compose,
3373 msginfo->cc ? msginfo->cc : "",
3374 COMPOSE_CC, PREF_NONE);
3379 if (to_sender || (compose->followup_to &&
3380 !strncmp(compose->followup_to, "poster", 6)))
3381 compose_entry_append
3383 (compose->replyto ? compose->replyto :
3384 msginfo->from ? msginfo->from : ""),
3385 COMPOSE_TO, PREF_NONE);
3387 else if (followup_and_reply_to || to_all) {
3388 compose_entry_append
3390 (compose->replyto ? compose->replyto :
3391 msginfo->from ? msginfo->from : ""),
3392 COMPOSE_TO, PREF_NONE);
3394 compose_entry_append
3396 compose->followup_to ? compose->followup_to :
3397 compose->newsgroups ? compose->newsgroups : "",
3398 COMPOSE_NEWSGROUPS, PREF_NONE);
3400 compose_entry_append
3402 msginfo->cc ? msginfo->cc : "",
3403 COMPOSE_CC, PREF_NONE);
3406 compose_entry_append
3408 compose->followup_to ? compose->followup_to :
3409 compose->newsgroups ? compose->newsgroups : "",
3410 COMPOSE_NEWSGROUPS, PREF_NONE);
3412 compose_reply_set_subject(compose, msginfo);
3414 if (to_ml && compose->ml_post) return;
3415 if (!to_all || compose->account->protocol == A_NNTP) return;
3417 if (compose->replyto) {
3418 Xstrdup_a(replyto, compose->replyto, return);
3419 extract_address(replyto);
3421 if (msginfo->from) {
3422 Xstrdup_a(from, msginfo->from, return);
3423 extract_address(from);
3426 if (replyto && from)
3427 cc_list = address_list_append_with_comments(cc_list, from);
3428 if (to_all && msginfo->folder &&
3429 msginfo->folder->prefs->enable_default_reply_to)
3430 cc_list = address_list_append_with_comments(cc_list,
3431 msginfo->folder->prefs->default_reply_to);
3432 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3433 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3435 ac_email = g_utf8_strdown(compose->account->address, -1);
3438 for (cur = cc_list; cur != NULL; cur = cur->next) {
3439 gchar *addr = g_utf8_strdown(cur->data, -1);
3440 extract_address(addr);
3442 if (strcmp(ac_email, addr))
3443 compose_entry_append(compose, (gchar *)cur->data,
3444 COMPOSE_CC, PREF_NONE);
3446 debug_print("Cc address same as compose account's, ignoring\n");
3451 slist_free_strings_full(cc_list);
3457 #define SET_ENTRY(entry, str) \
3460 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3463 #define SET_ADDRESS(type, str) \
3466 compose_entry_append(compose, str, type, PREF_NONE); \
3469 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3471 cm_return_if_fail(msginfo != NULL);
3473 SET_ENTRY(subject_entry, msginfo->subject);
3474 SET_ENTRY(from_name, msginfo->from);
3475 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3476 SET_ADDRESS(COMPOSE_CC, compose->cc);
3477 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3478 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3479 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3480 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3482 compose_update_priority_menu_item(compose);
3483 compose_update_privacy_system_menu_item(compose, FALSE);
3484 compose_show_first_last_header(compose, TRUE);
3490 static void compose_insert_sig(Compose *compose, gboolean replace)
3492 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3493 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3495 GtkTextIter iter, iter_end;
3496 gint cur_pos, ins_pos;
3497 gboolean prev_autowrap;
3498 gboolean found = FALSE;
3499 gboolean exists = FALSE;
3501 cm_return_if_fail(compose->account != NULL);
3505 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3506 G_CALLBACK(compose_changed_cb),
3509 mark = gtk_text_buffer_get_insert(buffer);
3510 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3511 cur_pos = gtk_text_iter_get_offset (&iter);
3514 gtk_text_buffer_get_end_iter(buffer, &iter);
3516 exists = (compose->sig_str != NULL);
3519 GtkTextIter first_iter, start_iter, end_iter;
3521 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3523 if (!exists || compose->sig_str[0] == '\0')
3526 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3527 compose->signature_tag);
3530 /* include previous \n\n */
3531 gtk_text_iter_backward_chars(&first_iter, 1);
3532 start_iter = first_iter;
3533 end_iter = first_iter;
3535 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3536 compose->signature_tag);
3537 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3538 compose->signature_tag);
3540 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3546 g_free(compose->sig_str);
3547 compose->sig_str = account_get_signature_str(compose->account);
3549 cur_pos = gtk_text_iter_get_offset(&iter);
3551 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3552 g_free(compose->sig_str);
3553 compose->sig_str = NULL;
3555 if (compose->sig_inserted == FALSE)
3556 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3557 compose->sig_inserted = TRUE;
3559 cur_pos = gtk_text_iter_get_offset(&iter);
3560 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3562 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3563 gtk_text_iter_forward_chars(&iter, 1);
3564 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3565 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3567 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3568 cur_pos = gtk_text_buffer_get_char_count (buffer);
3571 /* put the cursor where it should be
3572 * either where the quote_fmt says, either where it was */
3573 if (compose->set_cursor_pos < 0)
3574 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3576 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3577 compose->set_cursor_pos);
3579 compose->set_cursor_pos = -1;
3580 gtk_text_buffer_place_cursor(buffer, &iter);
3581 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3582 G_CALLBACK(compose_changed_cb),
3588 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3591 GtkTextBuffer *buffer;
3594 const gchar *cur_encoding;
3595 gchar buf[BUFFSIZE];
3598 gboolean prev_autowrap;
3601 GString *file_contents = NULL;
3602 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3604 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3606 /* get the size of the file we are about to insert */
3607 ret = g_stat(file, &file_stat);
3609 gchar *shortfile = g_path_get_basename(file);
3610 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3612 return COMPOSE_INSERT_NO_FILE;
3613 } else if (prefs_common.warn_large_insert == TRUE) {
3615 /* ask user for confirmation if the file is large */
3616 if (prefs_common.warn_large_insert_size < 0 ||
3617 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3621 msg = g_strdup_printf(_("You are about to insert a file of %s "
3622 "in the message body. Are you sure you want to do that?"),
3623 to_human_readable(file_stat.st_size));
3624 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3625 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3628 /* do we ask for confirmation next time? */
3629 if (aval & G_ALERTDISABLE) {
3630 /* no confirmation next time, disable feature in preferences */
3631 aval &= ~G_ALERTDISABLE;
3632 prefs_common.warn_large_insert = FALSE;
3635 /* abort file insertion if user canceled action */
3636 if (aval != G_ALERTALTERNATE) {
3637 return COMPOSE_INSERT_NO_FILE;
3643 if ((fp = g_fopen(file, "rb")) == NULL) {
3644 FILE_OP_ERROR(file, "fopen");
3645 return COMPOSE_INSERT_READ_ERROR;
3648 prev_autowrap = compose->autowrap;
3649 compose->autowrap = FALSE;
3651 text = GTK_TEXT_VIEW(compose->text);
3652 buffer = gtk_text_view_get_buffer(text);
3653 mark = gtk_text_buffer_get_insert(buffer);
3654 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3656 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3657 G_CALLBACK(text_inserted),
3660 cur_encoding = conv_get_locale_charset_str_no_utf8();
3662 file_contents = g_string_new("");
3663 while (fgets(buf, sizeof(buf), fp) != NULL) {
3666 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3667 str = g_strdup(buf);
3669 codeconv_set_strict(TRUE);
3670 str = conv_codeset_strdup
3671 (buf, cur_encoding, CS_INTERNAL);
3672 codeconv_set_strict(FALSE);
3675 result = COMPOSE_INSERT_INVALID_CHARACTER;
3681 /* strip <CR> if DOS/Windows file,
3682 replace <CR> with <LF> if Macintosh file. */
3685 if (len > 0 && str[len - 1] != '\n') {
3687 if (str[len] == '\r') str[len] = '\n';
3690 file_contents = g_string_append(file_contents, str);
3694 if (result == COMPOSE_INSERT_SUCCESS) {
3695 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3697 compose_changed_cb(NULL, compose);
3698 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3699 G_CALLBACK(text_inserted),
3701 compose->autowrap = prev_autowrap;
3702 if (compose->autowrap)
3703 compose_wrap_all(compose);
3706 g_string_free(file_contents, TRUE);
3712 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3713 const gchar *filename,
3714 const gchar *content_type,
3715 const gchar *charset)
3723 GtkListStore *store;
3725 gboolean has_binary = FALSE;
3727 if (!is_file_exist(file)) {
3728 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3729 gboolean result = FALSE;
3730 if (file_from_uri && is_file_exist(file_from_uri)) {
3731 result = compose_attach_append(
3732 compose, file_from_uri,
3733 filename, content_type,
3736 g_free(file_from_uri);
3739 alertpanel_error("File %s doesn't exist\n", filename);
3742 if ((size = get_file_size(file)) < 0) {
3743 alertpanel_error("Can't get file size of %s\n", filename);
3747 /* In batch mode, we allow 0-length files to be attached no questions asked */
3748 if (size == 0 && !compose->batch) {
3749 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3750 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3751 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3752 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3755 if (aval != G_ALERTALTERNATE) {
3759 if ((fp = g_fopen(file, "rb")) == NULL) {
3760 alertpanel_error(_("Can't read %s."), filename);
3765 ainfo = g_new0(AttachInfo, 1);
3766 auto_ainfo = g_auto_pointer_new_with_free
3767 (ainfo, (GFreeFunc) compose_attach_info_free);
3768 ainfo->file = g_strdup(file);
3771 ainfo->content_type = g_strdup(content_type);
3772 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3774 MsgFlags flags = {0, 0};
3776 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3777 ainfo->encoding = ENC_7BIT;
3779 ainfo->encoding = ENC_8BIT;
3781 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3782 if (msginfo && msginfo->subject)
3783 name = g_strdup(msginfo->subject);
3785 name = g_path_get_basename(filename ? filename : file);
3787 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3789 procmsg_msginfo_free(&msginfo);
3791 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3792 ainfo->charset = g_strdup(charset);
3793 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3795 ainfo->encoding = ENC_BASE64;
3797 name = g_path_get_basename(filename ? filename : file);
3798 ainfo->name = g_strdup(name);
3802 ainfo->content_type = procmime_get_mime_type(file);
3803 if (!ainfo->content_type) {
3804 ainfo->content_type =
3805 g_strdup("application/octet-stream");
3806 ainfo->encoding = ENC_BASE64;
3807 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3809 procmime_get_encoding_for_text_file(file, &has_binary);
3811 ainfo->encoding = ENC_BASE64;
3812 name = g_path_get_basename(filename ? filename : file);
3813 ainfo->name = g_strdup(name);
3817 if (ainfo->name != NULL
3818 && !strcmp(ainfo->name, ".")) {
3819 g_free(ainfo->name);
3823 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3824 g_free(ainfo->content_type);
3825 ainfo->content_type = g_strdup("application/octet-stream");
3826 g_free(ainfo->charset);
3827 ainfo->charset = NULL;
3830 ainfo->size = (goffset)size;
3831 size_text = to_human_readable((goffset)size);
3833 store = GTK_LIST_STORE(gtk_tree_view_get_model
3834 (GTK_TREE_VIEW(compose->attach_clist)));
3836 gtk_list_store_append(store, &iter);
3837 gtk_list_store_set(store, &iter,
3838 COL_MIMETYPE, ainfo->content_type,
3839 COL_SIZE, size_text,
3840 COL_NAME, ainfo->name,
3841 COL_CHARSET, ainfo->charset,
3843 COL_AUTODATA, auto_ainfo,
3846 g_auto_pointer_free(auto_ainfo);
3847 compose_attach_update_label(compose);
3851 static void compose_use_signing(Compose *compose, gboolean use_signing)
3853 compose->use_signing = use_signing;
3854 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3857 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3859 compose->use_encryption = use_encryption;
3860 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3863 #define NEXT_PART_NOT_CHILD(info) \
3865 node = info->node; \
3866 while (node->children) \
3867 node = g_node_last_child(node); \
3868 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3871 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3875 MimeInfo *firsttext = NULL;
3876 MimeInfo *encrypted = NULL;
3879 const gchar *partname = NULL;
3881 mimeinfo = procmime_scan_message(msginfo);
3882 if (!mimeinfo) return;
3884 if (mimeinfo->node->children == NULL) {
3885 procmime_mimeinfo_free_all(&mimeinfo);
3889 /* find first content part */
3890 child = (MimeInfo *) mimeinfo->node->children->data;
3891 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3892 child = (MimeInfo *)child->node->children->data;
3895 if (child->type == MIMETYPE_TEXT) {
3897 debug_print("First text part found\n");
3898 } else if (compose->mode == COMPOSE_REEDIT &&
3899 child->type == MIMETYPE_APPLICATION &&
3900 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3901 encrypted = (MimeInfo *)child->node->parent->data;
3904 child = (MimeInfo *) mimeinfo->node->children->data;
3905 while (child != NULL) {
3908 if (child == encrypted) {
3909 /* skip this part of tree */
3910 NEXT_PART_NOT_CHILD(child);
3914 if (child->type == MIMETYPE_MULTIPART) {
3915 /* get the actual content */
3916 child = procmime_mimeinfo_next(child);
3920 if (child == firsttext) {
3921 child = procmime_mimeinfo_next(child);
3925 outfile = procmime_get_tmp_file_name(child);
3926 if ((err = procmime_get_part(outfile, child)) < 0)
3927 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3929 gchar *content_type;
3931 content_type = procmime_get_content_type_str(child->type, child->subtype);
3933 /* if we meet a pgp signature, we don't attach it, but
3934 * we force signing. */
3935 if ((strcmp(content_type, "application/pgp-signature") &&
3936 strcmp(content_type, "application/pkcs7-signature") &&
3937 strcmp(content_type, "application/x-pkcs7-signature"))
3938 || compose->mode == COMPOSE_REDIRECT) {
3939 partname = procmime_mimeinfo_get_parameter(child, "filename");
3940 if (partname == NULL)
3941 partname = procmime_mimeinfo_get_parameter(child, "name");
3942 if (partname == NULL)
3944 compose_attach_append(compose, outfile,
3945 partname, content_type,
3946 procmime_mimeinfo_get_parameter(child, "charset"));
3948 compose_force_signing(compose, compose->account, NULL);
3950 g_free(content_type);
3953 NEXT_PART_NOT_CHILD(child);
3955 procmime_mimeinfo_free_all(&mimeinfo);
3958 #undef NEXT_PART_NOT_CHILD
3963 WAIT_FOR_INDENT_CHAR,
3964 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3967 /* return indent length, we allow:
3968 indent characters followed by indent characters or spaces/tabs,
3969 alphabets and numbers immediately followed by indent characters,
3970 and the repeating sequences of the above
3971 If quote ends with multiple spaces, only the first one is included. */
3972 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3973 const GtkTextIter *start, gint *len)
3975 GtkTextIter iter = *start;
3979 IndentState state = WAIT_FOR_INDENT_CHAR;
3982 gint alnum_count = 0;
3983 gint space_count = 0;
3986 if (prefs_common.quote_chars == NULL) {
3990 while (!gtk_text_iter_ends_line(&iter)) {
3991 wc = gtk_text_iter_get_char(&iter);
3992 if (g_unichar_iswide(wc))
3994 clen = g_unichar_to_utf8(wc, ch);
3998 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3999 is_space = g_unichar_isspace(wc);
4001 if (state == WAIT_FOR_INDENT_CHAR) {
4002 if (!is_indent && !g_unichar_isalnum(wc))
4005 quote_len += alnum_count + space_count + 1;
4006 alnum_count = space_count = 0;
4007 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4010 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4011 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4015 else if (is_indent) {
4016 quote_len += alnum_count + space_count + 1;
4017 alnum_count = space_count = 0;
4020 state = WAIT_FOR_INDENT_CHAR;
4024 gtk_text_iter_forward_char(&iter);
4027 if (quote_len > 0 && space_count > 0)
4033 if (quote_len > 0) {
4035 gtk_text_iter_forward_chars(&iter, quote_len);
4036 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4042 /* return >0 if the line is itemized */
4043 static int compose_itemized_length(GtkTextBuffer *buffer,
4044 const GtkTextIter *start)
4046 GtkTextIter iter = *start;
4051 if (gtk_text_iter_ends_line(&iter))
4056 wc = gtk_text_iter_get_char(&iter);
4057 if (!g_unichar_isspace(wc))
4059 gtk_text_iter_forward_char(&iter);
4060 if (gtk_text_iter_ends_line(&iter))
4064 clen = g_unichar_to_utf8(wc, ch);
4068 if (!strchr("*-+", ch[0]))
4071 gtk_text_iter_forward_char(&iter);
4072 if (gtk_text_iter_ends_line(&iter))
4074 wc = gtk_text_iter_get_char(&iter);
4075 if (g_unichar_isspace(wc)) {
4081 /* return the string at the start of the itemization */
4082 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4083 const GtkTextIter *start)
4085 GtkTextIter iter = *start;
4088 GString *item_chars = g_string_new("");
4091 if (gtk_text_iter_ends_line(&iter))
4096 wc = gtk_text_iter_get_char(&iter);
4097 if (!g_unichar_isspace(wc))
4099 gtk_text_iter_forward_char(&iter);
4100 if (gtk_text_iter_ends_line(&iter))
4102 g_string_append_unichar(item_chars, wc);
4105 str = item_chars->str;
4106 g_string_free(item_chars, FALSE);
4110 /* return the number of spaces at a line's start */
4111 static int compose_left_offset_length(GtkTextBuffer *buffer,
4112 const GtkTextIter *start)
4114 GtkTextIter iter = *start;
4117 if (gtk_text_iter_ends_line(&iter))
4121 wc = gtk_text_iter_get_char(&iter);
4122 if (!g_unichar_isspace(wc))
4125 gtk_text_iter_forward_char(&iter);
4126 if (gtk_text_iter_ends_line(&iter))
4130 gtk_text_iter_forward_char(&iter);
4131 if (gtk_text_iter_ends_line(&iter))
4136 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4137 const GtkTextIter *start,
4138 GtkTextIter *break_pos,
4142 GtkTextIter iter = *start, line_end = *start;
4143 PangoLogAttr *attrs;
4150 gboolean can_break = FALSE;
4151 gboolean do_break = FALSE;
4152 gboolean was_white = FALSE;
4153 gboolean prev_dont_break = FALSE;
4155 gtk_text_iter_forward_to_line_end(&line_end);
4156 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4157 len = g_utf8_strlen(str, -1);
4161 g_warning("compose_get_line_break_pos: len = 0!");
4165 /* g_print("breaking line: %d: %s (len = %d)\n",
4166 gtk_text_iter_get_line(&iter), str, len); */
4168 attrs = g_new(PangoLogAttr, len + 1);
4170 pango_default_break(str, -1, NULL, attrs, len + 1);
4174 /* skip quote and leading spaces */
4175 for (i = 0; *p != '\0' && i < len; i++) {
4178 wc = g_utf8_get_char(p);
4179 if (i >= quote_len && !g_unichar_isspace(wc))
4181 if (g_unichar_iswide(wc))
4183 else if (*p == '\t')
4187 p = g_utf8_next_char(p);
4190 for (; *p != '\0' && i < len; i++) {
4191 PangoLogAttr *attr = attrs + i;
4195 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4198 was_white = attr->is_white;
4200 /* don't wrap URI */
4201 if ((uri_len = get_uri_len(p)) > 0) {
4203 if (pos > 0 && col > max_col) {
4213 wc = g_utf8_get_char(p);
4214 if (g_unichar_iswide(wc)) {
4216 if (prev_dont_break && can_break && attr->is_line_break)
4218 } else if (*p == '\t')
4222 if (pos > 0 && col > max_col) {
4227 if (*p == '-' || *p == '/')
4228 prev_dont_break = TRUE;
4230 prev_dont_break = FALSE;
4232 p = g_utf8_next_char(p);
4236 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4241 *break_pos = *start;
4242 gtk_text_iter_set_line_offset(break_pos, pos);
4247 static gboolean compose_join_next_line(Compose *compose,
4248 GtkTextBuffer *buffer,
4250 const gchar *quote_str)
4252 GtkTextIter iter_ = *iter, cur, prev, next, end;
4253 PangoLogAttr attrs[3];
4255 gchar *next_quote_str;
4258 gboolean keep_cursor = FALSE;
4260 if (!gtk_text_iter_forward_line(&iter_) ||
4261 gtk_text_iter_ends_line(&iter_)) {
4264 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4266 if ((quote_str || next_quote_str) &&
4267 strcmp2(quote_str, next_quote_str) != 0) {
4268 g_free(next_quote_str);
4271 g_free(next_quote_str);
4274 if (quote_len > 0) {
4275 gtk_text_iter_forward_chars(&end, quote_len);
4276 if (gtk_text_iter_ends_line(&end)) {
4281 /* don't join itemized lines */
4282 if (compose_itemized_length(buffer, &end) > 0) {
4286 /* don't join signature separator */
4287 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4290 /* delete quote str */
4292 gtk_text_buffer_delete(buffer, &iter_, &end);
4294 /* don't join line breaks put by the user */
4296 gtk_text_iter_backward_char(&cur);
4297 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4298 gtk_text_iter_forward_char(&cur);
4302 gtk_text_iter_forward_char(&cur);
4303 /* delete linebreak and extra spaces */
4304 while (gtk_text_iter_backward_char(&cur)) {
4305 wc1 = gtk_text_iter_get_char(&cur);
4306 if (!g_unichar_isspace(wc1))
4311 while (!gtk_text_iter_ends_line(&cur)) {
4312 wc1 = gtk_text_iter_get_char(&cur);
4313 if (!g_unichar_isspace(wc1))
4315 gtk_text_iter_forward_char(&cur);
4318 if (!gtk_text_iter_equal(&prev, &next)) {
4321 mark = gtk_text_buffer_get_insert(buffer);
4322 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4323 if (gtk_text_iter_equal(&prev, &cur))
4325 gtk_text_buffer_delete(buffer, &prev, &next);
4329 /* insert space if required */
4330 gtk_text_iter_backward_char(&prev);
4331 wc1 = gtk_text_iter_get_char(&prev);
4332 wc2 = gtk_text_iter_get_char(&next);
4333 gtk_text_iter_forward_char(&next);
4334 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4335 pango_default_break(str, -1, NULL, attrs, 3);
4336 if (!attrs[1].is_line_break ||
4337 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4338 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4340 gtk_text_iter_backward_char(&iter_);
4341 gtk_text_buffer_place_cursor(buffer, &iter_);
4350 #define ADD_TXT_POS(bp_, ep_, pti_) \
4351 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4352 last = last->next; \
4353 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4354 last->next = NULL; \
4356 g_warning("alloc error scanning URIs"); \
4359 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4361 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4362 GtkTextBuffer *buffer;
4363 GtkTextIter iter, break_pos, end_of_line;
4364 gchar *quote_str = NULL;
4366 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4367 gboolean prev_autowrap = compose->autowrap;
4368 gint startq_offset = -1, noq_offset = -1;
4369 gint uri_start = -1, uri_stop = -1;
4370 gint nouri_start = -1, nouri_stop = -1;
4371 gint num_blocks = 0;
4372 gint quotelevel = -1;
4373 gboolean modified = force;
4374 gboolean removed = FALSE;
4375 gboolean modified_before_remove = FALSE;
4377 gboolean start = TRUE;
4378 gint itemized_len = 0, rem_item_len = 0;
4379 gchar *itemized_chars = NULL;
4380 gboolean item_continuation = FALSE;
4385 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4389 compose->autowrap = FALSE;
4391 buffer = gtk_text_view_get_buffer(text);
4392 undo_wrapping(compose->undostruct, TRUE);
4397 mark = gtk_text_buffer_get_insert(buffer);
4398 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4402 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4403 if (gtk_text_iter_ends_line(&iter)) {
4404 while (gtk_text_iter_ends_line(&iter) &&
4405 gtk_text_iter_forward_line(&iter))
4408 while (gtk_text_iter_backward_line(&iter)) {
4409 if (gtk_text_iter_ends_line(&iter)) {
4410 gtk_text_iter_forward_line(&iter);
4416 /* move to line start */
4417 gtk_text_iter_set_line_offset(&iter, 0);
4420 itemized_len = compose_itemized_length(buffer, &iter);
4422 if (!itemized_len) {
4423 itemized_len = compose_left_offset_length(buffer, &iter);
4424 item_continuation = TRUE;
4428 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4430 /* go until paragraph end (empty line) */
4431 while (start || !gtk_text_iter_ends_line(&iter)) {
4432 gchar *scanpos = NULL;
4433 /* parse table - in order of priority */
4435 const gchar *needle; /* token */
4437 /* token search function */
4438 gchar *(*search) (const gchar *haystack,
4439 const gchar *needle);
4440 /* part parsing function */
4441 gboolean (*parse) (const gchar *start,
4442 const gchar *scanpos,
4446 /* part to URI function */
4447 gchar *(*build_uri) (const gchar *bp,
4451 static struct table parser[] = {
4452 {"http://", strcasestr, get_uri_part, make_uri_string},
4453 {"https://", strcasestr, get_uri_part, make_uri_string},
4454 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4455 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4456 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4457 {"www.", strcasestr, get_uri_part, make_http_string},
4458 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4459 {"@", strcasestr, get_email_part, make_email_string}
4461 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4462 gint last_index = PARSE_ELEMS;
4464 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4468 if (!prev_autowrap && num_blocks == 0) {
4470 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4471 G_CALLBACK(text_inserted),
4474 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4477 uri_start = uri_stop = -1;
4479 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4482 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4483 if (startq_offset == -1)
4484 startq_offset = gtk_text_iter_get_offset(&iter);
4485 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4486 if (quotelevel > 2) {
4487 /* recycle colors */
4488 if (prefs_common.recycle_quote_colors)
4497 if (startq_offset == -1)
4498 noq_offset = gtk_text_iter_get_offset(&iter);
4502 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4505 if (gtk_text_iter_ends_line(&iter)) {
4507 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4508 prefs_common.linewrap_len,
4510 GtkTextIter prev, next, cur;
4511 if (prev_autowrap != FALSE || force) {
4512 compose->automatic_break = TRUE;
4514 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4515 compose->automatic_break = FALSE;
4516 if (itemized_len && compose->autoindent) {
4517 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4518 if (!item_continuation)
4519 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4521 } else if (quote_str && wrap_quote) {
4522 compose->automatic_break = TRUE;
4524 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4525 compose->automatic_break = FALSE;
4526 if (itemized_len && compose->autoindent) {
4527 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4528 if (!item_continuation)
4529 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4533 /* remove trailing spaces */
4535 rem_item_len = itemized_len;
4536 while (compose->autoindent && rem_item_len-- > 0)
4537 gtk_text_iter_backward_char(&cur);
4538 gtk_text_iter_backward_char(&cur);
4541 while (!gtk_text_iter_starts_line(&cur)) {
4544 gtk_text_iter_backward_char(&cur);
4545 wc = gtk_text_iter_get_char(&cur);
4546 if (!g_unichar_isspace(wc))
4550 if (!gtk_text_iter_equal(&prev, &next)) {
4551 gtk_text_buffer_delete(buffer, &prev, &next);
4553 gtk_text_iter_forward_char(&break_pos);
4557 gtk_text_buffer_insert(buffer, &break_pos,
4561 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4563 /* move iter to current line start */
4564 gtk_text_iter_set_line_offset(&iter, 0);
4571 /* move iter to next line start */
4577 if (!prev_autowrap && num_blocks > 0) {
4579 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4580 G_CALLBACK(text_inserted),
4584 while (!gtk_text_iter_ends_line(&end_of_line)) {
4585 gtk_text_iter_forward_char(&end_of_line);
4587 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4589 nouri_start = gtk_text_iter_get_offset(&iter);
4590 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4592 walk_pos = gtk_text_iter_get_offset(&iter);
4593 /* FIXME: this looks phony. scanning for anything in the parse table */
4594 for (n = 0; n < PARSE_ELEMS; n++) {
4597 tmp = parser[n].search(walk, parser[n].needle);
4599 if (scanpos == NULL || tmp < scanpos) {
4608 /* check if URI can be parsed */
4609 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4610 (const gchar **)&ep, FALSE)
4611 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4615 strlen(parser[last_index].needle);
4618 uri_start = walk_pos + (bp - o_walk);
4619 uri_stop = walk_pos + (ep - o_walk);
4623 gtk_text_iter_forward_line(&iter);
4626 if (startq_offset != -1) {
4627 GtkTextIter startquote, endquote;
4628 gtk_text_buffer_get_iter_at_offset(
4629 buffer, &startquote, startq_offset);
4632 switch (quotelevel) {
4634 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4635 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4636 gtk_text_buffer_apply_tag_by_name(
4637 buffer, "quote0", &startquote, &endquote);
4638 gtk_text_buffer_remove_tag_by_name(
4639 buffer, "quote1", &startquote, &endquote);
4640 gtk_text_buffer_remove_tag_by_name(
4641 buffer, "quote2", &startquote, &endquote);
4646 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4647 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4648 gtk_text_buffer_apply_tag_by_name(
4649 buffer, "quote1", &startquote, &endquote);
4650 gtk_text_buffer_remove_tag_by_name(
4651 buffer, "quote0", &startquote, &endquote);
4652 gtk_text_buffer_remove_tag_by_name(
4653 buffer, "quote2", &startquote, &endquote);
4658 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4659 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4660 gtk_text_buffer_apply_tag_by_name(
4661 buffer, "quote2", &startquote, &endquote);
4662 gtk_text_buffer_remove_tag_by_name(
4663 buffer, "quote0", &startquote, &endquote);
4664 gtk_text_buffer_remove_tag_by_name(
4665 buffer, "quote1", &startquote, &endquote);
4671 } else if (noq_offset != -1) {
4672 GtkTextIter startnoquote, endnoquote;
4673 gtk_text_buffer_get_iter_at_offset(
4674 buffer, &startnoquote, noq_offset);
4677 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4678 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4679 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4680 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4681 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4682 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4683 gtk_text_buffer_remove_tag_by_name(
4684 buffer, "quote0", &startnoquote, &endnoquote);
4685 gtk_text_buffer_remove_tag_by_name(
4686 buffer, "quote1", &startnoquote, &endnoquote);
4687 gtk_text_buffer_remove_tag_by_name(
4688 buffer, "quote2", &startnoquote, &endnoquote);
4694 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4695 GtkTextIter nouri_start_iter, nouri_end_iter;
4696 gtk_text_buffer_get_iter_at_offset(
4697 buffer, &nouri_start_iter, nouri_start);
4698 gtk_text_buffer_get_iter_at_offset(
4699 buffer, &nouri_end_iter, nouri_stop);
4700 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4701 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4702 gtk_text_buffer_remove_tag_by_name(
4703 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4704 modified_before_remove = modified;
4709 if (uri_start >= 0 && uri_stop > 0) {
4710 GtkTextIter uri_start_iter, uri_end_iter, back;
4711 gtk_text_buffer_get_iter_at_offset(
4712 buffer, &uri_start_iter, uri_start);
4713 gtk_text_buffer_get_iter_at_offset(
4714 buffer, &uri_end_iter, uri_stop);
4715 back = uri_end_iter;
4716 gtk_text_iter_backward_char(&back);
4717 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4718 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4719 gtk_text_buffer_apply_tag_by_name(
4720 buffer, "link", &uri_start_iter, &uri_end_iter);
4722 if (removed && !modified_before_remove) {
4728 // debug_print("not modified, out after %d lines\n", lines);
4732 // debug_print("modified, out after %d lines\n", lines);
4734 g_free(itemized_chars);
4737 undo_wrapping(compose->undostruct, FALSE);
4738 compose->autowrap = prev_autowrap;
4743 void compose_action_cb(void *data)
4745 Compose *compose = (Compose *)data;
4746 compose_wrap_all(compose);
4749 static void compose_wrap_all(Compose *compose)
4751 compose_wrap_all_full(compose, FALSE);
4754 static void compose_wrap_all_full(Compose *compose, gboolean force)
4756 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4757 GtkTextBuffer *buffer;
4759 gboolean modified = TRUE;
4761 buffer = gtk_text_view_get_buffer(text);
4763 gtk_text_buffer_get_start_iter(buffer, &iter);
4765 undo_wrapping(compose->undostruct, TRUE);
4767 while (!gtk_text_iter_is_end(&iter) && modified)
4768 modified = compose_beautify_paragraph(compose, &iter, force);
4770 undo_wrapping(compose->undostruct, FALSE);
4774 static void compose_set_title(Compose *compose)
4780 edited = compose->modified ? _(" [Edited]") : "";
4782 subject = gtk_editable_get_chars(
4783 GTK_EDITABLE(compose->subject_entry), 0, -1);
4785 #ifndef GENERIC_UMPC
4786 if (subject && strlen(subject))
4787 str = g_strdup_printf(_("%s - Compose message%s"),
4790 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4792 str = g_strdup(_("Compose message"));
4795 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4801 * compose_current_mail_account:
4803 * Find a current mail account (the currently selected account, or the
4804 * default account, if a news account is currently selected). If a
4805 * mail account cannot be found, display an error message.
4807 * Return value: Mail account, or NULL if not found.
4809 static PrefsAccount *
4810 compose_current_mail_account(void)
4814 if (cur_account && cur_account->protocol != A_NNTP)
4817 ac = account_get_default();
4818 if (!ac || ac->protocol == A_NNTP) {
4819 alertpanel_error(_("Account for sending mail is not specified.\n"
4820 "Please select a mail account before sending."));
4827 #define QUOTE_IF_REQUIRED(out, str) \
4829 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4833 len = strlen(str) + 3; \
4834 if ((__tmp = alloca(len)) == NULL) { \
4835 g_warning("can't allocate memory"); \
4836 g_string_free(header, TRUE); \
4839 g_snprintf(__tmp, len, "\"%s\"", str); \
4844 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4845 g_warning("can't allocate memory"); \
4846 g_string_free(header, TRUE); \
4849 strcpy(__tmp, str); \
4855 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4857 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4861 len = strlen(str) + 3; \
4862 if ((__tmp = alloca(len)) == NULL) { \
4863 g_warning("can't allocate memory"); \
4866 g_snprintf(__tmp, len, "\"%s\"", str); \
4871 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4872 g_warning("can't allocate memory"); \
4875 strcpy(__tmp, str); \
4881 static void compose_select_account(Compose *compose, PrefsAccount *account,
4884 gchar *from = NULL, *header = NULL;
4885 ComposeHeaderEntry *header_entry;
4886 #if GTK_CHECK_VERSION(2, 24, 0)
4890 cm_return_if_fail(account != NULL);
4892 compose->account = account;
4893 if (account->name && *account->name) {
4895 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4896 qbuf = escape_internal_quotes(buf, '"');
4897 from = g_strdup_printf("%s <%s>",
4898 qbuf, account->address);
4901 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4903 from = g_strdup_printf("<%s>",
4905 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4910 compose_set_title(compose);
4912 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4913 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4915 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4916 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4917 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4919 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4921 activate_privacy_system(compose, account, FALSE);
4923 if (!init && compose->mode != COMPOSE_REDIRECT) {
4924 undo_block(compose->undostruct);
4925 compose_insert_sig(compose, TRUE);
4926 undo_unblock(compose->undostruct);
4929 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4930 #if !GTK_CHECK_VERSION(2, 24, 0)
4931 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4933 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4934 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4935 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4938 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4939 if (account->protocol == A_NNTP) {
4940 if (!strcmp(header, _("To:")))
4941 combobox_select_by_text(
4942 GTK_COMBO_BOX(header_entry->combo),
4945 if (!strcmp(header, _("Newsgroups:")))
4946 combobox_select_by_text(
4947 GTK_COMBO_BOX(header_entry->combo),
4955 /* use account's dict info if set */
4956 if (compose->gtkaspell) {
4957 if (account->enable_default_dictionary)
4958 gtkaspell_change_dict(compose->gtkaspell,
4959 account->default_dictionary, FALSE);
4960 if (account->enable_default_alt_dictionary)
4961 gtkaspell_change_alt_dict(compose->gtkaspell,
4962 account->default_alt_dictionary);
4963 if (account->enable_default_dictionary
4964 || account->enable_default_alt_dictionary)
4965 compose_spell_menu_changed(compose);
4970 gboolean compose_check_for_valid_recipient(Compose *compose) {
4971 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4972 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4973 gboolean recipient_found = FALSE;
4977 /* free to and newsgroup list */
4978 slist_free_strings_full(compose->to_list);
4979 compose->to_list = NULL;
4981 slist_free_strings_full(compose->newsgroup_list);
4982 compose->newsgroup_list = NULL;
4984 /* search header entries for to and newsgroup entries */
4985 for (list = compose->header_list; list; list = list->next) {
4988 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4989 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4992 if (entry[0] != '\0') {
4993 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4994 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4995 compose->to_list = address_list_append(compose->to_list, entry);
4996 recipient_found = TRUE;
4999 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
5000 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5001 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5002 recipient_found = TRUE;
5009 return recipient_found;
5012 static gboolean compose_check_for_set_recipients(Compose *compose)
5014 if (compose->account->set_autocc && compose->account->auto_cc) {
5015 gboolean found_other = FALSE;
5017 /* search header entries for to and newsgroup entries */
5018 for (list = compose->header_list; list; list = list->next) {
5021 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5022 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5025 if (strcmp(entry, compose->account->auto_cc)
5026 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5036 if (compose->batch) {
5037 gtk_widget_show_all(compose->window);
5039 aval = alertpanel(_("Send"),
5040 _("The only recipient is the default CC address. Send anyway?"),
5041 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5042 if (aval != G_ALERTALTERNATE)
5046 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5047 gboolean found_other = FALSE;
5049 /* search header entries for to and newsgroup entries */
5050 for (list = compose->header_list; list; list = list->next) {
5053 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5054 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5057 if (strcmp(entry, compose->account->auto_bcc)
5058 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5068 if (compose->batch) {
5069 gtk_widget_show_all(compose->window);
5071 aval = alertpanel(_("Send"),
5072 _("The only recipient is the default BCC address. Send anyway?"),
5073 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5074 if (aval != G_ALERTALTERNATE)
5081 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5085 if (compose_check_for_valid_recipient(compose) == FALSE) {
5086 if (compose->batch) {
5087 gtk_widget_show_all(compose->window);
5089 alertpanel_error(_("Recipient is not specified."));
5093 if (compose_check_for_set_recipients(compose) == FALSE) {
5097 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5098 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5099 if (*str == '\0' && check_everything == TRUE &&
5100 compose->mode != COMPOSE_REDIRECT) {
5102 gchar *button_label;
5105 if (compose->sending)
5106 button_label = g_strconcat("+", _("_Send"), NULL);
5108 button_label = g_strconcat("+", _("_Queue"), NULL);
5109 message = g_strdup_printf(_("Subject is empty. %s"),
5110 compose->sending?_("Send it anyway?"):
5111 _("Queue it anyway?"));
5113 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5114 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5115 ALERT_QUESTION, G_ALERTDEFAULT);
5117 if (aval & G_ALERTDISABLE) {
5118 aval &= ~G_ALERTDISABLE;
5119 prefs_common.warn_empty_subj = FALSE;
5121 if (aval != G_ALERTALTERNATE)
5126 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5132 gint compose_send(Compose *compose)
5135 FolderItem *folder = NULL;
5137 gchar *msgpath = NULL;
5138 gboolean discard_window = FALSE;
5139 gchar *errstr = NULL;
5140 gchar *tmsgid = NULL;
5141 MainWindow *mainwin = mainwindow_get_mainwindow();
5142 gboolean queued_removed = FALSE;
5144 if (prefs_common.send_dialog_invisible
5145 || compose->batch == TRUE)
5146 discard_window = TRUE;
5148 compose_allow_user_actions (compose, FALSE);
5149 compose->sending = TRUE;
5151 if (compose_check_entries(compose, TRUE) == FALSE) {
5152 if (compose->batch) {
5153 gtk_widget_show_all(compose->window);
5159 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5162 if (compose->batch) {
5163 gtk_widget_show_all(compose->window);
5166 alertpanel_error(_("Could not queue message for sending:\n\n"
5167 "Charset conversion failed."));
5168 } else if (val == -5) {
5169 alertpanel_error(_("Could not queue message for sending:\n\n"
5170 "Couldn't get recipient encryption key."));
5171 } else if (val == -6) {
5173 } else if (val == -3) {
5174 if (privacy_peek_error())
5175 alertpanel_error(_("Could not queue message for sending:\n\n"
5176 "Signature failed: %s"), privacy_get_error());
5177 } else if (val == -2 && errno != 0) {
5178 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5180 alertpanel_error(_("Could not queue message for sending."));
5185 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5186 if (discard_window) {
5187 compose->sending = FALSE;
5188 compose_close(compose);
5189 /* No more compose access in the normal codepath
5190 * after this point! */
5195 alertpanel_error(_("The message was queued but could not be "
5196 "sent.\nUse \"Send queued messages\" from "
5197 "the main window to retry."));
5198 if (!discard_window) {
5205 if (msgpath == NULL) {
5206 msgpath = folder_item_fetch_msg(folder, msgnum);
5207 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5210 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5211 claws_unlink(msgpath);
5214 if (!discard_window) {
5216 if (!queued_removed)
5217 folder_item_remove_msg(folder, msgnum);
5218 folder_item_scan(folder);
5220 /* make sure we delete that */
5221 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5223 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5224 folder_item_remove_msg(folder, tmp->msgnum);
5225 procmsg_msginfo_free(&tmp);
5232 if (!queued_removed)
5233 folder_item_remove_msg(folder, msgnum);
5234 folder_item_scan(folder);
5236 /* make sure we delete that */
5237 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5239 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5240 folder_item_remove_msg(folder, tmp->msgnum);
5241 procmsg_msginfo_free(&tmp);
5244 if (!discard_window) {
5245 compose->sending = FALSE;
5246 compose_allow_user_actions (compose, TRUE);
5247 compose_close(compose);
5251 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5252 "the main window to retry."), errstr);
5255 alertpanel_error_log(_("The message was queued but could not be "
5256 "sent.\nUse \"Send queued messages\" from "
5257 "the main window to retry."));
5259 if (!discard_window) {
5268 toolbar_main_set_sensitive(mainwin);
5269 main_window_set_menu_sensitive(mainwin);
5275 compose_allow_user_actions (compose, TRUE);
5276 compose->sending = FALSE;
5277 compose->modified = TRUE;
5278 toolbar_main_set_sensitive(mainwin);
5279 main_window_set_menu_sensitive(mainwin);
5284 static gboolean compose_use_attach(Compose *compose)
5286 GtkTreeModel *model = gtk_tree_view_get_model
5287 (GTK_TREE_VIEW(compose->attach_clist));
5288 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5291 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5294 gchar buf[BUFFSIZE];
5296 gboolean first_to_address;
5297 gboolean first_cc_address;
5299 ComposeHeaderEntry *headerentry;
5300 const gchar *headerentryname;
5301 const gchar *cc_hdr;
5302 const gchar *to_hdr;
5303 gboolean err = FALSE;
5305 debug_print("Writing redirect header\n");
5307 cc_hdr = prefs_common_translated_header_name("Cc:");
5308 to_hdr = prefs_common_translated_header_name("To:");
5310 first_to_address = TRUE;
5311 for (list = compose->header_list; list; list = list->next) {
5312 headerentry = ((ComposeHeaderEntry *)list->data);
5313 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5315 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5316 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5317 Xstrdup_a(str, entstr, return -1);
5319 if (str[0] != '\0') {
5320 compose_convert_header
5321 (compose, buf, sizeof(buf), str,
5322 strlen("Resent-To") + 2, TRUE);
5324 if (first_to_address) {
5325 err |= (fprintf(fp, "Resent-To: ") < 0);
5326 first_to_address = FALSE;
5328 err |= (fprintf(fp, ",") < 0);
5330 err |= (fprintf(fp, "%s", buf) < 0);
5334 if (!first_to_address) {
5335 err |= (fprintf(fp, "\n") < 0);
5338 first_cc_address = TRUE;
5339 for (list = compose->header_list; list; list = list->next) {
5340 headerentry = ((ComposeHeaderEntry *)list->data);
5341 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5343 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5344 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5345 Xstrdup_a(str, strg, return -1);
5347 if (str[0] != '\0') {
5348 compose_convert_header
5349 (compose, buf, sizeof(buf), str,
5350 strlen("Resent-Cc") + 2, TRUE);
5352 if (first_cc_address) {
5353 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5354 first_cc_address = FALSE;
5356 err |= (fprintf(fp, ",") < 0);
5358 err |= (fprintf(fp, "%s", buf) < 0);
5362 if (!first_cc_address) {
5363 err |= (fprintf(fp, "\n") < 0);
5366 return (err ? -1:0);
5369 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5371 gchar buf[BUFFSIZE];
5373 const gchar *entstr;
5374 /* struct utsname utsbuf; */
5375 gboolean err = FALSE;
5377 cm_return_val_if_fail(fp != NULL, -1);
5378 cm_return_val_if_fail(compose->account != NULL, -1);
5379 cm_return_val_if_fail(compose->account->address != NULL, -1);
5382 if (prefs_common.hide_timezone)
5383 get_rfc822_date_hide_tz(buf, sizeof(buf));
5385 get_rfc822_date(buf, sizeof(buf));
5386 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5389 if (compose->account->name && *compose->account->name) {
5390 compose_convert_header
5391 (compose, buf, sizeof(buf), compose->account->name,
5392 strlen("From: "), TRUE);
5393 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5394 buf, compose->account->address) < 0);
5396 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5399 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5400 if (*entstr != '\0') {
5401 Xstrdup_a(str, entstr, return -1);
5404 compose_convert_header(compose, buf, sizeof(buf), str,
5405 strlen("Subject: "), FALSE);
5406 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5410 /* Resent-Message-ID */
5411 if (compose->account->gen_msgid) {
5412 gchar *addr = prefs_account_generate_msgid(compose->account);
5413 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5415 g_free(compose->msgid);
5416 compose->msgid = addr;
5418 compose->msgid = NULL;
5421 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5424 /* separator between header and body */
5425 err |= (fputs("\n", fp) == EOF);
5427 return (err ? -1:0);
5430 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5434 gchar buf[BUFFSIZE];
5436 gboolean skip = FALSE;
5437 gboolean err = FALSE;
5438 gchar *not_included[]={
5439 "Return-Path:", "Delivered-To:", "Received:",
5440 "Subject:", "X-UIDL:", "AF:",
5441 "NF:", "PS:", "SRH:",
5442 "SFN:", "DSR:", "MID:",
5443 "CFG:", "PT:", "S:",
5444 "RQ:", "SSV:", "NSV:",
5445 "SSH:", "R:", "MAID:",
5446 "NAID:", "RMID:", "FMID:",
5447 "SCF:", "RRCPT:", "NG:",
5448 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5449 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5450 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5451 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5452 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5455 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5456 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5460 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5462 for (i = 0; not_included[i] != NULL; i++) {
5463 if (g_ascii_strncasecmp(buf, not_included[i],
5464 strlen(not_included[i])) == 0) {
5471 if (fputs(buf, fdest) == -1)
5474 if (!prefs_common.redirect_keep_from) {
5475 if (g_ascii_strncasecmp(buf, "From:",
5476 strlen("From:")) == 0) {
5477 err |= (fputs(" (by way of ", fdest) == EOF);
5478 if (compose->account->name
5479 && *compose->account->name) {
5480 compose_convert_header
5481 (compose, buf, sizeof(buf),
5482 compose->account->name,
5485 err |= (fprintf(fdest, "%s <%s>",
5487 compose->account->address) < 0);
5489 err |= (fprintf(fdest, "%s",
5490 compose->account->address) < 0);
5491 err |= (fputs(")", fdest) == EOF);
5495 if (fputs("\n", fdest) == -1)
5502 if (compose_redirect_write_headers(compose, fdest))
5505 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5506 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5519 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5521 GtkTextBuffer *buffer;
5522 GtkTextIter start, end;
5523 gchar *chars, *tmp_enc_file, *content;
5525 const gchar *out_codeset;
5526 EncodingType encoding = ENC_UNKNOWN;
5527 MimeInfo *mimemsg, *mimetext;
5529 const gchar *src_codeset = CS_INTERNAL;
5530 gchar *from_addr = NULL;
5531 gchar *from_name = NULL;
5534 if (action == COMPOSE_WRITE_FOR_SEND) {
5535 attach_parts = TRUE;
5537 /* We're sending the message, generate a Message-ID
5539 if (compose->msgid == NULL &&
5540 compose->account->gen_msgid) {
5541 compose->msgid = prefs_account_generate_msgid(compose->account);
5545 /* create message MimeInfo */
5546 mimemsg = procmime_mimeinfo_new();
5547 mimemsg->type = MIMETYPE_MESSAGE;
5548 mimemsg->subtype = g_strdup("rfc822");
5549 mimemsg->content = MIMECONTENT_MEM;
5550 mimemsg->tmp = TRUE; /* must free content later */
5551 mimemsg->data.mem = compose_get_header(compose);
5553 /* Create text part MimeInfo */
5554 /* get all composed text */
5555 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5556 gtk_text_buffer_get_start_iter(buffer, &start);
5557 gtk_text_buffer_get_end_iter(buffer, &end);
5558 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5560 out_codeset = conv_get_charset_str(compose->out_encoding);
5562 if (!out_codeset && is_ascii_str(chars)) {
5563 out_codeset = CS_US_ASCII;
5564 } else if (prefs_common.outgoing_fallback_to_ascii &&
5565 is_ascii_str(chars)) {
5566 out_codeset = CS_US_ASCII;
5567 encoding = ENC_7BIT;
5571 gchar *test_conv_global_out = NULL;
5572 gchar *test_conv_reply = NULL;
5574 /* automatic mode. be automatic. */
5575 codeconv_set_strict(TRUE);
5577 out_codeset = conv_get_outgoing_charset_str();
5579 debug_print("trying to convert to %s\n", out_codeset);
5580 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5583 if (!test_conv_global_out && compose->orig_charset
5584 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5585 out_codeset = compose->orig_charset;
5586 debug_print("failure; trying to convert to %s\n", out_codeset);
5587 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5590 if (!test_conv_global_out && !test_conv_reply) {
5592 out_codeset = CS_INTERNAL;
5593 debug_print("failure; finally using %s\n", out_codeset);
5595 g_free(test_conv_global_out);
5596 g_free(test_conv_reply);
5597 codeconv_set_strict(FALSE);
5600 if (encoding == ENC_UNKNOWN) {
5601 if (prefs_common.encoding_method == CTE_BASE64)
5602 encoding = ENC_BASE64;
5603 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5604 encoding = ENC_QUOTED_PRINTABLE;
5605 else if (prefs_common.encoding_method == CTE_8BIT)
5606 encoding = ENC_8BIT;
5608 encoding = procmime_get_encoding_for_charset(out_codeset);
5611 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5612 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5614 if (action == COMPOSE_WRITE_FOR_SEND) {
5615 codeconv_set_strict(TRUE);
5616 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5617 codeconv_set_strict(FALSE);
5622 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5623 "to the specified %s charset.\n"
5624 "Send it as %s?"), out_codeset, src_codeset);
5625 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5626 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5627 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5630 if (aval != G_ALERTALTERNATE) {
5635 out_codeset = src_codeset;
5641 out_codeset = src_codeset;
5646 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5647 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5648 strstr(buf, "\nFrom ") != NULL) {
5649 encoding = ENC_QUOTED_PRINTABLE;
5653 mimetext = procmime_mimeinfo_new();
5654 mimetext->content = MIMECONTENT_MEM;
5655 mimetext->tmp = TRUE; /* must free content later */
5656 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5657 * and free the data, which we need later. */
5658 mimetext->data.mem = g_strdup(buf);
5659 mimetext->type = MIMETYPE_TEXT;
5660 mimetext->subtype = g_strdup("plain");
5661 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5662 g_strdup(out_codeset));
5664 /* protect trailing spaces when signing message */
5665 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5666 privacy_system_can_sign(compose->privacy_system)) {
5667 encoding = ENC_QUOTED_PRINTABLE;
5671 debug_print("main text: %Id bytes encoded as %s in %d\n",
5673 debug_print("main text: %zd bytes encoded as %s in %d\n",
5675 strlen(buf), out_codeset, encoding);
5677 /* check for line length limit */
5678 if (action == COMPOSE_WRITE_FOR_SEND &&
5679 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5680 check_line_length(buf, 1000, &line) < 0) {
5683 msg = g_strdup_printf
5684 (_("Line %d exceeds the line length limit (998 bytes).\n"
5685 "The contents of the message might be broken on the way to the delivery.\n"
5687 "Send it anyway?"), line + 1);
5688 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5690 if (aval != G_ALERTALTERNATE) {
5696 if (encoding != ENC_UNKNOWN)
5697 procmime_encode_content(mimetext, encoding);
5699 /* append attachment parts */
5700 if (compose_use_attach(compose) && attach_parts) {
5701 MimeInfo *mimempart;
5702 gchar *boundary = NULL;
5703 mimempart = procmime_mimeinfo_new();
5704 mimempart->content = MIMECONTENT_EMPTY;
5705 mimempart->type = MIMETYPE_MULTIPART;
5706 mimempart->subtype = g_strdup("mixed");
5710 boundary = generate_mime_boundary(NULL);
5711 } while (strstr(buf, boundary) != NULL);
5713 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5716 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5718 g_node_append(mimempart->node, mimetext->node);
5719 g_node_append(mimemsg->node, mimempart->node);
5721 if (compose_add_attachments(compose, mimempart) < 0)
5724 g_node_append(mimemsg->node, mimetext->node);
5728 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5729 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5730 /* extract name and address */
5731 if (strstr(spec, " <") && strstr(spec, ">")) {
5732 from_addr = g_strdup(strrchr(spec, '<')+1);
5733 *(strrchr(from_addr, '>')) = '\0';
5734 from_name = g_strdup(spec);
5735 *(strrchr(from_name, '<')) = '\0';
5742 /* sign message if sending */
5743 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5744 privacy_system_can_sign(compose->privacy_system))
5745 if (!privacy_sign(compose->privacy_system, mimemsg,
5746 compose->account, from_addr)) {
5754 if (compose->use_encryption) {
5755 if (compose->encdata != NULL &&
5756 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5758 /* First, write an unencrypted copy and save it to outbox, if
5759 * user wants that. */
5760 if (compose->account->save_encrypted_as_clear_text) {
5761 debug_print("saving sent message unencrypted...\n");
5762 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5766 /* fp now points to a file with headers written,
5767 * let's make a copy. */
5769 content = file_read_stream_to_str(fp);
5771 str_write_to_file(content, tmp_enc_file);
5774 /* Now write the unencrypted body. */
5775 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5776 procmime_write_mimeinfo(mimemsg, tmpfp);
5779 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5781 outbox = folder_get_default_outbox();
5783 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5784 claws_unlink(tmp_enc_file);
5786 g_warning("Can't open file '%s'", tmp_enc_file);
5789 g_warning("couldn't get tempfile");
5792 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5793 debug_print("Couldn't encrypt mime structure: %s.\n",
5794 privacy_get_error());
5795 alertpanel_error(_("Couldn't encrypt the email: %s"),
5796 privacy_get_error());
5801 procmime_write_mimeinfo(mimemsg, fp);
5803 procmime_mimeinfo_free_all(&mimemsg);
5808 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5810 GtkTextBuffer *buffer;
5811 GtkTextIter start, end;
5816 if ((fp = g_fopen(file, "wb")) == NULL) {
5817 FILE_OP_ERROR(file, "fopen");
5821 /* chmod for security */
5822 if (change_file_mode_rw(fp, file) < 0) {
5823 FILE_OP_ERROR(file, "chmod");
5824 g_warning("can't change file mode");
5827 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5828 gtk_text_buffer_get_start_iter(buffer, &start);
5829 gtk_text_buffer_get_end_iter(buffer, &end);
5830 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5832 chars = conv_codeset_strdup
5833 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5842 len = strlen(chars);
5843 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5844 FILE_OP_ERROR(file, "fwrite");
5853 if (fclose(fp) == EOF) {
5854 FILE_OP_ERROR(file, "fclose");
5861 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5864 MsgInfo *msginfo = compose->targetinfo;
5866 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5867 if (!msginfo) return -1;
5869 if (!force && MSG_IS_LOCKED(msginfo->flags))
5872 item = msginfo->folder;
5873 cm_return_val_if_fail(item != NULL, -1);
5875 if (procmsg_msg_exist(msginfo) &&
5876 (folder_has_parent_of_type(item, F_QUEUE) ||
5877 folder_has_parent_of_type(item, F_DRAFT)
5878 || msginfo == compose->autosaved_draft)) {
5879 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5880 g_warning("can't remove the old message");
5883 debug_print("removed reedit target %d\n", msginfo->msgnum);
5890 static void compose_remove_draft(Compose *compose)
5893 MsgInfo *msginfo = compose->targetinfo;
5894 drafts = account_get_special_folder(compose->account, F_DRAFT);
5896 if (procmsg_msg_exist(msginfo)) {
5897 folder_item_remove_msg(drafts, msginfo->msgnum);
5902 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5903 gboolean remove_reedit_target)
5905 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5908 static gboolean compose_warn_encryption(Compose *compose)
5910 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5911 AlertValue val = G_ALERTALTERNATE;
5913 if (warning == NULL)
5916 val = alertpanel_full(_("Encryption warning"), warning,
5917 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5918 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5919 if (val & G_ALERTDISABLE) {
5920 val &= ~G_ALERTDISABLE;
5921 if (val == G_ALERTALTERNATE)
5922 privacy_inhibit_encrypt_warning(compose->privacy_system,
5926 if (val == G_ALERTALTERNATE) {
5933 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5934 gchar **msgpath, gboolean check_subject,
5935 gboolean remove_reedit_target)
5942 PrefsAccount *mailac = NULL, *newsac = NULL;
5943 gboolean err = FALSE;
5945 debug_print("queueing message...\n");
5946 cm_return_val_if_fail(compose->account != NULL, -1);
5948 if (compose_check_entries(compose, check_subject) == FALSE) {
5949 if (compose->batch) {
5950 gtk_widget_show_all(compose->window);
5955 if (!compose->to_list && !compose->newsgroup_list) {
5956 g_warning("can't get recipient list.");
5960 if (compose->to_list) {
5961 if (compose->account->protocol != A_NNTP)
5962 mailac = compose->account;
5963 else if (cur_account && cur_account->protocol != A_NNTP)
5964 mailac = cur_account;
5965 else if (!(mailac = compose_current_mail_account())) {
5966 alertpanel_error(_("No account for sending mails available!"));
5971 if (compose->newsgroup_list) {
5972 if (compose->account->protocol == A_NNTP)
5973 newsac = compose->account;
5975 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5980 /* write queue header */
5981 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5982 G_DIR_SEPARATOR, compose, (guint) rand());
5983 debug_print("queuing to %s\n", tmp);
5984 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5985 FILE_OP_ERROR(tmp, "fopen");
5990 if (change_file_mode_rw(fp, tmp) < 0) {
5991 FILE_OP_ERROR(tmp, "chmod");
5992 g_warning("can't change file mode");
5995 /* queueing variables */
5996 err |= (fprintf(fp, "AF:\n") < 0);
5997 err |= (fprintf(fp, "NF:0\n") < 0);
5998 err |= (fprintf(fp, "PS:10\n") < 0);
5999 err |= (fprintf(fp, "SRH:1\n") < 0);
6000 err |= (fprintf(fp, "SFN:\n") < 0);
6001 err |= (fprintf(fp, "DSR:\n") < 0);
6003 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6005 err |= (fprintf(fp, "MID:\n") < 0);
6006 err |= (fprintf(fp, "CFG:\n") < 0);
6007 err |= (fprintf(fp, "PT:0\n") < 0);
6008 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6009 err |= (fprintf(fp, "RQ:\n") < 0);
6011 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6013 err |= (fprintf(fp, "SSV:\n") < 0);
6015 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6017 err |= (fprintf(fp, "NSV:\n") < 0);
6018 err |= (fprintf(fp, "SSH:\n") < 0);
6019 /* write recepient list */
6020 if (compose->to_list) {
6021 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6022 for (cur = compose->to_list->next; cur != NULL;
6024 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6025 err |= (fprintf(fp, "\n") < 0);
6027 /* write newsgroup list */
6028 if (compose->newsgroup_list) {
6029 err |= (fprintf(fp, "NG:") < 0);
6030 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6031 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6032 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6033 err |= (fprintf(fp, "\n") < 0);
6035 /* Sylpheed account IDs */
6037 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6039 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6042 if (compose->privacy_system != NULL) {
6043 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6044 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6045 if (compose->use_encryption) {
6046 if (!compose_warn_encryption(compose)) {
6052 if (mailac && mailac->encrypt_to_self) {
6053 GSList *tmp_list = g_slist_copy(compose->to_list);
6054 tmp_list = g_slist_append(tmp_list, compose->account->address);
6055 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6056 g_slist_free(tmp_list);
6058 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6060 if (compose->encdata != NULL) {
6061 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6062 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6063 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6064 compose->encdata) < 0);
6065 } /* else we finally dont want to encrypt */
6067 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6068 /* and if encdata was null, it means there's been a problem in
6071 g_warning("failed to write queue message");
6080 /* Save copy folder */
6081 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6082 gchar *savefolderid;
6084 savefolderid = compose_get_save_to(compose);
6085 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6086 g_free(savefolderid);
6088 /* Save copy folder */
6089 if (compose->return_receipt) {
6090 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6092 /* Message-ID of message replying to */
6093 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6094 gchar *folderid = NULL;
6096 if (compose->replyinfo->folder)
6097 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6098 if (folderid == NULL)
6099 folderid = g_strdup("NULL");
6101 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6104 /* Message-ID of message forwarding to */
6105 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6106 gchar *folderid = NULL;
6108 if (compose->fwdinfo->folder)
6109 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6110 if (folderid == NULL)
6111 folderid = g_strdup("NULL");
6113 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6117 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6118 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6120 /* end of headers */
6121 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6123 if (compose->redirect_filename != NULL) {
6124 if (compose_redirect_write_to_file(compose, fp) < 0) {
6132 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6136 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6140 g_warning("failed to write queue message");
6146 if (fclose(fp) == EOF) {
6147 FILE_OP_ERROR(tmp, "fclose");
6153 if (item && *item) {
6156 queue = account_get_special_folder(compose->account, F_QUEUE);
6159 g_warning("can't find queue folder");
6164 folder_item_scan(queue);
6165 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6166 g_warning("can't queue the message");
6172 if (msgpath == NULL) {
6178 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6179 compose_remove_reedit_target(compose, FALSE);
6182 if ((msgnum != NULL) && (item != NULL)) {
6190 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6193 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6196 gchar *type, *subtype;
6197 GtkTreeModel *model;
6200 model = gtk_tree_view_get_model(tree_view);
6202 if (!gtk_tree_model_get_iter_first(model, &iter))
6205 gtk_tree_model_get(model, &iter,
6209 if (!is_file_exist(ainfo->file)) {
6210 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6211 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6212 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6214 if (val == G_ALERTDEFAULT) {
6219 if (g_stat(ainfo->file, &statbuf) < 0)
6222 mimepart = procmime_mimeinfo_new();
6223 mimepart->content = MIMECONTENT_FILE;
6224 mimepart->data.filename = g_strdup(ainfo->file);
6225 mimepart->tmp = FALSE; /* or we destroy our attachment */
6226 mimepart->offset = 0;
6227 mimepart->length = statbuf.st_size;
6229 type = g_strdup(ainfo->content_type);
6231 if (!strchr(type, '/')) {
6233 type = g_strdup("application/octet-stream");
6236 subtype = strchr(type, '/') + 1;
6237 *(subtype - 1) = '\0';
6238 mimepart->type = procmime_get_media_type(type);
6239 mimepart->subtype = g_strdup(subtype);
6242 if (mimepart->type == MIMETYPE_MESSAGE &&
6243 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6244 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6245 } else if (mimepart->type == MIMETYPE_TEXT) {
6246 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6247 /* Text parts with no name come from multipart/alternative
6248 * forwards. Make sure the recipient won't look at the
6249 * original HTML part by mistake. */
6250 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6251 ainfo->name = g_strdup_printf(_("Original %s part"),
6255 g_hash_table_insert(mimepart->typeparameters,
6256 g_strdup("charset"), g_strdup(ainfo->charset));
6258 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6259 if (mimepart->type == MIMETYPE_APPLICATION &&
6260 !strcmp2(mimepart->subtype, "octet-stream"))
6261 g_hash_table_insert(mimepart->typeparameters,
6262 g_strdup("name"), g_strdup(ainfo->name));
6263 g_hash_table_insert(mimepart->dispositionparameters,
6264 g_strdup("filename"), g_strdup(ainfo->name));
6265 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6268 if (mimepart->type == MIMETYPE_MESSAGE
6269 || mimepart->type == MIMETYPE_MULTIPART)
6270 ainfo->encoding = ENC_BINARY;
6271 else if (compose->use_signing) {
6272 if (ainfo->encoding == ENC_7BIT)
6273 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6274 else if (ainfo->encoding == ENC_8BIT)
6275 ainfo->encoding = ENC_BASE64;
6280 procmime_encode_content(mimepart, ainfo->encoding);
6282 g_node_append(parent->node, mimepart->node);
6283 } while (gtk_tree_model_iter_next(model, &iter));
6288 static gchar *compose_quote_list_of_addresses(gchar *str)
6290 GSList *list = NULL, *item = NULL;
6291 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6293 list = address_list_append_with_comments(list, str);
6294 for (item = list; item != NULL; item = item->next) {
6295 gchar *spec = item->data;
6296 gchar *endofname = strstr(spec, " <");
6297 if (endofname != NULL) {
6300 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6301 qqname = escape_internal_quotes(qname, '"');
6303 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6304 gchar *addr = g_strdup(endofname);
6305 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6306 faddr = g_strconcat(name, addr, NULL);
6309 debug_print("new auto-quoted address: '%s'\n", faddr);
6313 result = g_strdup((faddr != NULL)? faddr: spec);
6315 result = g_strconcat(result,
6317 (faddr != NULL)? faddr: spec,
6320 if (faddr != NULL) {
6325 slist_free_strings_full(list);
6330 #define IS_IN_CUSTOM_HEADER(header) \
6331 (compose->account->add_customhdr && \
6332 custom_header_find(compose->account->customhdr_list, header) != NULL)
6334 static void compose_add_headerfield_from_headerlist(Compose *compose,
6336 const gchar *fieldname,
6337 const gchar *seperator)
6339 gchar *str, *fieldname_w_colon;
6340 gboolean add_field = FALSE;
6342 ComposeHeaderEntry *headerentry;
6343 const gchar *headerentryname;
6344 const gchar *trans_fieldname;
6347 if (IS_IN_CUSTOM_HEADER(fieldname))
6350 debug_print("Adding %s-fields\n", fieldname);
6352 fieldstr = g_string_sized_new(64);
6354 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6355 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6357 for (list = compose->header_list; list; list = list->next) {
6358 headerentry = ((ComposeHeaderEntry *)list->data);
6359 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6361 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6362 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6364 str = compose_quote_list_of_addresses(ustr);
6366 if (str != NULL && str[0] != '\0') {
6368 g_string_append(fieldstr, seperator);
6369 g_string_append(fieldstr, str);
6378 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6379 compose_convert_header
6380 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6381 strlen(fieldname) + 2, TRUE);
6382 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6386 g_free(fieldname_w_colon);
6387 g_string_free(fieldstr, TRUE);
6392 static gchar *compose_get_manual_headers_info(Compose *compose)
6394 GString *sh_header = g_string_new(" ");
6396 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6398 for (list = compose->header_list; list; list = list->next) {
6399 ComposeHeaderEntry *headerentry;
6402 gchar *headername_wcolon;
6403 const gchar *headername_trans;
6405 gboolean standard_header = FALSE;
6407 headerentry = ((ComposeHeaderEntry *)list->data);
6409 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6411 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6416 if (!strstr(tmp, ":")) {
6417 headername_wcolon = g_strconcat(tmp, ":", NULL);
6418 headername = g_strdup(tmp);
6420 headername_wcolon = g_strdup(tmp);
6421 headername = g_strdup(strtok(tmp, ":"));
6425 string = std_headers;
6426 while (*string != NULL) {
6427 headername_trans = prefs_common_translated_header_name(*string);
6428 if (!strcmp(headername_trans, headername_wcolon))
6429 standard_header = TRUE;
6432 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6433 g_string_append_printf(sh_header, "%s ", headername);
6435 g_free(headername_wcolon);
6437 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6438 return g_string_free(sh_header, FALSE);
6441 static gchar *compose_get_header(Compose *compose)
6443 gchar buf[BUFFSIZE];
6444 const gchar *entry_str;
6448 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6450 gchar *from_name = NULL, *from_address = NULL;
6453 cm_return_val_if_fail(compose->account != NULL, NULL);
6454 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6456 header = g_string_sized_new(64);
6459 if (prefs_common.hide_timezone)
6460 get_rfc822_date_hide_tz(buf, sizeof(buf));
6462 get_rfc822_date(buf, sizeof(buf));
6463 g_string_append_printf(header, "Date: %s\n", buf);
6467 if (compose->account->name && *compose->account->name) {
6469 QUOTE_IF_REQUIRED(buf, compose->account->name);
6470 tmp = g_strdup_printf("%s <%s>",
6471 buf, compose->account->address);
6473 tmp = g_strdup_printf("%s",
6474 compose->account->address);
6476 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6477 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6479 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6480 from_address = g_strdup(compose->account->address);
6482 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6483 /* extract name and address */
6484 if (strstr(spec, " <") && strstr(spec, ">")) {
6485 from_address = g_strdup(strrchr(spec, '<')+1);
6486 *(strrchr(from_address, '>')) = '\0';
6487 from_name = g_strdup(spec);
6488 *(strrchr(from_name, '<')) = '\0';
6491 from_address = g_strdup(spec);
6498 if (from_name && *from_name) {
6500 compose_convert_header
6501 (compose, buf, sizeof(buf), from_name,
6502 strlen("From: "), TRUE);
6503 QUOTE_IF_REQUIRED(name, buf);
6504 qname = escape_internal_quotes(name, '"');
6506 g_string_append_printf(header, "From: %s <%s>\n",
6507 qname, from_address);
6508 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6509 compose->return_receipt) {
6510 compose_convert_header(compose, buf, sizeof(buf), from_name,
6511 strlen("Disposition-Notification-To: "),
6513 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6518 g_string_append_printf(header, "From: %s\n", from_address);
6519 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6520 compose->return_receipt)
6521 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6525 g_free(from_address);
6528 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6531 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6534 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6538 * If this account is a NNTP account remove Bcc header from
6539 * message body since it otherwise will be publicly shown
6541 if (compose->account->protocol != A_NNTP)
6542 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6545 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6547 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6550 compose_convert_header(compose, buf, sizeof(buf), str,
6551 strlen("Subject: "), FALSE);
6552 g_string_append_printf(header, "Subject: %s\n", buf);
6558 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6559 g_string_append_printf(header, "Message-ID: <%s>\n",
6563 if (compose->remove_references == FALSE) {
6565 if (compose->inreplyto && compose->to_list)
6566 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6569 if (compose->references)
6570 g_string_append_printf(header, "References: %s\n", compose->references);
6574 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6577 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6580 if (compose->account->organization &&
6581 strlen(compose->account->organization) &&
6582 !IS_IN_CUSTOM_HEADER("Organization")) {
6583 compose_convert_header(compose, buf, sizeof(buf),
6584 compose->account->organization,
6585 strlen("Organization: "), FALSE);
6586 g_string_append_printf(header, "Organization: %s\n", buf);
6589 /* Program version and system info */
6590 if (compose->account->gen_xmailer &&
6591 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6592 !compose->newsgroup_list) {
6593 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6595 gtk_major_version, gtk_minor_version, gtk_micro_version,
6598 if (compose->account->gen_xmailer &&
6599 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6600 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6602 gtk_major_version, gtk_minor_version, gtk_micro_version,
6606 /* custom headers */
6607 if (compose->account->add_customhdr) {
6610 for (cur = compose->account->customhdr_list; cur != NULL;
6612 CustomHeader *chdr = (CustomHeader *)cur->data;
6614 if (custom_header_is_allowed(chdr->name)
6615 && chdr->value != NULL
6616 && *(chdr->value) != '\0') {
6617 compose_convert_header
6618 (compose, buf, sizeof(buf),
6620 strlen(chdr->name) + 2, FALSE);
6621 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6626 /* Automatic Faces and X-Faces */
6627 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6628 g_string_append_printf(header, "X-Face: %s\n", buf);
6630 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6631 g_string_append_printf(header, "X-Face: %s\n", buf);
6633 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6634 g_string_append_printf(header, "Face: %s\n", buf);
6636 else if (get_default_face (buf, sizeof(buf)) == 0) {
6637 g_string_append_printf(header, "Face: %s\n", buf);
6641 switch (compose->priority) {
6642 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6643 "X-Priority: 1 (Highest)\n");
6645 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6646 "X-Priority: 2 (High)\n");
6648 case PRIORITY_NORMAL: break;
6649 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6650 "X-Priority: 4 (Low)\n");
6652 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6653 "X-Priority: 5 (Lowest)\n");
6655 default: debug_print("compose: priority unknown : %d\n",
6659 /* get special headers */
6660 for (list = compose->header_list; list; list = list->next) {
6661 ComposeHeaderEntry *headerentry;
6664 gchar *headername_wcolon;
6665 const gchar *headername_trans;
6668 gboolean standard_header = FALSE;
6670 headerentry = ((ComposeHeaderEntry *)list->data);
6672 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6674 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6679 if (!strstr(tmp, ":")) {
6680 headername_wcolon = g_strconcat(tmp, ":", NULL);
6681 headername = g_strdup(tmp);
6683 headername_wcolon = g_strdup(tmp);
6684 headername = g_strdup(strtok(tmp, ":"));
6688 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6689 Xstrdup_a(headervalue, entry_str, return NULL);
6690 subst_char(headervalue, '\r', ' ');
6691 subst_char(headervalue, '\n', ' ');
6692 string = std_headers;
6693 while (*string != NULL) {
6694 headername_trans = prefs_common_translated_header_name(*string);
6695 if (!strcmp(headername_trans, headername_wcolon))
6696 standard_header = TRUE;
6699 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6700 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6703 g_free(headername_wcolon);
6707 g_string_free(header, FALSE);
6712 #undef IS_IN_CUSTOM_HEADER
6714 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6715 gint header_len, gboolean addr_field)
6717 gchar *tmpstr = NULL;
6718 const gchar *out_codeset = NULL;
6720 cm_return_if_fail(src != NULL);
6721 cm_return_if_fail(dest != NULL);
6723 if (len < 1) return;
6725 tmpstr = g_strdup(src);
6727 subst_char(tmpstr, '\n', ' ');
6728 subst_char(tmpstr, '\r', ' ');
6731 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6732 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6733 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6738 codeconv_set_strict(TRUE);
6739 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6740 conv_get_charset_str(compose->out_encoding));
6741 codeconv_set_strict(FALSE);
6743 if (!dest || *dest == '\0') {
6744 gchar *test_conv_global_out = NULL;
6745 gchar *test_conv_reply = NULL;
6747 /* automatic mode. be automatic. */
6748 codeconv_set_strict(TRUE);
6750 out_codeset = conv_get_outgoing_charset_str();
6752 debug_print("trying to convert to %s\n", out_codeset);
6753 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6756 if (!test_conv_global_out && compose->orig_charset
6757 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6758 out_codeset = compose->orig_charset;
6759 debug_print("failure; trying to convert to %s\n", out_codeset);
6760 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6763 if (!test_conv_global_out && !test_conv_reply) {
6765 out_codeset = CS_INTERNAL;
6766 debug_print("finally using %s\n", out_codeset);
6768 g_free(test_conv_global_out);
6769 g_free(test_conv_reply);
6770 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6772 codeconv_set_strict(FALSE);
6777 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6781 cm_return_if_fail(user_data != NULL);
6783 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6784 g_strstrip(address);
6785 if (*address != '\0') {
6786 gchar *name = procheader_get_fromname(address);
6787 extract_address(address);
6788 #ifndef USE_ALT_ADDRBOOK
6789 addressbook_add_contact(name, address, NULL, NULL);
6791 debug_print("%s: %s\n", name, address);
6792 if (addressadd_selection(name, address, NULL, NULL)) {
6793 debug_print( "addressbook_add_contact - added\n" );
6800 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6802 GtkWidget *menuitem;
6805 cm_return_if_fail(menu != NULL);
6806 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6808 menuitem = gtk_separator_menu_item_new();
6809 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6810 gtk_widget_show(menuitem);
6812 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6813 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6815 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6816 g_strstrip(address);
6817 if (*address == '\0') {
6818 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6821 g_signal_connect(G_OBJECT(menuitem), "activate",
6822 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6823 gtk_widget_show(menuitem);
6826 void compose_add_extra_header(gchar *header, GtkListStore *model)
6829 if (strcmp(header, "")) {
6830 COMBOBOX_ADD(model, header, COMPOSE_TO);
6834 void compose_add_extra_header_entries(GtkListStore *model)
6838 gchar buf[BUFFSIZE];
6841 if (extra_headers == NULL) {
6842 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6843 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6844 debug_print("extra headers file not found\n");
6845 goto extra_headers_done;
6847 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6848 lastc = strlen(buf) - 1; /* remove trailing control chars */
6849 while (lastc >= 0 && buf[lastc] != ':')
6850 buf[lastc--] = '\0';
6851 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6852 buf[lastc] = '\0'; /* remove trailing : for comparison */
6853 if (custom_header_is_allowed(buf)) {
6855 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6858 g_message("disallowed extra header line: %s\n", buf);
6862 g_message("invalid extra header line: %s\n", buf);
6868 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6869 extra_headers = g_slist_reverse(extra_headers);
6871 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6874 static void compose_create_header_entry(Compose *compose)
6876 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6883 const gchar *header = NULL;
6884 ComposeHeaderEntry *headerentry;
6885 gboolean standard_header = FALSE;
6886 GtkListStore *model;
6889 headerentry = g_new0(ComposeHeaderEntry, 1);
6891 /* Combo box model */
6892 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6893 #if !GTK_CHECK_VERSION(2, 24, 0)
6894 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6896 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6898 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6900 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6902 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6903 COMPOSE_NEWSGROUPS);
6904 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6906 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6907 COMPOSE_FOLLOWUPTO);
6908 compose_add_extra_header_entries(model);
6911 #if GTK_CHECK_VERSION(2, 24, 0)
6912 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6913 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6914 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6915 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6916 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6918 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6919 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6920 G_CALLBACK(compose_grab_focus_cb), compose);
6921 gtk_widget_show(combo);
6923 /* Putting only the combobox child into focus chain of its parent causes
6924 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6925 * This eliminates need to pres Tab twice in order to really get from the
6926 * combobox to next widget. */
6928 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6929 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6932 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6933 compose->header_nextrow, compose->header_nextrow+1,
6934 GTK_SHRINK, GTK_FILL, 0, 0);
6935 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6936 const gchar *last_header_entry = gtk_entry_get_text(
6937 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6939 while (*string != NULL) {
6940 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6941 standard_header = TRUE;
6944 if (standard_header)
6945 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6947 if (!compose->header_last || !standard_header) {
6948 switch(compose->account->protocol) {
6950 header = prefs_common_translated_header_name("Newsgroups:");
6953 header = prefs_common_translated_header_name("To:");
6958 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6960 gtk_editable_set_editable(
6961 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6962 prefs_common.type_any_header);
6964 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6965 G_CALLBACK(compose_grab_focus_cb), compose);
6967 /* Entry field with cleanup button */
6968 button = gtk_button_new();
6969 gtk_button_set_image(GTK_BUTTON(button),
6970 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6971 gtk_widget_show(button);
6972 CLAWS_SET_TIP(button,
6973 _("Delete entry contents"));
6974 entry = gtk_entry_new();
6975 gtk_widget_show(entry);
6976 CLAWS_SET_TIP(entry,
6977 _("Use <tab> to autocomplete from addressbook"));
6978 hbox = gtk_hbox_new (FALSE, 0);
6979 gtk_widget_show(hbox);
6980 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6981 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6982 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6983 compose->header_nextrow, compose->header_nextrow+1,
6984 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6986 g_signal_connect(G_OBJECT(entry), "key-press-event",
6987 G_CALLBACK(compose_headerentry_key_press_event_cb),
6989 g_signal_connect(G_OBJECT(entry), "changed",
6990 G_CALLBACK(compose_headerentry_changed_cb),
6992 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6993 G_CALLBACK(compose_grab_focus_cb), compose);
6995 g_signal_connect(G_OBJECT(button), "clicked",
6996 G_CALLBACK(compose_headerentry_button_clicked_cb),
7000 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7001 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7002 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7003 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7004 G_CALLBACK(compose_header_drag_received_cb),
7006 g_signal_connect(G_OBJECT(entry), "drag-drop",
7007 G_CALLBACK(compose_drag_drop),
7009 g_signal_connect(G_OBJECT(entry), "populate-popup",
7010 G_CALLBACK(compose_entry_popup_extend),
7013 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7015 headerentry->compose = compose;
7016 headerentry->combo = combo;
7017 headerentry->entry = entry;
7018 headerentry->button = button;
7019 headerentry->hbox = hbox;
7020 headerentry->headernum = compose->header_nextrow;
7021 headerentry->type = PREF_NONE;
7023 compose->header_nextrow++;
7024 compose->header_last = headerentry;
7025 compose->header_list =
7026 g_slist_append(compose->header_list,
7030 static void compose_add_header_entry(Compose *compose, const gchar *header,
7031 gchar *text, ComposePrefType pref_type)
7033 ComposeHeaderEntry *last_header = compose->header_last;
7034 gchar *tmp = g_strdup(text), *email;
7035 gboolean replyto_hdr;
7037 replyto_hdr = (!strcasecmp(header,
7038 prefs_common_translated_header_name("Reply-To:")) ||
7040 prefs_common_translated_header_name("Followup-To:")) ||
7042 prefs_common_translated_header_name("In-Reply-To:")));
7044 extract_address(tmp);
7045 email = g_utf8_strdown(tmp, -1);
7047 if (replyto_hdr == FALSE &&
7048 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7050 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7051 header, text, (gint) pref_type);
7057 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7058 gtk_entry_set_text(GTK_ENTRY(
7059 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7061 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7062 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7063 last_header->type = pref_type;
7065 if (replyto_hdr == FALSE)
7066 g_hash_table_insert(compose->email_hashtable, email,
7067 GUINT_TO_POINTER(1));
7074 static void compose_destroy_headerentry(Compose *compose,
7075 ComposeHeaderEntry *headerentry)
7077 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7080 extract_address(text);
7081 email = g_utf8_strdown(text, -1);
7082 g_hash_table_remove(compose->email_hashtable, email);
7086 gtk_widget_destroy(headerentry->combo);
7087 gtk_widget_destroy(headerentry->entry);
7088 gtk_widget_destroy(headerentry->button);
7089 gtk_widget_destroy(headerentry->hbox);
7090 g_free(headerentry);
7093 static void compose_remove_header_entries(Compose *compose)
7096 for (list = compose->header_list; list; list = list->next)
7097 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7099 compose->header_last = NULL;
7100 g_slist_free(compose->header_list);
7101 compose->header_list = NULL;
7102 compose->header_nextrow = 1;
7103 compose_create_header_entry(compose);
7106 static GtkWidget *compose_create_header(Compose *compose)
7108 GtkWidget *from_optmenu_hbox;
7109 GtkWidget *header_table_main;
7110 GtkWidget *header_scrolledwin;
7111 GtkWidget *header_table;
7113 /* parent with account selection and from header */
7114 header_table_main = gtk_table_new(2, 2, FALSE);
7115 gtk_widget_show(header_table_main);
7116 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7118 from_optmenu_hbox = compose_account_option_menu_create(compose);
7119 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7120 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7122 /* child with header labels and entries */
7123 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7124 gtk_widget_show(header_scrolledwin);
7125 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7127 header_table = gtk_table_new(2, 2, FALSE);
7128 gtk_widget_show(header_table);
7129 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7130 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7131 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7132 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7133 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7135 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7136 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7138 compose->header_table = header_table;
7139 compose->header_list = NULL;
7140 compose->header_nextrow = 0;
7142 compose_create_header_entry(compose);
7144 compose->table = NULL;
7146 return header_table_main;
7149 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7151 Compose *compose = (Compose *)data;
7152 GdkEventButton event;
7155 event.time = gtk_get_current_event_time();
7157 return attach_button_pressed(compose->attach_clist, &event, compose);
7160 static GtkWidget *compose_create_attach(Compose *compose)
7162 GtkWidget *attach_scrwin;
7163 GtkWidget *attach_clist;
7165 GtkListStore *store;
7166 GtkCellRenderer *renderer;
7167 GtkTreeViewColumn *column;
7168 GtkTreeSelection *selection;
7170 /* attachment list */
7171 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7172 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7173 GTK_POLICY_AUTOMATIC,
7174 GTK_POLICY_AUTOMATIC);
7175 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7177 store = gtk_list_store_new(N_ATTACH_COLS,
7183 G_TYPE_AUTO_POINTER,
7185 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7186 (GTK_TREE_MODEL(store)));
7187 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7188 g_object_unref(store);
7190 renderer = gtk_cell_renderer_text_new();
7191 column = gtk_tree_view_column_new_with_attributes
7192 (_("Mime type"), renderer, "text",
7193 COL_MIMETYPE, NULL);
7194 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7196 renderer = gtk_cell_renderer_text_new();
7197 column = gtk_tree_view_column_new_with_attributes
7198 (_("Size"), renderer, "text",
7200 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7202 renderer = gtk_cell_renderer_text_new();
7203 column = gtk_tree_view_column_new_with_attributes
7204 (_("Name"), renderer, "text",
7206 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7208 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7209 prefs_common.use_stripes_everywhere);
7210 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7211 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7213 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7214 G_CALLBACK(attach_selected), compose);
7215 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7216 G_CALLBACK(attach_button_pressed), compose);
7217 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7218 G_CALLBACK(popup_attach_button_pressed), compose);
7219 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7220 G_CALLBACK(attach_key_pressed), compose);
7223 gtk_drag_dest_set(attach_clist,
7224 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7225 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7226 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7227 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7228 G_CALLBACK(compose_attach_drag_received_cb),
7230 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7231 G_CALLBACK(compose_drag_drop),
7234 compose->attach_scrwin = attach_scrwin;
7235 compose->attach_clist = attach_clist;
7237 return attach_scrwin;
7240 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7241 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7243 static GtkWidget *compose_create_others(Compose *compose)
7246 GtkWidget *savemsg_checkbtn;
7247 GtkWidget *savemsg_combo;
7248 GtkWidget *savemsg_select;
7251 gchar *folderidentifier;
7253 /* Table for settings */
7254 table = gtk_table_new(3, 1, FALSE);
7255 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7256 gtk_widget_show(table);
7257 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7260 /* Save Message to folder */
7261 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7262 gtk_widget_show(savemsg_checkbtn);
7263 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7264 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7265 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7267 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7268 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7270 #if !GTK_CHECK_VERSION(2, 24, 0)
7271 savemsg_combo = gtk_combo_box_entry_new_text();
7273 savemsg_combo = gtk_combo_box_text_new_with_entry();
7275 compose->savemsg_checkbtn = savemsg_checkbtn;
7276 compose->savemsg_combo = savemsg_combo;
7277 gtk_widget_show(savemsg_combo);
7279 if (prefs_common.compose_save_to_history)
7280 #if !GTK_CHECK_VERSION(2, 24, 0)
7281 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7282 prefs_common.compose_save_to_history);
7284 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7285 prefs_common.compose_save_to_history);
7287 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7288 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7289 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7290 G_CALLBACK(compose_grab_focus_cb), compose);
7291 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7292 folderidentifier = folder_item_get_identifier(account_get_special_folder
7293 (compose->account, F_OUTBOX));
7294 compose_set_save_to(compose, folderidentifier);
7295 g_free(folderidentifier);
7298 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7299 gtk_widget_show(savemsg_select);
7300 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7301 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7302 G_CALLBACK(compose_savemsg_select_cb),
7308 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7310 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7311 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7314 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7319 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7322 path = folder_item_get_identifier(dest);
7324 compose_set_save_to(compose, path);
7328 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7329 GdkAtom clip, GtkTextIter *insert_place);
7332 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7336 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7338 if (event->button == 3) {
7340 GtkTextIter sel_start, sel_end;
7341 gboolean stuff_selected;
7343 /* move the cursor to allow GtkAspell to check the word
7344 * under the mouse */
7345 if (event->x && event->y) {
7346 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7347 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7349 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7352 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7353 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7356 stuff_selected = gtk_text_buffer_get_selection_bounds(
7358 &sel_start, &sel_end);
7360 gtk_text_buffer_place_cursor (buffer, &iter);
7361 /* reselect stuff */
7363 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7364 gtk_text_buffer_select_range(buffer,
7365 &sel_start, &sel_end);
7367 return FALSE; /* pass the event so that the right-click goes through */
7370 if (event->button == 2) {
7375 /* get the middle-click position to paste at the correct place */
7376 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7377 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7379 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7382 entry_paste_clipboard(compose, text,
7383 prefs_common.linewrap_pastes,
7384 GDK_SELECTION_PRIMARY, &iter);
7392 static void compose_spell_menu_changed(void *data)
7394 Compose *compose = (Compose *)data;
7396 GtkWidget *menuitem;
7397 GtkWidget *parent_item;
7398 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7401 if (compose->gtkaspell == NULL)
7404 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7405 "/Menu/Spelling/Options");
7407 /* setting the submenu removes /Spelling/Options from the factory
7408 * so we need to save it */
7410 if (parent_item == NULL) {
7411 parent_item = compose->aspell_options_menu;
7412 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7414 compose->aspell_options_menu = parent_item;
7416 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7418 spell_menu = g_slist_reverse(spell_menu);
7419 for (items = spell_menu;
7420 items; items = items->next) {
7421 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7422 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7423 gtk_widget_show(GTK_WIDGET(menuitem));
7425 g_slist_free(spell_menu);
7427 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7428 gtk_widget_show(parent_item);
7431 static void compose_dict_changed(void *data)
7433 Compose *compose = (Compose *) data;
7435 if(!compose->gtkaspell)
7437 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7440 gtkaspell_highlight_all(compose->gtkaspell);
7441 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7445 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7447 Compose *compose = (Compose *)data;
7448 GdkEventButton event;
7451 event.time = gtk_get_current_event_time();
7455 return text_clicked(compose->text, &event, compose);
7458 static gboolean compose_force_window_origin = TRUE;
7459 static Compose *compose_create(PrefsAccount *account,
7468 GtkWidget *handlebox;
7470 GtkWidget *notebook;
7472 GtkWidget *attach_hbox;
7473 GtkWidget *attach_lab1;
7474 GtkWidget *attach_lab2;
7479 GtkWidget *subject_hbox;
7480 GtkWidget *subject_frame;
7481 GtkWidget *subject_entry;
7485 GtkWidget *edit_vbox;
7486 GtkWidget *ruler_hbox;
7488 GtkWidget *scrolledwin;
7490 GtkTextBuffer *buffer;
7491 GtkClipboard *clipboard;
7493 UndoMain *undostruct;
7495 GtkWidget *popupmenu;
7496 GtkWidget *tmpl_menu;
7497 GtkActionGroup *action_group = NULL;
7500 GtkAspell * gtkaspell = NULL;
7503 static GdkGeometry geometry;
7505 cm_return_val_if_fail(account != NULL, NULL);
7507 debug_print("Creating compose window...\n");
7508 compose = g_new0(Compose, 1);
7510 compose->batch = batch;
7511 compose->account = account;
7512 compose->folder = folder;
7514 compose->mutex = cm_mutex_new();
7515 compose->set_cursor_pos = -1;
7517 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7519 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7520 gtk_widget_set_size_request(window, prefs_common.compose_width,
7521 prefs_common.compose_height);
7523 if (!geometry.max_width) {
7524 geometry.max_width = gdk_screen_width();
7525 geometry.max_height = gdk_screen_height();
7528 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7529 &geometry, GDK_HINT_MAX_SIZE);
7530 if (!geometry.min_width) {
7531 geometry.min_width = 600;
7532 geometry.min_height = 440;
7534 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7535 &geometry, GDK_HINT_MIN_SIZE);
7537 #ifndef GENERIC_UMPC
7538 if (compose_force_window_origin)
7539 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7540 prefs_common.compose_y);
7542 g_signal_connect(G_OBJECT(window), "delete_event",
7543 G_CALLBACK(compose_delete_cb), compose);
7544 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7545 gtk_widget_realize(window);
7547 gtkut_widget_set_composer_icon(window);
7549 vbox = gtk_vbox_new(FALSE, 0);
7550 gtk_container_add(GTK_CONTAINER(window), vbox);
7552 compose->ui_manager = gtk_ui_manager_new();
7553 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7554 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7555 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7556 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7557 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7558 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7559 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7560 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7561 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7562 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7654 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7681 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)
7682 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)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7685 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7688 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)
7689 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)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7693 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7694 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)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7698 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)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7704 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)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7706 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7708 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7711 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)
7712 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)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7716 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7725 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)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7743 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7744 gtk_widget_show_all(menubar);
7746 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7747 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7749 if (prefs_common.toolbar_detachable) {
7750 handlebox = gtk_handle_box_new();
7752 handlebox = gtk_hbox_new(FALSE, 0);
7754 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7756 gtk_widget_realize(handlebox);
7757 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7760 vbox2 = gtk_vbox_new(FALSE, 2);
7761 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7762 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7765 notebook = gtk_notebook_new();
7766 gtk_widget_show(notebook);
7768 /* header labels and entries */
7769 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7770 compose_create_header(compose),
7771 gtk_label_new_with_mnemonic(_("Hea_der")));
7772 /* attachment list */
7773 attach_hbox = gtk_hbox_new(FALSE, 0);
7774 gtk_widget_show(attach_hbox);
7776 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7777 gtk_widget_show(attach_lab1);
7778 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7780 attach_lab2 = gtk_label_new("");
7781 gtk_widget_show(attach_lab2);
7782 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7784 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7785 compose_create_attach(compose),
7788 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7789 compose_create_others(compose),
7790 gtk_label_new_with_mnemonic(_("Othe_rs")));
7793 subject_hbox = gtk_hbox_new(FALSE, 0);
7794 gtk_widget_show(subject_hbox);
7796 subject_frame = gtk_frame_new(NULL);
7797 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7798 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7799 gtk_widget_show(subject_frame);
7801 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7802 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7803 gtk_widget_show(subject);
7805 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7806 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7807 gtk_widget_show(label);
7810 subject_entry = claws_spell_entry_new();
7812 subject_entry = gtk_entry_new();
7814 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7815 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7816 G_CALLBACK(compose_grab_focus_cb), compose);
7817 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7818 gtk_widget_show(subject_entry);
7819 compose->subject_entry = subject_entry;
7820 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7822 edit_vbox = gtk_vbox_new(FALSE, 0);
7824 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7827 ruler_hbox = gtk_hbox_new(FALSE, 0);
7828 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7830 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7831 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7832 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7836 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7837 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7838 GTK_POLICY_AUTOMATIC,
7839 GTK_POLICY_AUTOMATIC);
7840 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7842 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7844 text = gtk_text_view_new();
7845 if (prefs_common.show_compose_margin) {
7846 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7847 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7849 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7850 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7851 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7852 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7853 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7855 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7856 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7857 G_CALLBACK(compose_edit_size_alloc),
7859 g_signal_connect(G_OBJECT(buffer), "changed",
7860 G_CALLBACK(compose_changed_cb), compose);
7861 g_signal_connect(G_OBJECT(text), "grab_focus",
7862 G_CALLBACK(compose_grab_focus_cb), compose);
7863 g_signal_connect(G_OBJECT(buffer), "insert_text",
7864 G_CALLBACK(text_inserted), compose);
7865 g_signal_connect(G_OBJECT(text), "button_press_event",
7866 G_CALLBACK(text_clicked), compose);
7867 g_signal_connect(G_OBJECT(text), "popup-menu",
7868 G_CALLBACK(compose_popup_menu), compose);
7869 g_signal_connect(G_OBJECT(subject_entry), "changed",
7870 G_CALLBACK(compose_changed_cb), compose);
7871 g_signal_connect(G_OBJECT(subject_entry), "activate",
7872 G_CALLBACK(compose_subject_entry_activated), compose);
7875 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7876 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7877 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7878 g_signal_connect(G_OBJECT(text), "drag_data_received",
7879 G_CALLBACK(compose_insert_drag_received_cb),
7881 g_signal_connect(G_OBJECT(text), "drag-drop",
7882 G_CALLBACK(compose_drag_drop),
7884 g_signal_connect(G_OBJECT(text), "key-press-event",
7885 G_CALLBACK(completion_set_focus_to_subject),
7887 gtk_widget_show_all(vbox);
7889 /* pane between attach clist and text */
7890 paned = gtk_vpaned_new();
7891 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7892 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7893 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7894 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7895 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7896 G_CALLBACK(compose_notebook_size_alloc), paned);
7898 gtk_widget_show_all(paned);
7901 if (prefs_common.textfont) {
7902 PangoFontDescription *font_desc;
7904 font_desc = pango_font_description_from_string
7905 (prefs_common.textfont);
7907 gtk_widget_modify_font(text, font_desc);
7908 pango_font_description_free(font_desc);
7912 gtk_action_group_add_actions(action_group, compose_popup_entries,
7913 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7914 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7915 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7916 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7917 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7921 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7923 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7924 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7925 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7927 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7929 undostruct = undo_init(text);
7930 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7933 address_completion_start(window);
7935 compose->window = window;
7936 compose->vbox = vbox;
7937 compose->menubar = menubar;
7938 compose->handlebox = handlebox;
7940 compose->vbox2 = vbox2;
7942 compose->paned = paned;
7944 compose->attach_label = attach_lab2;
7946 compose->notebook = notebook;
7947 compose->edit_vbox = edit_vbox;
7948 compose->ruler_hbox = ruler_hbox;
7949 compose->ruler = ruler;
7950 compose->scrolledwin = scrolledwin;
7951 compose->text = text;
7953 compose->focused_editable = NULL;
7955 compose->popupmenu = popupmenu;
7957 compose->tmpl_menu = tmpl_menu;
7959 compose->mode = mode;
7960 compose->rmode = mode;
7962 compose->targetinfo = NULL;
7963 compose->replyinfo = NULL;
7964 compose->fwdinfo = NULL;
7966 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7967 g_str_equal, (GDestroyNotify) g_free, NULL);
7969 compose->replyto = NULL;
7971 compose->bcc = NULL;
7972 compose->followup_to = NULL;
7974 compose->ml_post = NULL;
7976 compose->inreplyto = NULL;
7977 compose->references = NULL;
7978 compose->msgid = NULL;
7979 compose->boundary = NULL;
7981 compose->autowrap = prefs_common.autowrap;
7982 compose->autoindent = prefs_common.auto_indent;
7983 compose->use_signing = FALSE;
7984 compose->use_encryption = FALSE;
7985 compose->privacy_system = NULL;
7986 compose->encdata = NULL;
7988 compose->modified = FALSE;
7990 compose->return_receipt = FALSE;
7992 compose->to_list = NULL;
7993 compose->newsgroup_list = NULL;
7995 compose->undostruct = undostruct;
7997 compose->sig_str = NULL;
7999 compose->exteditor_file = NULL;
8000 compose->exteditor_pid = -1;
8001 compose->exteditor_tag = -1;
8002 compose->exteditor_socket = NULL;
8003 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8005 compose->folder_update_callback_id =
8006 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8007 compose_update_folder_hook,
8008 (gpointer) compose);
8011 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8012 if (mode != COMPOSE_REDIRECT) {
8013 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8014 strcmp(prefs_common.dictionary, "")) {
8015 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8016 prefs_common.alt_dictionary,
8017 conv_get_locale_charset_str(),
8018 prefs_common.misspelled_col,
8019 prefs_common.check_while_typing,
8020 prefs_common.recheck_when_changing_dict,
8021 prefs_common.use_alternate,
8022 prefs_common.use_both_dicts,
8023 GTK_TEXT_VIEW(text),
8024 GTK_WINDOW(compose->window),
8025 compose_dict_changed,
8026 compose_spell_menu_changed,
8029 alertpanel_error(_("Spell checker could not "
8031 gtkaspell_checkers_strerror());
8032 gtkaspell_checkers_reset_error();
8034 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8038 compose->gtkaspell = gtkaspell;
8039 compose_spell_menu_changed(compose);
8040 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8043 compose_select_account(compose, account, TRUE);
8045 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8046 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8048 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8049 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8051 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8052 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8054 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8055 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8057 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8058 if (account->protocol != A_NNTP)
8059 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8060 prefs_common_translated_header_name("To:"));
8062 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8063 prefs_common_translated_header_name("Newsgroups:"));
8065 #ifndef USE_ALT_ADDRBOOK
8066 addressbook_set_target_compose(compose);
8068 if (mode != COMPOSE_REDIRECT)
8069 compose_set_template_menu(compose);
8071 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8074 compose_list = g_list_append(compose_list, compose);
8076 if (!prefs_common.show_ruler)
8077 gtk_widget_hide(ruler_hbox);
8079 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8082 compose->priority = PRIORITY_NORMAL;
8083 compose_update_priority_menu_item(compose);
8085 compose_set_out_encoding(compose);
8088 compose_update_actions_menu(compose);
8090 /* Privacy Systems menu */
8091 compose_update_privacy_systems_menu(compose);
8093 activate_privacy_system(compose, account, TRUE);
8094 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8096 gtk_widget_realize(window);
8098 gtk_widget_show(window);
8101 gtkut_convert_int_to_gdk_color(prefs_common.default_to_bgcolor,
8102 &default_to_bgcolor);
8103 gtkut_convert_int_to_gdk_color(prefs_common.default_to_color,
8109 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8114 GtkWidget *optmenubox;
8115 GtkWidget *fromlabel;
8118 GtkWidget *from_name = NULL;
8120 gint num = 0, def_menu = 0;
8122 accounts = account_get_list();
8123 cm_return_val_if_fail(accounts != NULL, NULL);
8125 optmenubox = gtk_event_box_new();
8126 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8127 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8129 hbox = gtk_hbox_new(FALSE, 4);
8130 from_name = gtk_entry_new();
8132 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8133 G_CALLBACK(compose_grab_focus_cb), compose);
8134 g_signal_connect_after(G_OBJECT(from_name), "activate",
8135 G_CALLBACK(from_name_activate_cb), optmenu);
8137 for (; accounts != NULL; accounts = accounts->next, num++) {
8138 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8139 gchar *name, *from = NULL;
8141 if (ac == compose->account) def_menu = num;
8143 name = g_markup_printf_escaped("<i>%s</i>",
8146 if (ac == compose->account) {
8147 if (ac->name && *ac->name) {
8149 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8150 from = g_strdup_printf("%s <%s>",
8152 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8154 from = g_strdup_printf("%s",
8156 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8159 COMBOBOX_ADD(menu, name, ac->account_id);
8164 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8166 g_signal_connect(G_OBJECT(optmenu), "changed",
8167 G_CALLBACK(account_activated),
8169 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8170 G_CALLBACK(compose_entry_popup_extend),
8173 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8174 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8176 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8177 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8178 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8180 /* Putting only the GtkEntry into focus chain of parent hbox causes
8181 * the account selector combobox next to it to be unreachable when
8182 * navigating widgets in GtkTable with up/down arrow keys.
8183 * Note: gtk_widget_set_can_focus() was not enough. */
8185 l = g_list_prepend(l, from_name);
8186 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8189 CLAWS_SET_TIP(optmenubox,
8190 _("Account to use for this email"));
8191 CLAWS_SET_TIP(from_name,
8192 _("Sender address to be used"));
8194 compose->account_combo = optmenu;
8195 compose->from_name = from_name;
8200 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8202 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8203 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8204 Compose *compose = (Compose *) data;
8206 compose->priority = value;
8210 static void compose_reply_change_mode(Compose *compose,
8213 gboolean was_modified = compose->modified;
8215 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8217 cm_return_if_fail(compose->replyinfo != NULL);
8219 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8221 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8223 if (action == COMPOSE_REPLY_TO_ALL)
8225 if (action == COMPOSE_REPLY_TO_SENDER)
8227 if (action == COMPOSE_REPLY_TO_LIST)
8230 compose_remove_header_entries(compose);
8231 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8232 if (compose->account->set_autocc && compose->account->auto_cc)
8233 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8235 if (compose->account->set_autobcc && compose->account->auto_bcc)
8236 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8238 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8239 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8240 compose_show_first_last_header(compose, TRUE);
8241 compose->modified = was_modified;
8242 compose_set_title(compose);
8245 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8247 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8248 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8249 Compose *compose = (Compose *) data;
8252 compose_reply_change_mode(compose, value);
8255 static void compose_update_priority_menu_item(Compose * compose)
8257 GtkWidget *menuitem = NULL;
8258 switch (compose->priority) {
8259 case PRIORITY_HIGHEST:
8260 menuitem = gtk_ui_manager_get_widget
8261 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8264 menuitem = gtk_ui_manager_get_widget
8265 (compose->ui_manager, "/Menu/Options/Priority/High");
8267 case PRIORITY_NORMAL:
8268 menuitem = gtk_ui_manager_get_widget
8269 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8272 menuitem = gtk_ui_manager_get_widget
8273 (compose->ui_manager, "/Menu/Options/Priority/Low");
8275 case PRIORITY_LOWEST:
8276 menuitem = gtk_ui_manager_get_widget
8277 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8280 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8283 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8285 Compose *compose = (Compose *) data;
8287 gboolean can_sign = FALSE, can_encrypt = FALSE;
8289 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8291 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8294 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8295 g_free(compose->privacy_system);
8296 compose->privacy_system = NULL;
8297 g_free(compose->encdata);
8298 compose->encdata = NULL;
8299 if (systemid != NULL) {
8300 compose->privacy_system = g_strdup(systemid);
8302 can_sign = privacy_system_can_sign(systemid);
8303 can_encrypt = privacy_system_can_encrypt(systemid);
8306 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8308 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8309 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8312 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8314 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8315 GtkWidget *menuitem = NULL;
8316 GList *children, *amenu;
8317 gboolean can_sign = FALSE, can_encrypt = FALSE;
8318 gboolean found = FALSE;
8320 if (compose->privacy_system != NULL) {
8322 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8323 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8324 cm_return_if_fail(menuitem != NULL);
8326 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8329 while (amenu != NULL) {
8330 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8331 if (systemid != NULL) {
8332 if (strcmp(systemid, compose->privacy_system) == 0 &&
8333 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8334 menuitem = GTK_WIDGET(amenu->data);
8336 can_sign = privacy_system_can_sign(systemid);
8337 can_encrypt = privacy_system_can_encrypt(systemid);
8341 } else if (strlen(compose->privacy_system) == 0 &&
8342 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8343 menuitem = GTK_WIDGET(amenu->data);
8346 can_encrypt = FALSE;
8351 amenu = amenu->next;
8353 g_list_free(children);
8354 if (menuitem != NULL)
8355 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8357 if (warn && !found && strlen(compose->privacy_system)) {
8358 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8359 "will not be able to sign or encrypt this message."),
8360 compose->privacy_system);
8364 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8365 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8368 static void compose_set_out_encoding(Compose *compose)
8370 CharSet out_encoding;
8371 const gchar *branch = NULL;
8372 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8374 switch(out_encoding) {
8375 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8376 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8377 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8378 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8379 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8380 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8381 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8382 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8383 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8384 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8385 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8386 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8387 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8388 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8389 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8390 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8391 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8392 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8393 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8394 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8395 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8396 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8397 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8398 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8399 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8400 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8401 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8402 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8403 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8404 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8405 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8406 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8407 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8408 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8410 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8413 static void compose_set_template_menu(Compose *compose)
8415 GSList *tmpl_list, *cur;
8419 tmpl_list = template_get_config();
8421 menu = gtk_menu_new();
8423 gtk_menu_set_accel_group (GTK_MENU (menu),
8424 gtk_ui_manager_get_accel_group(compose->ui_manager));
8425 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8426 Template *tmpl = (Template *)cur->data;
8427 gchar *accel_path = NULL;
8428 item = gtk_menu_item_new_with_label(tmpl->name);
8429 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8430 g_signal_connect(G_OBJECT(item), "activate",
8431 G_CALLBACK(compose_template_activate_cb),
8433 g_object_set_data(G_OBJECT(item), "template", tmpl);
8434 gtk_widget_show(item);
8435 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8436 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8440 gtk_widget_show(menu);
8441 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8444 void compose_update_actions_menu(Compose *compose)
8446 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8449 static void compose_update_privacy_systems_menu(Compose *compose)
8451 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8452 GSList *systems, *cur;
8454 GtkWidget *system_none;
8456 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8457 GtkWidget *privacy_menu = gtk_menu_new();
8459 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8460 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8462 g_signal_connect(G_OBJECT(system_none), "activate",
8463 G_CALLBACK(compose_set_privacy_system_cb), compose);
8465 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8466 gtk_widget_show(system_none);
8468 systems = privacy_get_system_ids();
8469 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8470 gchar *systemid = cur->data;
8472 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8473 widget = gtk_radio_menu_item_new_with_label(group,
8474 privacy_system_get_name(systemid));
8475 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8476 g_strdup(systemid), g_free);
8477 g_signal_connect(G_OBJECT(widget), "activate",
8478 G_CALLBACK(compose_set_privacy_system_cb), compose);
8480 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8481 gtk_widget_show(widget);
8484 g_slist_free(systems);
8485 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8486 gtk_widget_show_all(privacy_menu);
8487 gtk_widget_show_all(privacy_menuitem);
8490 void compose_reflect_prefs_all(void)
8495 for (cur = compose_list; cur != NULL; cur = cur->next) {
8496 compose = (Compose *)cur->data;
8497 compose_set_template_menu(compose);
8501 void compose_reflect_prefs_pixmap_theme(void)
8506 for (cur = compose_list; cur != NULL; cur = cur->next) {
8507 compose = (Compose *)cur->data;
8508 toolbar_update(TOOLBAR_COMPOSE, compose);
8512 static const gchar *compose_quote_char_from_context(Compose *compose)
8514 const gchar *qmark = NULL;
8516 cm_return_val_if_fail(compose != NULL, NULL);
8518 switch (compose->mode) {
8519 /* use forward-specific quote char */
8520 case COMPOSE_FORWARD:
8521 case COMPOSE_FORWARD_AS_ATTACH:
8522 case COMPOSE_FORWARD_INLINE:
8523 if (compose->folder && compose->folder->prefs &&
8524 compose->folder->prefs->forward_with_format)
8525 qmark = compose->folder->prefs->forward_quotemark;
8526 else if (compose->account->forward_with_format)
8527 qmark = compose->account->forward_quotemark;
8529 qmark = prefs_common.fw_quotemark;
8532 /* use reply-specific quote char in all other modes */
8534 if (compose->folder && compose->folder->prefs &&
8535 compose->folder->prefs->reply_with_format)
8536 qmark = compose->folder->prefs->reply_quotemark;
8537 else if (compose->account->reply_with_format)
8538 qmark = compose->account->reply_quotemark;
8540 qmark = prefs_common.quotemark;
8544 if (qmark == NULL || *qmark == '\0')
8550 static void compose_template_apply(Compose *compose, Template *tmpl,
8554 GtkTextBuffer *buffer;
8558 gchar *parsed_str = NULL;
8559 gint cursor_pos = 0;
8560 const gchar *err_msg = _("The body of the template has an error at line %d.");
8563 /* process the body */
8565 text = GTK_TEXT_VIEW(compose->text);
8566 buffer = gtk_text_view_get_buffer(text);
8569 qmark = compose_quote_char_from_context(compose);
8571 if (compose->replyinfo != NULL) {
8574 gtk_text_buffer_set_text(buffer, "", -1);
8575 mark = gtk_text_buffer_get_insert(buffer);
8576 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8578 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8579 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8581 } else if (compose->fwdinfo != NULL) {
8584 gtk_text_buffer_set_text(buffer, "", -1);
8585 mark = gtk_text_buffer_get_insert(buffer);
8586 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8588 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8589 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8592 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8594 GtkTextIter start, end;
8597 gtk_text_buffer_get_start_iter(buffer, &start);
8598 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8599 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8601 /* clear the buffer now */
8603 gtk_text_buffer_set_text(buffer, "", -1);
8605 parsed_str = compose_quote_fmt(compose, dummyinfo,
8606 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8607 procmsg_msginfo_free( &dummyinfo );
8613 gtk_text_buffer_set_text(buffer, "", -1);
8614 mark = gtk_text_buffer_get_insert(buffer);
8615 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8618 if (replace && parsed_str && compose->account->auto_sig)
8619 compose_insert_sig(compose, FALSE);
8621 if (replace && parsed_str) {
8622 gtk_text_buffer_get_start_iter(buffer, &iter);
8623 gtk_text_buffer_place_cursor(buffer, &iter);
8627 cursor_pos = quote_fmt_get_cursor_pos();
8628 compose->set_cursor_pos = cursor_pos;
8629 if (cursor_pos == -1)
8631 gtk_text_buffer_get_start_iter(buffer, &iter);
8632 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8633 gtk_text_buffer_place_cursor(buffer, &iter);
8636 /* process the other fields */
8638 compose_template_apply_fields(compose, tmpl);
8639 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8640 quote_fmt_reset_vartable();
8641 compose_changed_cb(NULL, compose);
8644 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8645 gtkaspell_highlight_all(compose->gtkaspell);
8649 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8651 MsgInfo* dummyinfo = NULL;
8652 MsgInfo *msginfo = NULL;
8655 if (compose->replyinfo != NULL)
8656 msginfo = compose->replyinfo;
8657 else if (compose->fwdinfo != NULL)
8658 msginfo = compose->fwdinfo;
8660 dummyinfo = compose_msginfo_new_from_compose(compose);
8661 msginfo = dummyinfo;
8664 if (tmpl->from && *tmpl->from != '\0') {
8666 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8667 compose->gtkaspell);
8669 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8671 quote_fmt_scan_string(tmpl->from);
8674 buf = quote_fmt_get_buffer();
8676 alertpanel_error(_("Template From format error."));
8678 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8682 if (tmpl->to && *tmpl->to != '\0') {
8684 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8685 compose->gtkaspell);
8687 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8689 quote_fmt_scan_string(tmpl->to);
8692 buf = quote_fmt_get_buffer();
8694 alertpanel_error(_("Template To format error."));
8696 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8700 if (tmpl->cc && *tmpl->cc != '\0') {
8702 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8703 compose->gtkaspell);
8705 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8707 quote_fmt_scan_string(tmpl->cc);
8710 buf = quote_fmt_get_buffer();
8712 alertpanel_error(_("Template Cc format error."));
8714 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8718 if (tmpl->bcc && *tmpl->bcc != '\0') {
8720 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8721 compose->gtkaspell);
8723 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8725 quote_fmt_scan_string(tmpl->bcc);
8728 buf = quote_fmt_get_buffer();
8730 alertpanel_error(_("Template Bcc format error."));
8732 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8736 if (tmpl->replyto && *tmpl->replyto != '\0') {
8738 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8739 compose->gtkaspell);
8741 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8743 quote_fmt_scan_string(tmpl->replyto);
8746 buf = quote_fmt_get_buffer();
8748 alertpanel_error(_("Template Reply-To format error."));
8750 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8754 /* process the subject */
8755 if (tmpl->subject && *tmpl->subject != '\0') {
8757 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8758 compose->gtkaspell);
8760 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8762 quote_fmt_scan_string(tmpl->subject);
8765 buf = quote_fmt_get_buffer();
8767 alertpanel_error(_("Template subject format error."));
8769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8773 procmsg_msginfo_free( &dummyinfo );
8776 static void compose_destroy(Compose *compose)
8778 GtkAllocation allocation;
8779 GtkTextBuffer *buffer;
8780 GtkClipboard *clipboard;
8782 compose_list = g_list_remove(compose_list, compose);
8784 if (compose->updating) {
8785 debug_print("danger, not destroying anything now\n");
8786 compose->deferred_destroy = TRUE;
8790 /* NOTE: address_completion_end() does nothing with the window
8791 * however this may change. */
8792 address_completion_end(compose->window);
8794 slist_free_strings_full(compose->to_list);
8795 slist_free_strings_full(compose->newsgroup_list);
8796 slist_free_strings_full(compose->header_list);
8798 slist_free_strings_full(extra_headers);
8799 extra_headers = NULL;
8801 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8803 g_hash_table_destroy(compose->email_hashtable);
8805 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8806 compose->folder_update_callback_id);
8808 procmsg_msginfo_free(&(compose->targetinfo));
8809 procmsg_msginfo_free(&(compose->replyinfo));
8810 procmsg_msginfo_free(&(compose->fwdinfo));
8812 g_free(compose->replyto);
8813 g_free(compose->cc);
8814 g_free(compose->bcc);
8815 g_free(compose->newsgroups);
8816 g_free(compose->followup_to);
8818 g_free(compose->ml_post);
8820 g_free(compose->inreplyto);
8821 g_free(compose->references);
8822 g_free(compose->msgid);
8823 g_free(compose->boundary);
8825 g_free(compose->redirect_filename);
8826 if (compose->undostruct)
8827 undo_destroy(compose->undostruct);
8829 g_free(compose->sig_str);
8831 g_free(compose->exteditor_file);
8833 g_free(compose->orig_charset);
8835 g_free(compose->privacy_system);
8836 g_free(compose->encdata);
8838 #ifndef USE_ALT_ADDRBOOK
8839 if (addressbook_get_target_compose() == compose)
8840 addressbook_set_target_compose(NULL);
8843 if (compose->gtkaspell) {
8844 gtkaspell_delete(compose->gtkaspell);
8845 compose->gtkaspell = NULL;
8849 if (!compose->batch) {
8850 gtk_widget_get_allocation(compose->window, &allocation);
8851 prefs_common.compose_width = allocation.width;
8852 prefs_common.compose_height = allocation.height;
8855 if (!gtk_widget_get_parent(compose->paned))
8856 gtk_widget_destroy(compose->paned);
8857 gtk_widget_destroy(compose->popupmenu);
8859 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8860 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8861 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8863 gtk_widget_destroy(compose->window);
8864 toolbar_destroy(compose->toolbar);
8865 g_free(compose->toolbar);
8866 cm_mutex_free(compose->mutex);
8870 static void compose_attach_info_free(AttachInfo *ainfo)
8872 g_free(ainfo->file);
8873 g_free(ainfo->content_type);
8874 g_free(ainfo->name);
8875 g_free(ainfo->charset);
8879 static void compose_attach_update_label(Compose *compose)
8884 GtkTreeModel *model;
8889 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8890 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8891 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8895 while(gtk_tree_model_iter_next(model, &iter))
8898 text = g_strdup_printf("(%d)", i);
8899 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8903 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8905 Compose *compose = (Compose *)data;
8906 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8907 GtkTreeSelection *selection;
8909 GtkTreeModel *model;
8911 selection = gtk_tree_view_get_selection(tree_view);
8912 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8917 for (cur = sel; cur != NULL; cur = cur->next) {
8918 GtkTreePath *path = cur->data;
8919 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8922 gtk_tree_path_free(path);
8925 for (cur = sel; cur != NULL; cur = cur->next) {
8926 GtkTreeRowReference *ref = cur->data;
8927 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8930 if (gtk_tree_model_get_iter(model, &iter, path))
8931 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8933 gtk_tree_path_free(path);
8934 gtk_tree_row_reference_free(ref);
8938 compose_attach_update_label(compose);
8941 static struct _AttachProperty
8944 GtkWidget *mimetype_entry;
8945 GtkWidget *encoding_optmenu;
8946 GtkWidget *path_entry;
8947 GtkWidget *filename_entry;
8949 GtkWidget *cancel_btn;
8952 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8954 gtk_tree_path_free((GtkTreePath *)ptr);
8957 static void compose_attach_property(GtkAction *action, gpointer data)
8959 Compose *compose = (Compose *)data;
8960 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8962 GtkComboBox *optmenu;
8963 GtkTreeSelection *selection;
8965 GtkTreeModel *model;
8968 static gboolean cancelled;
8970 /* only if one selected */
8971 selection = gtk_tree_view_get_selection(tree_view);
8972 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8975 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8979 path = (GtkTreePath *) sel->data;
8980 gtk_tree_model_get_iter(model, &iter, path);
8981 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8984 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8990 if (!attach_prop.window)
8991 compose_attach_property_create(&cancelled);
8992 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8993 gtk_widget_grab_focus(attach_prop.ok_btn);
8994 gtk_widget_show(attach_prop.window);
8995 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8996 GTK_WINDOW(compose->window));
8998 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8999 if (ainfo->encoding == ENC_UNKNOWN)
9000 combobox_select_by_data(optmenu, ENC_BASE64);
9002 combobox_select_by_data(optmenu, ainfo->encoding);
9004 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9005 ainfo->content_type ? ainfo->content_type : "");
9006 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9007 ainfo->file ? ainfo->file : "");
9008 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9009 ainfo->name ? ainfo->name : "");
9012 const gchar *entry_text;
9014 gchar *cnttype = NULL;
9021 gtk_widget_hide(attach_prop.window);
9022 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9027 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9028 if (*entry_text != '\0') {
9031 text = g_strstrip(g_strdup(entry_text));
9032 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9033 cnttype = g_strdup(text);
9036 alertpanel_error(_("Invalid MIME type."));
9042 ainfo->encoding = combobox_get_active_data(optmenu);
9044 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9045 if (*entry_text != '\0') {
9046 if (is_file_exist(entry_text) &&
9047 (size = get_file_size(entry_text)) > 0)
9048 file = g_strdup(entry_text);
9051 (_("File doesn't exist or is empty."));
9057 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9058 if (*entry_text != '\0') {
9059 g_free(ainfo->name);
9060 ainfo->name = g_strdup(entry_text);
9064 g_free(ainfo->content_type);
9065 ainfo->content_type = cnttype;
9068 g_free(ainfo->file);
9072 ainfo->size = (goffset)size;
9074 /* update tree store */
9075 text = to_human_readable(ainfo->size);
9076 gtk_tree_model_get_iter(model, &iter, path);
9077 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9078 COL_MIMETYPE, ainfo->content_type,
9080 COL_NAME, ainfo->name,
9081 COL_CHARSET, ainfo->charset,
9087 gtk_tree_path_free(path);
9090 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9092 label = gtk_label_new(str); \
9093 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9094 GTK_FILL, 0, 0, 0); \
9095 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9097 entry = gtk_entry_new(); \
9098 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9099 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9102 static void compose_attach_property_create(gboolean *cancelled)
9108 GtkWidget *mimetype_entry;
9111 GtkListStore *optmenu_menu;
9112 GtkWidget *path_entry;
9113 GtkWidget *filename_entry;
9116 GtkWidget *cancel_btn;
9117 GList *mime_type_list, *strlist;
9120 debug_print("Creating attach_property window...\n");
9122 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9123 gtk_widget_set_size_request(window, 480, -1);
9124 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9125 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9126 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9127 g_signal_connect(G_OBJECT(window), "delete_event",
9128 G_CALLBACK(attach_property_delete_event),
9130 g_signal_connect(G_OBJECT(window), "key_press_event",
9131 G_CALLBACK(attach_property_key_pressed),
9134 vbox = gtk_vbox_new(FALSE, 8);
9135 gtk_container_add(GTK_CONTAINER(window), vbox);
9137 table = gtk_table_new(4, 2, FALSE);
9138 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9139 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9140 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9142 label = gtk_label_new(_("MIME type"));
9143 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9145 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9146 #if !GTK_CHECK_VERSION(2, 24, 0)
9147 mimetype_entry = gtk_combo_box_entry_new_text();
9149 mimetype_entry = gtk_combo_box_text_new_with_entry();
9151 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9152 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9154 /* stuff with list */
9155 mime_type_list = procmime_get_mime_type_list();
9157 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9158 MimeType *type = (MimeType *) mime_type_list->data;
9161 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9163 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9166 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9167 (GCompareFunc)strcmp2);
9170 for (mime_type_list = strlist; mime_type_list != NULL;
9171 mime_type_list = mime_type_list->next) {
9172 #if !GTK_CHECK_VERSION(2, 24, 0)
9173 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9175 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9177 g_free(mime_type_list->data);
9179 g_list_free(strlist);
9180 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9181 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9183 label = gtk_label_new(_("Encoding"));
9184 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9186 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9188 hbox = gtk_hbox_new(FALSE, 0);
9189 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9190 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9192 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9193 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9195 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9196 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9197 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9198 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9199 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9201 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9203 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9204 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9206 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9207 &ok_btn, GTK_STOCK_OK,
9209 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9210 gtk_widget_grab_default(ok_btn);
9212 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9213 G_CALLBACK(attach_property_ok),
9215 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9216 G_CALLBACK(attach_property_cancel),
9219 gtk_widget_show_all(vbox);
9221 attach_prop.window = window;
9222 attach_prop.mimetype_entry = mimetype_entry;
9223 attach_prop.encoding_optmenu = optmenu;
9224 attach_prop.path_entry = path_entry;
9225 attach_prop.filename_entry = filename_entry;
9226 attach_prop.ok_btn = ok_btn;
9227 attach_prop.cancel_btn = cancel_btn;
9230 #undef SET_LABEL_AND_ENTRY
9232 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9238 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9244 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9245 gboolean *cancelled)
9253 static gboolean attach_property_key_pressed(GtkWidget *widget,
9255 gboolean *cancelled)
9257 if (event && event->keyval == GDK_KEY_Escape) {
9261 if (event && event->keyval == GDK_KEY_Return) {
9269 static void compose_exec_ext_editor(Compose *compose)
9274 GdkNativeWindow socket_wid = 0;
9278 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9279 G_DIR_SEPARATOR, compose);
9281 if (compose_get_ext_editor_uses_socket()) {
9282 /* Only allow one socket */
9283 if (compose->exteditor_socket != NULL) {
9284 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9285 /* Move the focus off of the socket */
9286 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9291 /* Create the receiving GtkSocket */
9292 socket = gtk_socket_new ();
9293 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9294 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9296 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9297 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9298 /* Realize the socket so that we can use its ID */
9299 gtk_widget_realize(socket);
9300 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9301 compose->exteditor_socket = socket;
9304 if (pipe(pipe_fds) < 0) {
9310 if ((pid = fork()) < 0) {
9317 /* close the write side of the pipe */
9320 compose->exteditor_file = g_strdup(tmp);
9321 compose->exteditor_pid = pid;
9323 compose_set_ext_editor_sensitive(compose, FALSE);
9326 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9328 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9330 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9334 } else { /* process-monitoring process */
9340 /* close the read side of the pipe */
9343 if (compose_write_body_to_file(compose, tmp) < 0) {
9344 fd_write_all(pipe_fds[1], "2\n", 2);
9348 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9350 fd_write_all(pipe_fds[1], "1\n", 2);
9354 /* wait until editor is terminated */
9355 waitpid(pid_ed, NULL, 0);
9357 fd_write_all(pipe_fds[1], "0\n", 2);
9364 #endif /* G_OS_UNIX */
9368 static gboolean compose_get_ext_editor_cmd_valid()
9370 gboolean has_s = FALSE;
9371 gboolean has_w = FALSE;
9372 const gchar *p = prefs_common_get_ext_editor_cmd();
9375 while ((p = strchr(p, '%'))) {
9381 } else if (*p == 'w') {
9392 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9399 cm_return_val_if_fail(file != NULL, -1);
9401 if ((pid = fork()) < 0) {
9406 if (pid != 0) return pid;
9408 /* grandchild process */
9410 if (setpgid(0, getppid()))
9413 if (compose_get_ext_editor_cmd_valid()) {
9414 if (compose_get_ext_editor_uses_socket()) {
9415 p = g_strdup(prefs_common_get_ext_editor_cmd());
9416 s = strstr(p, "%w");
9418 if (strstr(p, "%s") < s)
9419 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9421 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9424 g_snprintf(buf, sizeof(buf),
9425 prefs_common_get_ext_editor_cmd(), file);
9428 if (prefs_common_get_ext_editor_cmd())
9429 g_warning("External editor command-line is invalid: '%s'",
9430 prefs_common_get_ext_editor_cmd());
9431 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9434 cmdline = strsplit_with_quote(buf, " ", 1024);
9435 execvp(cmdline[0], cmdline);
9438 g_strfreev(cmdline);
9443 static gboolean compose_ext_editor_kill(Compose *compose)
9445 pid_t pgid = compose->exteditor_pid * -1;
9448 ret = kill(pgid, 0);
9450 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9454 msg = g_strdup_printf
9455 (_("The external editor is still working.\n"
9456 "Force terminating the process?\n"
9457 "process group id: %d"), -pgid);
9458 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9459 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9463 if (val == G_ALERTALTERNATE) {
9464 g_source_remove(compose->exteditor_tag);
9465 g_io_channel_shutdown(compose->exteditor_ch,
9467 g_io_channel_unref(compose->exteditor_ch);
9469 if (kill(pgid, SIGTERM) < 0) perror("kill");
9470 waitpid(compose->exteditor_pid, NULL, 0);
9472 g_warning("Terminated process group id: %d. "
9473 "Temporary file: %s", -pgid, compose->exteditor_file);
9475 compose_set_ext_editor_sensitive(compose, TRUE);
9477 g_free(compose->exteditor_file);
9478 compose->exteditor_file = NULL;
9479 compose->exteditor_pid = -1;
9480 compose->exteditor_ch = NULL;
9481 compose->exteditor_tag = -1;
9489 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9493 Compose *compose = (Compose *)data;
9496 debug_print("Compose: input from monitoring process\n");
9498 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9503 g_io_channel_shutdown(source, FALSE, NULL);
9504 g_io_channel_unref(source);
9506 waitpid(compose->exteditor_pid, NULL, 0);
9508 if (buf[0] == '0') { /* success */
9509 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9510 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9511 GtkTextIter start, end;
9514 gtk_text_buffer_set_text(buffer, "", -1);
9515 compose_insert_file(compose, compose->exteditor_file);
9516 compose_changed_cb(NULL, compose);
9517 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9519 if (claws_unlink(compose->exteditor_file) < 0)
9520 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9522 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9523 gtk_text_buffer_get_start_iter(buffer, &start);
9524 gtk_text_buffer_get_end_iter(buffer, &end);
9525 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9526 if (chars && strlen(chars) > 0)
9527 compose->modified = TRUE;
9529 } else if (buf[0] == '1') { /* failed */
9530 g_warning("Couldn't exec external editor");
9531 if (claws_unlink(compose->exteditor_file) < 0)
9532 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9533 } else if (buf[0] == '2') {
9534 g_warning("Couldn't write to file");
9535 } else if (buf[0] == '3') {
9536 g_warning("Pipe read failed");
9539 compose_set_ext_editor_sensitive(compose, TRUE);
9541 g_free(compose->exteditor_file);
9542 compose->exteditor_file = NULL;
9543 compose->exteditor_pid = -1;
9544 compose->exteditor_ch = NULL;
9545 compose->exteditor_tag = -1;
9546 if (compose->exteditor_socket) {
9547 gtk_widget_destroy(compose->exteditor_socket);
9548 compose->exteditor_socket = NULL;
9555 static char *ext_editor_menu_entries[] = {
9556 "Menu/Message/Send",
9557 "Menu/Message/SendLater",
9558 "Menu/Message/InsertFile",
9559 "Menu/Message/InsertSig",
9560 "Menu/Message/ReplaceSig",
9561 "Menu/Message/Save",
9562 "Menu/Message/Print",
9567 "Menu/Tools/ShowRuler",
9568 "Menu/Tools/Actions",
9573 static void compose_set_ext_editor_sensitive(Compose *compose,
9578 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9579 cm_menu_set_sensitive_full(compose->ui_manager,
9580 ext_editor_menu_entries[i], sensitive);
9583 if (compose_get_ext_editor_uses_socket()) {
9585 if (compose->exteditor_socket)
9586 gtk_widget_hide(compose->exteditor_socket);
9587 gtk_widget_show(compose->scrolledwin);
9588 if (prefs_common.show_ruler)
9589 gtk_widget_show(compose->ruler_hbox);
9590 /* Fix the focus, as it doesn't go anywhere when the
9591 * socket is hidden or destroyed */
9592 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9594 g_assert (compose->exteditor_socket != NULL);
9595 /* Fix the focus, as it doesn't go anywhere when the
9596 * edit box is hidden */
9597 if (gtk_widget_is_focus(compose->text))
9598 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9599 gtk_widget_hide(compose->scrolledwin);
9600 gtk_widget_hide(compose->ruler_hbox);
9601 gtk_widget_show(compose->exteditor_socket);
9604 gtk_widget_set_sensitive(compose->text, sensitive);
9606 if (compose->toolbar->send_btn)
9607 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9608 if (compose->toolbar->sendl_btn)
9609 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9610 if (compose->toolbar->draft_btn)
9611 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9612 if (compose->toolbar->insert_btn)
9613 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9614 if (compose->toolbar->sig_btn)
9615 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9616 if (compose->toolbar->exteditor_btn)
9617 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9618 if (compose->toolbar->linewrap_current_btn)
9619 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9620 if (compose->toolbar->linewrap_all_btn)
9621 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9624 static gboolean compose_get_ext_editor_uses_socket()
9626 return (prefs_common_get_ext_editor_cmd() &&
9627 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9630 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9632 compose->exteditor_socket = NULL;
9633 /* returning FALSE allows destruction of the socket */
9636 #endif /* G_OS_UNIX */
9639 * compose_undo_state_changed:
9641 * Change the sensivity of the menuentries undo and redo
9643 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9644 gint redo_state, gpointer data)
9646 Compose *compose = (Compose *)data;
9648 switch (undo_state) {
9649 case UNDO_STATE_TRUE:
9650 if (!undostruct->undo_state) {
9651 undostruct->undo_state = TRUE;
9652 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9655 case UNDO_STATE_FALSE:
9656 if (undostruct->undo_state) {
9657 undostruct->undo_state = FALSE;
9658 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9661 case UNDO_STATE_UNCHANGED:
9663 case UNDO_STATE_REFRESH:
9664 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9667 g_warning("Undo state not recognized");
9671 switch (redo_state) {
9672 case UNDO_STATE_TRUE:
9673 if (!undostruct->redo_state) {
9674 undostruct->redo_state = TRUE;
9675 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9678 case UNDO_STATE_FALSE:
9679 if (undostruct->redo_state) {
9680 undostruct->redo_state = FALSE;
9681 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9684 case UNDO_STATE_UNCHANGED:
9686 case UNDO_STATE_REFRESH:
9687 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9690 g_warning("Redo state not recognized");
9695 /* callback functions */
9697 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9698 GtkAllocation *allocation,
9701 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9704 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9705 * includes "non-client" (windows-izm) in calculation, so this calculation
9706 * may not be accurate.
9708 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9709 GtkAllocation *allocation,
9710 GtkSHRuler *shruler)
9712 if (prefs_common.show_ruler) {
9713 gint char_width = 0, char_height = 0;
9714 gint line_width_in_chars;
9716 gtkut_get_font_size(GTK_WIDGET(widget),
9717 &char_width, &char_height);
9718 line_width_in_chars =
9719 (allocation->width - allocation->x) / char_width;
9721 /* got the maximum */
9722 gtk_shruler_set_range(GTK_SHRULER(shruler),
9723 0.0, line_width_in_chars, 0);
9732 ComposePrefType type;
9733 gboolean entry_marked;
9736 static void account_activated(GtkComboBox *optmenu, gpointer data)
9738 Compose *compose = (Compose *)data;
9741 gchar *folderidentifier;
9742 gint account_id = 0;
9745 GSList *list = NULL;
9747 /* Get ID of active account in the combo box */
9748 menu = gtk_combo_box_get_model(optmenu);
9749 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9750 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9752 ac = account_find_from_id(account_id);
9753 cm_return_if_fail(ac != NULL);
9755 if (ac != compose->account) {
9756 compose_select_account(compose, ac, FALSE);
9758 for (list = compose->header_list; list; list = list->next) {
9759 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9761 if (hentry->type == PREF_ACCOUNT || !list->next)
9762 compose_destroy_headerentry(compose, hentry);
9765 compose->header_last = NULL;
9766 g_slist_free(compose->header_list);
9767 compose->header_list = NULL;
9768 compose->header_nextrow = 1;
9769 compose_create_header_entry(compose);
9771 if (ac->set_autocc && ac->auto_cc) {
9772 compose_entry_append(compose, ac->auto_cc,
9773 COMPOSE_CC, PREF_ACCOUNT);
9774 compose_entry_mark_default_to(compose, ac->auto_cc);
9776 if (ac->set_autobcc && ac->auto_bcc) {
9777 compose_entry_append(compose, ac->auto_bcc,
9778 COMPOSE_BCC, PREF_ACCOUNT);
9779 compose_entry_mark_default_to(compose, ac->auto_bcc);
9781 if (ac->set_autoreplyto && ac->auto_replyto) {
9782 compose_entry_append(compose, ac->auto_replyto,
9783 COMPOSE_REPLYTO, PREF_ACCOUNT);
9784 compose_entry_mark_default_to(compose, ac->auto_replyto);
9787 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9788 (ac->protocol == A_NNTP) ?
9789 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9792 /* Set message save folder */
9793 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9794 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9796 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9797 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9799 compose_set_save_to(compose, NULL);
9800 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9801 folderidentifier = folder_item_get_identifier(account_get_special_folder
9802 (compose->account, F_OUTBOX));
9803 compose_set_save_to(compose, folderidentifier);
9804 g_free(folderidentifier);
9808 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9809 GtkTreeViewColumn *column, Compose *compose)
9811 compose_attach_property(NULL, compose);
9814 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9817 Compose *compose = (Compose *)data;
9818 GtkTreeSelection *attach_selection;
9819 gint attach_nr_selected;
9822 if (!event) return FALSE;
9824 if (event->button == 3) {
9825 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9826 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9828 /* If no rows, or just one row is selected, right-click should
9829 * open menu relevant to the row being right-clicked on. We
9830 * achieve that by selecting the clicked row first. If more
9831 * than one row is selected, we shouldn't modify the selection,
9832 * as user may want to remove selected rows (attachments). */
9833 if (attach_nr_selected < 2) {
9834 gtk_tree_selection_unselect_all(attach_selection);
9835 attach_nr_selected = 0;
9836 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9837 event->x, event->y, &path, NULL, NULL, NULL);
9839 gtk_tree_selection_select_path(attach_selection, path);
9840 gtk_tree_path_free(path);
9841 attach_nr_selected++;
9845 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9846 /* Properties menu item makes no sense with more than one row
9847 * selected, the properties dialog can only edit one attachment. */
9848 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9850 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9851 NULL, NULL, event->button, event->time);
9858 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9861 Compose *compose = (Compose *)data;
9863 if (!event) return FALSE;
9865 switch (event->keyval) {
9866 case GDK_KEY_Delete:
9867 compose_attach_remove_selected(NULL, compose);
9873 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9875 toolbar_comp_set_sensitive(compose, allow);
9876 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9877 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9879 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9881 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9882 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9883 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9885 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9889 static void compose_send_cb(GtkAction *action, gpointer data)
9891 Compose *compose = (Compose *)data;
9894 if (compose->exteditor_tag != -1) {
9895 debug_print("ignoring send: external editor still open\n");
9899 if (prefs_common.work_offline &&
9900 !inc_offline_should_override(TRUE,
9901 _("Claws Mail needs network access in order "
9902 "to send this email.")))
9905 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9906 g_source_remove(compose->draft_timeout_tag);
9907 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9910 compose_send(compose);
9913 static void compose_send_later_cb(GtkAction *action, gpointer data)
9915 Compose *compose = (Compose *)data;
9919 compose_allow_user_actions(compose, FALSE);
9920 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9921 compose_allow_user_actions(compose, TRUE);
9925 compose_close(compose);
9926 } else if (val == -1) {
9927 alertpanel_error(_("Could not queue message."));
9928 } else if (val == -2) {
9929 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9930 } else if (val == -3) {
9931 if (privacy_peek_error())
9932 alertpanel_error(_("Could not queue message for sending:\n\n"
9933 "Signature failed: %s"), privacy_get_error());
9934 } else if (val == -4) {
9935 alertpanel_error(_("Could not queue message for sending:\n\n"
9936 "Charset conversion failed."));
9937 } else if (val == -5) {
9938 alertpanel_error(_("Could not queue message for sending:\n\n"
9939 "Couldn't get recipient encryption key."));
9940 } else if (val == -6) {
9943 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9946 #define DRAFTED_AT_EXIT "drafted_at_exit"
9947 static void compose_register_draft(MsgInfo *info)
9949 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9950 DRAFTED_AT_EXIT, NULL);
9951 FILE *fp = g_fopen(filepath, "ab");
9954 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9962 gboolean compose_draft (gpointer data, guint action)
9964 Compose *compose = (Compose *)data;
9969 MsgFlags flag = {0, 0};
9970 static gboolean lock = FALSE;
9971 MsgInfo *newmsginfo;
9973 gboolean target_locked = FALSE;
9974 gboolean err = FALSE;
9976 if (lock) return FALSE;
9978 if (compose->sending)
9981 draft = account_get_special_folder(compose->account, F_DRAFT);
9982 cm_return_val_if_fail(draft != NULL, FALSE);
9984 if (!g_mutex_trylock(compose->mutex)) {
9985 /* we don't want to lock the mutex once it's available,
9986 * because as the only other part of compose.c locking
9987 * it is compose_close - which means once unlocked,
9988 * the compose struct will be freed */
9989 debug_print("couldn't lock mutex, probably sending\n");
9995 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9996 G_DIR_SEPARATOR, compose);
9997 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9998 FILE_OP_ERROR(tmp, "fopen");
10002 /* chmod for security */
10003 if (change_file_mode_rw(fp, tmp) < 0) {
10004 FILE_OP_ERROR(tmp, "chmod");
10005 g_warning("can't change file mode");
10008 /* Save draft infos */
10009 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10010 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10012 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10013 gchar *savefolderid;
10015 savefolderid = compose_get_save_to(compose);
10016 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10017 g_free(savefolderid);
10019 if (compose->return_receipt) {
10020 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10022 if (compose->privacy_system) {
10023 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10024 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10025 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10028 /* Message-ID of message replying to */
10029 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10030 gchar *folderid = NULL;
10032 if (compose->replyinfo->folder)
10033 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10034 if (folderid == NULL)
10035 folderid = g_strdup("NULL");
10037 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10040 /* Message-ID of message forwarding to */
10041 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10042 gchar *folderid = NULL;
10044 if (compose->fwdinfo->folder)
10045 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10046 if (folderid == NULL)
10047 folderid = g_strdup("NULL");
10049 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10053 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10054 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10056 sheaders = compose_get_manual_headers_info(compose);
10057 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10060 /* end of headers */
10061 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10068 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10072 if (fclose(fp) == EOF) {
10076 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10077 if (compose->targetinfo) {
10078 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10080 flag.perm_flags |= MSG_LOCKED;
10082 flag.tmp_flags = MSG_DRAFT;
10084 folder_item_scan(draft);
10085 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10086 MsgInfo *tmpinfo = NULL;
10087 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10088 if (compose->msgid) {
10089 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10092 msgnum = tmpinfo->msgnum;
10093 procmsg_msginfo_free(&tmpinfo);
10094 debug_print("got draft msgnum %d from scanning\n", msgnum);
10096 debug_print("didn't get draft msgnum after scanning\n");
10099 debug_print("got draft msgnum %d from adding\n", msgnum);
10105 if (action != COMPOSE_AUTO_SAVE) {
10106 if (action != COMPOSE_DRAFT_FOR_EXIT)
10107 alertpanel_error(_("Could not save draft."));
10110 gtkut_window_popup(compose->window);
10111 val = alertpanel_full(_("Could not save draft"),
10112 _("Could not save draft.\n"
10113 "Do you want to cancel exit or discard this email?"),
10114 _("_Cancel exit"), _("_Discard email"), NULL,
10115 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10116 if (val == G_ALERTALTERNATE) {
10118 g_mutex_unlock(compose->mutex); /* must be done before closing */
10119 compose_close(compose);
10123 g_mutex_unlock(compose->mutex); /* must be done before closing */
10132 if (compose->mode == COMPOSE_REEDIT) {
10133 compose_remove_reedit_target(compose, TRUE);
10136 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10139 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10141 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10143 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10144 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10145 procmsg_msginfo_set_flags(newmsginfo, 0,
10146 MSG_HAS_ATTACHMENT);
10148 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10149 compose_register_draft(newmsginfo);
10151 procmsg_msginfo_free(&newmsginfo);
10154 folder_item_scan(draft);
10156 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10158 g_mutex_unlock(compose->mutex); /* must be done before closing */
10159 compose_close(compose);
10165 path = folder_item_fetch_msg(draft, msgnum);
10166 if (path == NULL) {
10167 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10170 if (g_stat(path, &s) < 0) {
10171 FILE_OP_ERROR(path, "stat");
10177 procmsg_msginfo_free(&(compose->targetinfo));
10178 compose->targetinfo = procmsg_msginfo_new();
10179 compose->targetinfo->msgnum = msgnum;
10180 compose->targetinfo->size = (goffset)s.st_size;
10181 compose->targetinfo->mtime = s.st_mtime;
10182 compose->targetinfo->folder = draft;
10184 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10185 compose->mode = COMPOSE_REEDIT;
10187 if (action == COMPOSE_AUTO_SAVE) {
10188 compose->autosaved_draft = compose->targetinfo;
10190 compose->modified = FALSE;
10191 compose_set_title(compose);
10195 g_mutex_unlock(compose->mutex);
10199 void compose_clear_exit_drafts(void)
10201 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10202 DRAFTED_AT_EXIT, NULL);
10203 if (is_file_exist(filepath))
10204 claws_unlink(filepath);
10209 void compose_reopen_exit_drafts(void)
10211 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10212 DRAFTED_AT_EXIT, NULL);
10213 FILE *fp = g_fopen(filepath, "rb");
10217 while (fgets(buf, sizeof(buf), fp)) {
10218 gchar **parts = g_strsplit(buf, "\t", 2);
10219 const gchar *folder = parts[0];
10220 int msgnum = parts[1] ? atoi(parts[1]):-1;
10222 if (folder && *folder && msgnum > -1) {
10223 FolderItem *item = folder_find_item_from_identifier(folder);
10224 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10226 compose_reedit(info, FALSE);
10233 compose_clear_exit_drafts();
10236 static void compose_save_cb(GtkAction *action, gpointer data)
10238 Compose *compose = (Compose *)data;
10239 compose_draft(compose, COMPOSE_KEEP_EDITING);
10240 compose->rmode = COMPOSE_REEDIT;
10243 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10245 if (compose && file_list) {
10248 for ( tmp = file_list; tmp; tmp = tmp->next) {
10249 gchar *file = (gchar *) tmp->data;
10250 gchar *utf8_filename = conv_filename_to_utf8(file);
10251 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10252 compose_changed_cb(NULL, compose);
10257 g_free(utf8_filename);
10262 static void compose_attach_cb(GtkAction *action, gpointer data)
10264 Compose *compose = (Compose *)data;
10267 if (compose->redirect_filename != NULL)
10270 /* Set focus_window properly, in case we were called via popup menu,
10271 * which unsets it (via focus_out_event callback on compose window). */
10272 manage_window_focus_in(compose->window, NULL, NULL);
10274 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10277 compose_attach_from_list(compose, file_list, TRUE);
10278 g_list_free(file_list);
10282 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10284 Compose *compose = (Compose *)data;
10286 gint files_inserted = 0;
10288 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10293 for ( tmp = file_list; tmp; tmp = tmp->next) {
10294 gchar *file = (gchar *) tmp->data;
10295 gchar *filedup = g_strdup(file);
10296 gchar *shortfile = g_path_get_basename(filedup);
10297 ComposeInsertResult res;
10298 /* insert the file if the file is short or if the user confirmed that
10299 he/she wants to insert the large file */
10300 res = compose_insert_file(compose, file);
10301 if (res == COMPOSE_INSERT_READ_ERROR) {
10302 alertpanel_error(_("File '%s' could not be read."), shortfile);
10303 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10304 alertpanel_error(_("File '%s' contained invalid characters\n"
10305 "for the current encoding, insertion may be incorrect."),
10307 } else if (res == COMPOSE_INSERT_SUCCESS)
10314 g_list_free(file_list);
10318 if (files_inserted > 0 && compose->gtkaspell &&
10319 compose->gtkaspell->check_while_typing)
10320 gtkaspell_highlight_all(compose->gtkaspell);
10324 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10326 Compose *compose = (Compose *)data;
10328 compose_insert_sig(compose, FALSE);
10331 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10333 Compose *compose = (Compose *)data;
10335 compose_insert_sig(compose, TRUE);
10338 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10342 Compose *compose = (Compose *)data;
10344 gtkut_widget_get_uposition(widget, &x, &y);
10345 if (!compose->batch) {
10346 prefs_common.compose_x = x;
10347 prefs_common.compose_y = y;
10349 if (compose->sending || compose->updating)
10351 compose_close_cb(NULL, compose);
10355 void compose_close_toolbar(Compose *compose)
10357 compose_close_cb(NULL, compose);
10360 static gboolean compose_can_autosave(Compose *compose)
10362 if (compose->privacy_system && compose->use_encryption)
10363 return prefs_common.autosave && prefs_common.autosave_encrypted;
10365 return prefs_common.autosave;
10368 static void compose_close_cb(GtkAction *action, gpointer data)
10370 Compose *compose = (Compose *)data;
10374 if (compose->exteditor_tag != -1) {
10375 if (!compose_ext_editor_kill(compose))
10380 if (compose->modified) {
10381 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10382 if (!g_mutex_trylock(compose->mutex)) {
10383 /* we don't want to lock the mutex once it's available,
10384 * because as the only other part of compose.c locking
10385 * it is compose_close - which means once unlocked,
10386 * the compose struct will be freed */
10387 debug_print("couldn't lock mutex, probably sending\n");
10391 val = alertpanel(_("Discard message"),
10392 _("This message has been modified. Discard it?"),
10393 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10395 val = alertpanel(_("Save changes"),
10396 _("This message has been modified. Save the latest changes?"),
10397 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10400 g_mutex_unlock(compose->mutex);
10402 case G_ALERTDEFAULT:
10403 if (compose_can_autosave(compose) && !reedit)
10404 compose_remove_draft(compose);
10406 case G_ALERTALTERNATE:
10407 compose_draft(data, COMPOSE_QUIT_EDITING);
10414 compose_close(compose);
10417 static void compose_print_cb(GtkAction *action, gpointer data)
10419 Compose *compose = (Compose *) data;
10421 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10422 if (compose->targetinfo)
10423 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10426 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10428 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10429 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10430 Compose *compose = (Compose *) data;
10433 compose->out_encoding = (CharSet)value;
10436 static void compose_address_cb(GtkAction *action, gpointer data)
10438 Compose *compose = (Compose *)data;
10440 #ifndef USE_ALT_ADDRBOOK
10441 addressbook_open(compose);
10443 GError* error = NULL;
10444 addressbook_connect_signals(compose);
10445 addressbook_dbus_open(TRUE, &error);
10447 g_warning("%s", error->message);
10448 g_error_free(error);
10453 static void about_show_cb(GtkAction *action, gpointer data)
10458 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10460 Compose *compose = (Compose *)data;
10465 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10466 cm_return_if_fail(tmpl != NULL);
10468 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10470 val = alertpanel(_("Apply template"), msg,
10471 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10474 if (val == G_ALERTDEFAULT)
10475 compose_template_apply(compose, tmpl, TRUE);
10476 else if (val == G_ALERTALTERNATE)
10477 compose_template_apply(compose, tmpl, FALSE);
10480 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10482 Compose *compose = (Compose *)data;
10485 if (compose->exteditor_tag != -1) {
10486 debug_print("ignoring open external editor: external editor still open\n");
10490 compose_exec_ext_editor(compose);
10493 static void compose_undo_cb(GtkAction *action, gpointer data)
10495 Compose *compose = (Compose *)data;
10496 gboolean prev_autowrap = compose->autowrap;
10498 compose->autowrap = FALSE;
10499 undo_undo(compose->undostruct);
10500 compose->autowrap = prev_autowrap;
10503 static void compose_redo_cb(GtkAction *action, gpointer data)
10505 Compose *compose = (Compose *)data;
10506 gboolean prev_autowrap = compose->autowrap;
10508 compose->autowrap = FALSE;
10509 undo_redo(compose->undostruct);
10510 compose->autowrap = prev_autowrap;
10513 static void entry_cut_clipboard(GtkWidget *entry)
10515 if (GTK_IS_EDITABLE(entry))
10516 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10517 else if (GTK_IS_TEXT_VIEW(entry))
10518 gtk_text_buffer_cut_clipboard(
10519 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10520 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10524 static void entry_copy_clipboard(GtkWidget *entry)
10526 if (GTK_IS_EDITABLE(entry))
10527 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10528 else if (GTK_IS_TEXT_VIEW(entry))
10529 gtk_text_buffer_copy_clipboard(
10530 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10531 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10534 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10535 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10537 if (GTK_IS_TEXT_VIEW(entry)) {
10538 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10539 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10540 GtkTextIter start_iter, end_iter;
10542 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10544 if (contents == NULL)
10547 /* we shouldn't delete the selection when middle-click-pasting, or we
10548 * can't mid-click-paste our own selection */
10549 if (clip != GDK_SELECTION_PRIMARY) {
10550 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10551 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10554 if (insert_place == NULL) {
10555 /* if insert_place isn't specified, insert at the cursor.
10556 * used for Ctrl-V pasting */
10557 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10558 start = gtk_text_iter_get_offset(&start_iter);
10559 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10561 /* if insert_place is specified, paste here.
10562 * used for mid-click-pasting */
10563 start = gtk_text_iter_get_offset(insert_place);
10564 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10565 if (prefs_common.primary_paste_unselects)
10566 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10570 /* paste unwrapped: mark the paste so it's not wrapped later */
10571 end = start + strlen(contents);
10572 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10573 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10574 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10575 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10576 /* rewrap paragraph now (after a mid-click-paste) */
10577 mark_start = gtk_text_buffer_get_insert(buffer);
10578 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10579 gtk_text_iter_backward_char(&start_iter);
10580 compose_beautify_paragraph(compose, &start_iter, TRUE);
10582 } else if (GTK_IS_EDITABLE(entry))
10583 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10585 compose->modified = TRUE;
10588 static void entry_allsel(GtkWidget *entry)
10590 if (GTK_IS_EDITABLE(entry))
10591 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10592 else if (GTK_IS_TEXT_VIEW(entry)) {
10593 GtkTextIter startiter, enditer;
10594 GtkTextBuffer *textbuf;
10596 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10597 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10598 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10600 gtk_text_buffer_move_mark_by_name(textbuf,
10601 "selection_bound", &startiter);
10602 gtk_text_buffer_move_mark_by_name(textbuf,
10603 "insert", &enditer);
10607 static void compose_cut_cb(GtkAction *action, gpointer data)
10609 Compose *compose = (Compose *)data;
10610 if (compose->focused_editable
10611 #ifndef GENERIC_UMPC
10612 && gtk_widget_has_focus(compose->focused_editable)
10615 entry_cut_clipboard(compose->focused_editable);
10618 static void compose_copy_cb(GtkAction *action, gpointer data)
10620 Compose *compose = (Compose *)data;
10621 if (compose->focused_editable
10622 #ifndef GENERIC_UMPC
10623 && gtk_widget_has_focus(compose->focused_editable)
10626 entry_copy_clipboard(compose->focused_editable);
10629 static void compose_paste_cb(GtkAction *action, gpointer data)
10631 Compose *compose = (Compose *)data;
10632 gint prev_autowrap;
10633 GtkTextBuffer *buffer;
10635 if (compose->focused_editable &&
10636 #ifndef GENERIC_UMPC
10637 gtk_widget_has_focus(compose->focused_editable)
10640 entry_paste_clipboard(compose, compose->focused_editable,
10641 prefs_common.linewrap_pastes,
10642 GDK_SELECTION_CLIPBOARD, NULL);
10647 #ifndef GENERIC_UMPC
10648 gtk_widget_has_focus(compose->text) &&
10650 compose->gtkaspell &&
10651 compose->gtkaspell->check_while_typing)
10652 gtkaspell_highlight_all(compose->gtkaspell);
10656 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10658 Compose *compose = (Compose *)data;
10659 gint wrap_quote = prefs_common.linewrap_quote;
10660 if (compose->focused_editable
10661 #ifndef GENERIC_UMPC
10662 && gtk_widget_has_focus(compose->focused_editable)
10665 /* let text_insert() (called directly or at a later time
10666 * after the gtk_editable_paste_clipboard) know that
10667 * text is to be inserted as a quotation. implemented
10668 * by using a simple refcount... */
10669 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10670 G_OBJECT(compose->focused_editable),
10671 "paste_as_quotation"));
10672 g_object_set_data(G_OBJECT(compose->focused_editable),
10673 "paste_as_quotation",
10674 GINT_TO_POINTER(paste_as_quotation + 1));
10675 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10676 entry_paste_clipboard(compose, compose->focused_editable,
10677 prefs_common.linewrap_pastes,
10678 GDK_SELECTION_CLIPBOARD, NULL);
10679 prefs_common.linewrap_quote = wrap_quote;
10683 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10685 Compose *compose = (Compose *)data;
10686 gint prev_autowrap;
10687 GtkTextBuffer *buffer;
10689 if (compose->focused_editable
10690 #ifndef GENERIC_UMPC
10691 && gtk_widget_has_focus(compose->focused_editable)
10694 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10695 GDK_SELECTION_CLIPBOARD, NULL);
10700 #ifndef GENERIC_UMPC
10701 gtk_widget_has_focus(compose->text) &&
10703 compose->gtkaspell &&
10704 compose->gtkaspell->check_while_typing)
10705 gtkaspell_highlight_all(compose->gtkaspell);
10709 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10711 Compose *compose = (Compose *)data;
10712 gint prev_autowrap;
10713 GtkTextBuffer *buffer;
10715 if (compose->focused_editable
10716 #ifndef GENERIC_UMPC
10717 && gtk_widget_has_focus(compose->focused_editable)
10720 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10721 GDK_SELECTION_CLIPBOARD, NULL);
10726 #ifndef GENERIC_UMPC
10727 gtk_widget_has_focus(compose->text) &&
10729 compose->gtkaspell &&
10730 compose->gtkaspell->check_while_typing)
10731 gtkaspell_highlight_all(compose->gtkaspell);
10735 static void compose_allsel_cb(GtkAction *action, gpointer data)
10737 Compose *compose = (Compose *)data;
10738 if (compose->focused_editable
10739 #ifndef GENERIC_UMPC
10740 && gtk_widget_has_focus(compose->focused_editable)
10743 entry_allsel(compose->focused_editable);
10746 static void textview_move_beginning_of_line (GtkTextView *text)
10748 GtkTextBuffer *buffer;
10752 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10754 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10755 mark = gtk_text_buffer_get_insert(buffer);
10756 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10757 gtk_text_iter_set_line_offset(&ins, 0);
10758 gtk_text_buffer_place_cursor(buffer, &ins);
10761 static void textview_move_forward_character (GtkTextView *text)
10763 GtkTextBuffer *buffer;
10767 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10769 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10770 mark = gtk_text_buffer_get_insert(buffer);
10771 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10772 if (gtk_text_iter_forward_cursor_position(&ins))
10773 gtk_text_buffer_place_cursor(buffer, &ins);
10776 static void textview_move_backward_character (GtkTextView *text)
10778 GtkTextBuffer *buffer;
10782 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10784 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10785 mark = gtk_text_buffer_get_insert(buffer);
10786 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10787 if (gtk_text_iter_backward_cursor_position(&ins))
10788 gtk_text_buffer_place_cursor(buffer, &ins);
10791 static void textview_move_forward_word (GtkTextView *text)
10793 GtkTextBuffer *buffer;
10798 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10800 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10801 mark = gtk_text_buffer_get_insert(buffer);
10802 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10803 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10804 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10805 gtk_text_iter_backward_word_start(&ins);
10806 gtk_text_buffer_place_cursor(buffer, &ins);
10810 static void textview_move_backward_word (GtkTextView *text)
10812 GtkTextBuffer *buffer;
10816 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10818 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10819 mark = gtk_text_buffer_get_insert(buffer);
10820 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10821 if (gtk_text_iter_backward_word_starts(&ins, 1))
10822 gtk_text_buffer_place_cursor(buffer, &ins);
10825 static void textview_move_end_of_line (GtkTextView *text)
10827 GtkTextBuffer *buffer;
10831 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10833 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10834 mark = gtk_text_buffer_get_insert(buffer);
10835 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10836 if (gtk_text_iter_forward_to_line_end(&ins))
10837 gtk_text_buffer_place_cursor(buffer, &ins);
10840 static void textview_move_next_line (GtkTextView *text)
10842 GtkTextBuffer *buffer;
10847 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10849 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10850 mark = gtk_text_buffer_get_insert(buffer);
10851 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10852 offset = gtk_text_iter_get_line_offset(&ins);
10853 if (gtk_text_iter_forward_line(&ins)) {
10854 gtk_text_iter_set_line_offset(&ins, offset);
10855 gtk_text_buffer_place_cursor(buffer, &ins);
10859 static void textview_move_previous_line (GtkTextView *text)
10861 GtkTextBuffer *buffer;
10866 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10868 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10869 mark = gtk_text_buffer_get_insert(buffer);
10870 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10871 offset = gtk_text_iter_get_line_offset(&ins);
10872 if (gtk_text_iter_backward_line(&ins)) {
10873 gtk_text_iter_set_line_offset(&ins, offset);
10874 gtk_text_buffer_place_cursor(buffer, &ins);
10878 static void textview_delete_forward_character (GtkTextView *text)
10880 GtkTextBuffer *buffer;
10882 GtkTextIter ins, end_iter;
10884 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10886 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10887 mark = gtk_text_buffer_get_insert(buffer);
10888 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10890 if (gtk_text_iter_forward_char(&end_iter)) {
10891 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10895 static void textview_delete_backward_character (GtkTextView *text)
10897 GtkTextBuffer *buffer;
10899 GtkTextIter ins, end_iter;
10901 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10903 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10904 mark = gtk_text_buffer_get_insert(buffer);
10905 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10907 if (gtk_text_iter_backward_char(&end_iter)) {
10908 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10912 static void textview_delete_forward_word (GtkTextView *text)
10914 GtkTextBuffer *buffer;
10916 GtkTextIter ins, end_iter;
10918 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10920 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10921 mark = gtk_text_buffer_get_insert(buffer);
10922 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10924 if (gtk_text_iter_forward_word_end(&end_iter)) {
10925 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10929 static void textview_delete_backward_word (GtkTextView *text)
10931 GtkTextBuffer *buffer;
10933 GtkTextIter ins, end_iter;
10935 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10937 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10938 mark = gtk_text_buffer_get_insert(buffer);
10939 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10941 if (gtk_text_iter_backward_word_start(&end_iter)) {
10942 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10946 static void textview_delete_line (GtkTextView *text)
10948 GtkTextBuffer *buffer;
10950 GtkTextIter ins, start_iter, end_iter;
10952 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10954 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10955 mark = gtk_text_buffer_get_insert(buffer);
10956 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10959 gtk_text_iter_set_line_offset(&start_iter, 0);
10962 if (gtk_text_iter_ends_line(&end_iter)){
10963 if (!gtk_text_iter_forward_char(&end_iter))
10964 gtk_text_iter_backward_char(&start_iter);
10967 gtk_text_iter_forward_to_line_end(&end_iter);
10968 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10971 static void textview_delete_to_line_end (GtkTextView *text)
10973 GtkTextBuffer *buffer;
10975 GtkTextIter ins, end_iter;
10977 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10979 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10980 mark = gtk_text_buffer_get_insert(buffer);
10981 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10983 if (gtk_text_iter_ends_line(&end_iter))
10984 gtk_text_iter_forward_char(&end_iter);
10986 gtk_text_iter_forward_to_line_end(&end_iter);
10987 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10990 #define DO_ACTION(name, act) { \
10991 if(!strcmp(name, a_name)) { \
10995 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10997 const gchar *a_name = gtk_action_get_name(action);
10998 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10999 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11000 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11001 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11002 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11003 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11004 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11005 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11006 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11007 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11008 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11009 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11010 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11011 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11015 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11017 Compose *compose = (Compose *)data;
11018 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11019 ComposeCallAdvancedAction action = -1;
11021 action = compose_call_advanced_action_from_path(gaction);
11024 void (*do_action) (GtkTextView *text);
11025 } action_table[] = {
11026 {textview_move_beginning_of_line},
11027 {textview_move_forward_character},
11028 {textview_move_backward_character},
11029 {textview_move_forward_word},
11030 {textview_move_backward_word},
11031 {textview_move_end_of_line},
11032 {textview_move_next_line},
11033 {textview_move_previous_line},
11034 {textview_delete_forward_character},
11035 {textview_delete_backward_character},
11036 {textview_delete_forward_word},
11037 {textview_delete_backward_word},
11038 {textview_delete_line},
11039 {textview_delete_to_line_end}
11042 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11044 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11045 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11046 if (action_table[action].do_action)
11047 action_table[action].do_action(text);
11049 g_warning("Not implemented yet.");
11053 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11055 GtkAllocation allocation;
11059 if (GTK_IS_EDITABLE(widget)) {
11060 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11061 gtk_editable_set_position(GTK_EDITABLE(widget),
11064 if ((parent = gtk_widget_get_parent(widget))
11065 && (parent = gtk_widget_get_parent(parent))
11066 && (parent = gtk_widget_get_parent(parent))) {
11067 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11068 gtk_widget_get_allocation(widget, &allocation);
11069 gint y = allocation.y;
11070 gint height = allocation.height;
11071 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11072 (GTK_SCROLLED_WINDOW(parent));
11074 gfloat value = gtk_adjustment_get_value(shown);
11075 gfloat upper = gtk_adjustment_get_upper(shown);
11076 gfloat page_size = gtk_adjustment_get_page_size(shown);
11077 if (y < (int)value) {
11078 gtk_adjustment_set_value(shown, y - 1);
11080 if ((y + height) > ((int)value + (int)page_size)) {
11081 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11082 gtk_adjustment_set_value(shown,
11083 y + height - (int)page_size - 1);
11085 gtk_adjustment_set_value(shown,
11086 (int)upper - (int)page_size - 1);
11093 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11094 compose->focused_editable = widget;
11096 #ifdef GENERIC_UMPC
11097 if (GTK_IS_TEXT_VIEW(widget)
11098 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11099 g_object_ref(compose->notebook);
11100 g_object_ref(compose->edit_vbox);
11101 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11102 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11103 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11104 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11105 g_object_unref(compose->notebook);
11106 g_object_unref(compose->edit_vbox);
11107 g_signal_handlers_block_by_func(G_OBJECT(widget),
11108 G_CALLBACK(compose_grab_focus_cb),
11110 gtk_widget_grab_focus(widget);
11111 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11112 G_CALLBACK(compose_grab_focus_cb),
11114 } else if (!GTK_IS_TEXT_VIEW(widget)
11115 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11116 g_object_ref(compose->notebook);
11117 g_object_ref(compose->edit_vbox);
11118 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11119 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11120 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11121 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11122 g_object_unref(compose->notebook);
11123 g_object_unref(compose->edit_vbox);
11124 g_signal_handlers_block_by_func(G_OBJECT(widget),
11125 G_CALLBACK(compose_grab_focus_cb),
11127 gtk_widget_grab_focus(widget);
11128 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11129 G_CALLBACK(compose_grab_focus_cb),
11135 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11137 compose->modified = TRUE;
11138 // compose_beautify_paragraph(compose, NULL, TRUE);
11139 #ifndef GENERIC_UMPC
11140 compose_set_title(compose);
11144 static void compose_wrap_cb(GtkAction *action, gpointer data)
11146 Compose *compose = (Compose *)data;
11147 compose_beautify_paragraph(compose, NULL, TRUE);
11150 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11152 Compose *compose = (Compose *)data;
11153 compose_wrap_all_full(compose, TRUE);
11156 static void compose_find_cb(GtkAction *action, gpointer data)
11158 Compose *compose = (Compose *)data;
11160 message_search_compose(compose);
11163 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11166 Compose *compose = (Compose *)data;
11167 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11168 if (compose->autowrap)
11169 compose_wrap_all_full(compose, TRUE);
11170 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11173 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11176 Compose *compose = (Compose *)data;
11177 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11180 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11182 Compose *compose = (Compose *)data;
11184 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11187 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11189 Compose *compose = (Compose *)data;
11191 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11194 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11196 g_free(compose->privacy_system);
11197 g_free(compose->encdata);
11199 compose->privacy_system = g_strdup(account->default_privacy_system);
11200 compose_update_privacy_system_menu_item(compose, warn);
11203 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11205 Compose *compose = (Compose *)data;
11207 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11208 gtk_widget_show(compose->ruler_hbox);
11209 prefs_common.show_ruler = TRUE;
11211 gtk_widget_hide(compose->ruler_hbox);
11212 gtk_widget_queue_resize(compose->edit_vbox);
11213 prefs_common.show_ruler = FALSE;
11217 static void compose_attach_drag_received_cb (GtkWidget *widget,
11218 GdkDragContext *context,
11221 GtkSelectionData *data,
11224 gpointer user_data)
11226 Compose *compose = (Compose *)user_data;
11230 type = gtk_selection_data_get_data_type(data);
11231 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11232 && gtk_drag_get_source_widget(context) !=
11233 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11234 list = uri_list_extract_filenames(
11235 (const gchar *)gtk_selection_data_get_data(data));
11236 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11237 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11238 compose_attach_append
11239 (compose, (const gchar *)tmp->data,
11240 utf8_filename, NULL, NULL);
11241 g_free(utf8_filename);
11243 if (list) compose_changed_cb(NULL, compose);
11244 list_free_strings(list);
11246 } else if (gtk_drag_get_source_widget(context)
11247 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11248 /* comes from our summaryview */
11249 SummaryView * summaryview = NULL;
11250 GSList * list = NULL, *cur = NULL;
11252 if (mainwindow_get_mainwindow())
11253 summaryview = mainwindow_get_mainwindow()->summaryview;
11256 list = summary_get_selected_msg_list(summaryview);
11258 for (cur = list; cur; cur = cur->next) {
11259 MsgInfo *msginfo = (MsgInfo *)cur->data;
11260 gchar *file = NULL;
11262 file = procmsg_get_message_file_full(msginfo,
11265 compose_attach_append(compose, (const gchar *)file,
11266 (const gchar *)file, "message/rfc822", NULL);
11270 g_slist_free(list);
11274 static gboolean compose_drag_drop(GtkWidget *widget,
11275 GdkDragContext *drag_context,
11277 guint time, gpointer user_data)
11279 /* not handling this signal makes compose_insert_drag_received_cb
11284 static gboolean completion_set_focus_to_subject
11285 (GtkWidget *widget,
11286 GdkEventKey *event,
11289 cm_return_val_if_fail(compose != NULL, FALSE);
11291 /* make backtab move to subject field */
11292 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11293 gtk_widget_grab_focus(compose->subject_entry);
11299 static void compose_insert_drag_received_cb (GtkWidget *widget,
11300 GdkDragContext *drag_context,
11303 GtkSelectionData *data,
11306 gpointer user_data)
11308 Compose *compose = (Compose *)user_data;
11314 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11316 type = gtk_selection_data_get_data_type(data);
11317 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11318 AlertValue val = G_ALERTDEFAULT;
11319 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11321 list = uri_list_extract_filenames(ddata);
11322 num_files = g_list_length(list);
11323 if (list == NULL && strstr(ddata, "://")) {
11324 /* Assume a list of no files, and data has ://, is a remote link */
11325 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11326 gchar *tmpfile = get_tmp_file();
11327 str_write_to_file(tmpdata, tmpfile);
11329 compose_insert_file(compose, tmpfile);
11330 claws_unlink(tmpfile);
11332 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11333 compose_beautify_paragraph(compose, NULL, TRUE);
11336 switch (prefs_common.compose_dnd_mode) {
11337 case COMPOSE_DND_ASK:
11338 msg = g_strdup_printf(
11340 "Do you want to insert the contents of the file "
11341 "into the message body, or attach it to the email?",
11342 "Do you want to insert the contents of the %d files "
11343 "into the message body, or attach them to the email?",
11346 val = alertpanel_full(_("Insert or attach?"), msg,
11347 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11348 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11351 case COMPOSE_DND_INSERT:
11352 val = G_ALERTALTERNATE;
11354 case COMPOSE_DND_ATTACH:
11355 val = G_ALERTOTHER;
11358 /* unexpected case */
11359 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11362 if (val & G_ALERTDISABLE) {
11363 val &= ~G_ALERTDISABLE;
11364 /* remember what action to perform by default, only if we don't click Cancel */
11365 if (val == G_ALERTALTERNATE)
11366 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11367 else if (val == G_ALERTOTHER)
11368 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11371 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11372 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11373 list_free_strings(list);
11376 } else if (val == G_ALERTOTHER) {
11377 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11378 list_free_strings(list);
11383 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11384 compose_insert_file(compose, (const gchar *)tmp->data);
11386 list_free_strings(list);
11388 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11393 static void compose_header_drag_received_cb (GtkWidget *widget,
11394 GdkDragContext *drag_context,
11397 GtkSelectionData *data,
11400 gpointer user_data)
11402 GtkEditable *entry = (GtkEditable *)user_data;
11403 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11405 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11408 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11409 gchar *decoded=g_new(gchar, strlen(email));
11412 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11413 gtk_editable_delete_text(entry, 0, -1);
11414 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11415 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11419 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11422 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11424 Compose *compose = (Compose *)data;
11426 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11427 compose->return_receipt = TRUE;
11429 compose->return_receipt = FALSE;
11432 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11434 Compose *compose = (Compose *)data;
11436 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11437 compose->remove_references = TRUE;
11439 compose->remove_references = FALSE;
11442 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11443 ComposeHeaderEntry *headerentry)
11445 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11449 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11450 GdkEventKey *event,
11451 ComposeHeaderEntry *headerentry)
11453 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11454 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11455 !(event->state & GDK_MODIFIER_MASK) &&
11456 (event->keyval == GDK_KEY_BackSpace) &&
11457 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11458 gtk_container_remove
11459 (GTK_CONTAINER(headerentry->compose->header_table),
11460 headerentry->combo);
11461 gtk_container_remove
11462 (GTK_CONTAINER(headerentry->compose->header_table),
11463 headerentry->entry);
11464 headerentry->compose->header_list =
11465 g_slist_remove(headerentry->compose->header_list,
11467 g_free(headerentry);
11468 } else if (event->keyval == GDK_KEY_Tab) {
11469 if (headerentry->compose->header_last == headerentry) {
11470 /* Override default next focus, and give it to subject_entry
11471 * instead of notebook tabs
11473 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11474 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11481 static gboolean scroll_postpone(gpointer data)
11483 Compose *compose = (Compose *)data;
11485 if (compose->batch)
11488 GTK_EVENTS_FLUSH();
11489 compose_show_first_last_header(compose, FALSE);
11493 static void compose_headerentry_changed_cb(GtkWidget *entry,
11494 ComposeHeaderEntry *headerentry)
11496 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11497 compose_create_header_entry(headerentry->compose);
11498 g_signal_handlers_disconnect_matched
11499 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11500 0, 0, NULL, NULL, headerentry);
11502 if (!headerentry->compose->batch)
11503 g_timeout_add(0, scroll_postpone, headerentry->compose);
11507 static gboolean compose_defer_auto_save_draft(Compose *compose)
11509 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11510 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11514 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11516 GtkAdjustment *vadj;
11518 cm_return_if_fail(compose);
11523 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11524 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11525 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11526 gtk_widget_get_parent(compose->header_table)));
11527 gtk_adjustment_set_value(vadj, (show_first ?
11528 gtk_adjustment_get_lower(vadj) :
11529 (gtk_adjustment_get_upper(vadj) -
11530 gtk_adjustment_get_page_size(vadj))));
11531 gtk_adjustment_changed(vadj);
11534 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11535 const gchar *text, gint len, Compose *compose)
11537 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11538 (G_OBJECT(compose->text), "paste_as_quotation"));
11541 cm_return_if_fail(text != NULL);
11543 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11544 G_CALLBACK(text_inserted),
11546 if (paste_as_quotation) {
11548 const gchar *qmark;
11550 GtkTextIter start_iter;
11553 len = strlen(text);
11555 new_text = g_strndup(text, len);
11557 qmark = compose_quote_char_from_context(compose);
11559 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11560 gtk_text_buffer_place_cursor(buffer, iter);
11562 pos = gtk_text_iter_get_offset(iter);
11564 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11565 _("Quote format error at line %d."));
11566 quote_fmt_reset_vartable();
11568 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11569 GINT_TO_POINTER(paste_as_quotation - 1));
11571 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11572 gtk_text_buffer_place_cursor(buffer, iter);
11573 gtk_text_buffer_delete_mark(buffer, mark);
11575 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11576 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11577 compose_beautify_paragraph(compose, &start_iter, FALSE);
11578 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11579 gtk_text_buffer_delete_mark(buffer, mark);
11581 if (strcmp(text, "\n") || compose->automatic_break
11582 || gtk_text_iter_starts_line(iter)) {
11583 GtkTextIter before_ins;
11584 gtk_text_buffer_insert(buffer, iter, text, len);
11585 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11586 before_ins = *iter;
11587 gtk_text_iter_backward_chars(&before_ins, len);
11588 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11591 /* check if the preceding is just whitespace or quote */
11592 GtkTextIter start_line;
11593 gchar *tmp = NULL, *quote = NULL;
11594 gint quote_len = 0, is_normal = 0;
11595 start_line = *iter;
11596 gtk_text_iter_set_line_offset(&start_line, 0);
11597 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11600 if (*tmp == '\0') {
11603 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11611 gtk_text_buffer_insert(buffer, iter, text, len);
11613 gtk_text_buffer_insert_with_tags_by_name(buffer,
11614 iter, text, len, "no_join", NULL);
11619 if (!paste_as_quotation) {
11620 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11621 compose_beautify_paragraph(compose, iter, FALSE);
11622 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11623 gtk_text_buffer_delete_mark(buffer, mark);
11626 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11627 G_CALLBACK(text_inserted),
11629 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11631 if (compose_can_autosave(compose) &&
11632 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11633 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11634 compose->draft_timeout_tag = g_timeout_add
11635 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11639 static void compose_check_all(GtkAction *action, gpointer data)
11641 Compose *compose = (Compose *)data;
11642 if (!compose->gtkaspell)
11645 if (gtk_widget_has_focus(compose->subject_entry))
11646 claws_spell_entry_check_all(
11647 CLAWS_SPELL_ENTRY(compose->subject_entry));
11649 gtkaspell_check_all(compose->gtkaspell);
11652 static void compose_highlight_all(GtkAction *action, gpointer data)
11654 Compose *compose = (Compose *)data;
11655 if (compose->gtkaspell) {
11656 claws_spell_entry_recheck_all(
11657 CLAWS_SPELL_ENTRY(compose->subject_entry));
11658 gtkaspell_highlight_all(compose->gtkaspell);
11662 static void compose_check_backwards(GtkAction *action, gpointer data)
11664 Compose *compose = (Compose *)data;
11665 if (!compose->gtkaspell) {
11666 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11670 if (gtk_widget_has_focus(compose->subject_entry))
11671 claws_spell_entry_check_backwards(
11672 CLAWS_SPELL_ENTRY(compose->subject_entry));
11674 gtkaspell_check_backwards(compose->gtkaspell);
11677 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11679 Compose *compose = (Compose *)data;
11680 if (!compose->gtkaspell) {
11681 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11685 if (gtk_widget_has_focus(compose->subject_entry))
11686 claws_spell_entry_check_forwards_go(
11687 CLAWS_SPELL_ENTRY(compose->subject_entry));
11689 gtkaspell_check_forwards_go(compose->gtkaspell);
11694 *\brief Guess originating forward account from MsgInfo and several
11695 * "common preference" settings. Return NULL if no guess.
11697 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11699 PrefsAccount *account = NULL;
11701 cm_return_val_if_fail(msginfo, NULL);
11702 cm_return_val_if_fail(msginfo->folder, NULL);
11703 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11705 if (msginfo->folder->prefs->enable_default_account)
11706 account = account_find_from_id(msginfo->folder->prefs->default_account);
11708 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11710 Xstrdup_a(to, msginfo->to, return NULL);
11711 extract_address(to);
11712 account = account_find_from_address(to, FALSE);
11715 if (!account && prefs_common.forward_account_autosel) {
11716 gchar cc[BUFFSIZE];
11717 if (!procheader_get_header_from_msginfo
11718 (msginfo, cc,sizeof cc , "Cc:")) {
11719 gchar *buf = cc + strlen("Cc:");
11720 extract_address(buf);
11721 account = account_find_from_address(buf, FALSE);
11725 if (!account && prefs_common.forward_account_autosel) {
11726 gchar deliveredto[BUFFSIZE];
11727 if (!procheader_get_header_from_msginfo
11728 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11729 gchar *buf = deliveredto + strlen("Delivered-To:");
11730 extract_address(buf);
11731 account = account_find_from_address(buf, FALSE);
11736 account = msginfo->folder->folder->account;
11741 gboolean compose_close(Compose *compose)
11745 cm_return_val_if_fail(compose, FALSE);
11747 if (!g_mutex_trylock(compose->mutex)) {
11748 /* we have to wait for the (possibly deferred by auto-save)
11749 * drafting to be done, before destroying the compose under
11751 debug_print("waiting for drafting to finish...\n");
11752 compose_allow_user_actions(compose, FALSE);
11753 if (compose->close_timeout_tag == 0) {
11754 compose->close_timeout_tag =
11755 g_timeout_add (500, (GSourceFunc) compose_close,
11761 if (compose->draft_timeout_tag >= 0) {
11762 g_source_remove(compose->draft_timeout_tag);
11763 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11766 gtkut_widget_get_uposition(compose->window, &x, &y);
11767 if (!compose->batch) {
11768 prefs_common.compose_x = x;
11769 prefs_common.compose_y = y;
11771 g_mutex_unlock(compose->mutex);
11772 compose_destroy(compose);
11777 * Add entry field for each address in list.
11778 * \param compose E-Mail composition object.
11779 * \param listAddress List of (formatted) E-Mail addresses.
11781 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11784 node = listAddress;
11786 addr = ( gchar * ) node->data;
11787 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11788 node = g_list_next( node );
11792 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11793 guint action, gboolean opening_multiple)
11795 gchar *body = NULL;
11796 GSList *new_msglist = NULL;
11797 MsgInfo *tmp_msginfo = NULL;
11798 gboolean originally_enc = FALSE;
11799 gboolean originally_sig = FALSE;
11800 Compose *compose = NULL;
11801 gchar *s_system = NULL;
11803 cm_return_if_fail(msgview != NULL);
11805 cm_return_if_fail(msginfo_list != NULL);
11807 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11808 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11809 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11811 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11812 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11813 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11814 orig_msginfo, mimeinfo);
11815 if (tmp_msginfo != NULL) {
11816 new_msglist = g_slist_append(NULL, tmp_msginfo);
11818 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11819 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11820 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11822 tmp_msginfo->folder = orig_msginfo->folder;
11823 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11824 if (orig_msginfo->tags) {
11825 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11826 tmp_msginfo->folder->tags_dirty = TRUE;
11832 if (!opening_multiple)
11833 body = messageview_get_selection(msgview);
11836 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11837 procmsg_msginfo_free(&tmp_msginfo);
11838 g_slist_free(new_msglist);
11840 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11842 if (compose && originally_enc) {
11843 compose_force_encryption(compose, compose->account, FALSE, s_system);
11846 if (compose && originally_sig && compose->account->default_sign_reply) {
11847 compose_force_signing(compose, compose->account, s_system);
11851 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11854 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11857 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11858 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11859 GSList *cur = msginfo_list;
11860 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11861 "messages. Opening the windows "
11862 "could take some time. Do you "
11863 "want to continue?"),
11864 g_slist_length(msginfo_list));
11865 if (g_slist_length(msginfo_list) > 9
11866 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11867 != G_ALERTALTERNATE) {
11872 /* We'll open multiple compose windows */
11873 /* let the WM place the next windows */
11874 compose_force_window_origin = FALSE;
11875 for (; cur; cur = cur->next) {
11877 tmplist.data = cur->data;
11878 tmplist.next = NULL;
11879 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11881 compose_force_window_origin = TRUE;
11883 /* forwarding multiple mails as attachments is done via a
11884 * single compose window */
11885 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11889 void compose_check_for_email_account(Compose *compose)
11891 PrefsAccount *ac = NULL, *curr = NULL;
11897 if (compose->account && compose->account->protocol == A_NNTP) {
11898 ac = account_get_cur_account();
11899 if (ac->protocol == A_NNTP) {
11900 list = account_get_list();
11902 for( ; list != NULL ; list = g_list_next(list)) {
11903 curr = (PrefsAccount *) list->data;
11904 if (curr->protocol != A_NNTP) {
11910 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11915 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11916 const gchar *address)
11918 GSList *msginfo_list = NULL;
11919 gchar *body = messageview_get_selection(msgview);
11922 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11924 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11925 compose_check_for_email_account(compose);
11926 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11927 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11928 compose_reply_set_subject(compose, msginfo);
11931 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11934 void compose_set_position(Compose *compose, gint pos)
11936 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11938 gtkut_text_view_set_position(text, pos);
11941 gboolean compose_search_string(Compose *compose,
11942 const gchar *str, gboolean case_sens)
11944 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11946 return gtkut_text_view_search_string(text, str, case_sens);
11949 gboolean compose_search_string_backward(Compose *compose,
11950 const gchar *str, gboolean case_sens)
11952 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11954 return gtkut_text_view_search_string_backward(text, str, case_sens);
11957 /* allocate a msginfo structure and populate its data from a compose data structure */
11958 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11960 MsgInfo *newmsginfo;
11962 gchar buf[BUFFSIZE];
11964 cm_return_val_if_fail( compose != NULL, NULL );
11966 newmsginfo = procmsg_msginfo_new();
11969 get_rfc822_date(buf, sizeof(buf));
11970 newmsginfo->date = g_strdup(buf);
11973 if (compose->from_name) {
11974 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11975 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11979 if (compose->subject_entry)
11980 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11982 /* to, cc, reply-to, newsgroups */
11983 for (list = compose->header_list; list; list = list->next) {
11984 gchar *header = gtk_editable_get_chars(
11986 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11987 gchar *entry = gtk_editable_get_chars(
11988 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11990 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11991 if ( newmsginfo->to == NULL ) {
11992 newmsginfo->to = g_strdup(entry);
11993 } else if (entry && *entry) {
11994 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11995 g_free(newmsginfo->to);
11996 newmsginfo->to = tmp;
11999 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12000 if ( newmsginfo->cc == NULL ) {
12001 newmsginfo->cc = g_strdup(entry);
12002 } else if (entry && *entry) {
12003 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12004 g_free(newmsginfo->cc);
12005 newmsginfo->cc = tmp;
12008 if ( strcasecmp(header,
12009 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12010 if ( newmsginfo->newsgroups == NULL ) {
12011 newmsginfo->newsgroups = g_strdup(entry);
12012 } else if (entry && *entry) {
12013 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12014 g_free(newmsginfo->newsgroups);
12015 newmsginfo->newsgroups = tmp;
12023 /* other data is unset */
12029 /* update compose's dictionaries from folder dict settings */
12030 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12031 FolderItem *folder_item)
12033 cm_return_if_fail(compose != NULL);
12035 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12036 FolderItemPrefs *prefs = folder_item->prefs;
12038 if (prefs->enable_default_dictionary)
12039 gtkaspell_change_dict(compose->gtkaspell,
12040 prefs->default_dictionary, FALSE);
12041 if (folder_item->prefs->enable_default_alt_dictionary)
12042 gtkaspell_change_alt_dict(compose->gtkaspell,
12043 prefs->default_alt_dictionary);
12044 if (prefs->enable_default_dictionary
12045 || prefs->enable_default_alt_dictionary)
12046 compose_spell_menu_changed(compose);
12051 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12053 Compose *compose = (Compose *)data;
12055 cm_return_if_fail(compose != NULL);
12057 gtk_widget_grab_focus(compose->text);
12060 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12062 gtk_combo_box_popup(GTK_COMBO_BOX(data));