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_header_bgcolor = {
193 static GdkColor default_header_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_indicate (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_find_account (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);
1773 if (!account && !(account = compose_find_account(msginfo)))
1774 account = cur_account;
1776 if (!prefs_common.forward_as_attachment)
1777 mode = COMPOSE_FORWARD_INLINE;
1779 mode = COMPOSE_FORWARD;
1780 compose = compose_create(account, msginfo->folder, mode, batch);
1782 compose->updating = TRUE;
1783 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1784 if (!compose->fwdinfo)
1785 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1787 compose_extract_original_charset(compose);
1789 if (msginfo->subject && *msginfo->subject) {
1790 gchar *buf, *buf2, *p;
1792 buf = p = g_strdup(msginfo->subject);
1793 p += subject_get_prefix_length(p);
1794 memmove(buf, p, strlen(p) + 1);
1796 buf2 = g_strdup_printf("Fw: %s", buf);
1797 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1803 /* override from name according to folder properties */
1804 if (msginfo->folder && msginfo->folder->prefs &&
1805 msginfo->folder->prefs->forward_with_format &&
1806 msginfo->folder->prefs->forward_override_from_format &&
1807 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1811 MsgInfo *full_msginfo = NULL;
1814 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1816 full_msginfo = procmsg_msginfo_copy(msginfo);
1818 /* decode \-escape sequences in the internal representation of the quote format */
1819 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1820 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1823 gtkaspell_block_check(compose->gtkaspell);
1824 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1825 compose->gtkaspell);
1827 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1829 quote_fmt_scan_string(tmp);
1832 buf = quote_fmt_get_buffer();
1834 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1836 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1837 quote_fmt_reset_vartable();
1840 procmsg_msginfo_free(&full_msginfo);
1843 textview = GTK_TEXT_VIEW(compose->text);
1844 textbuf = gtk_text_view_get_buffer(textview);
1845 compose_create_tags(textview, compose);
1847 undo_block(compose->undostruct);
1851 msgfile = procmsg_get_message_file(msginfo);
1852 if (!is_file_exist(msgfile))
1853 g_warning("%s: file does not exist", msgfile);
1855 compose_attach_append(compose, msgfile, msgfile,
1856 "message/rfc822", NULL);
1860 const gchar *qmark = NULL;
1861 const gchar *body_fmt = NULL;
1862 MsgInfo *full_msginfo;
1864 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1866 full_msginfo = procmsg_msginfo_copy(msginfo);
1868 /* use the forward format of folder (if enabled), or the account's one
1869 (if enabled) or fallback to the global forward format, which is always
1870 enabled (even if empty), and use the relevant quotemark */
1871 if (msginfo->folder && msginfo->folder->prefs &&
1872 msginfo->folder->prefs->forward_with_format) {
1873 qmark = msginfo->folder->prefs->forward_quotemark;
1874 body_fmt = msginfo->folder->prefs->forward_body_format;
1876 } else if (account->forward_with_format) {
1877 qmark = account->forward_quotemark;
1878 body_fmt = account->forward_body_format;
1881 qmark = prefs_common.fw_quotemark;
1882 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1883 body_fmt = gettext(prefs_common.fw_quotefmt);
1888 /* empty quotemark is not allowed */
1889 if (qmark == NULL || *qmark == '\0')
1892 compose_quote_fmt(compose, full_msginfo,
1893 body_fmt, qmark, body, FALSE, TRUE,
1894 _("The body of the \"Forward\" template has an error at line %d."));
1895 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1896 quote_fmt_reset_vartable();
1897 compose_attach_parts(compose, msginfo);
1899 procmsg_msginfo_free(&full_msginfo);
1902 SIGNAL_BLOCK(textbuf);
1904 if (account->auto_sig)
1905 compose_insert_sig(compose, FALSE);
1907 compose_wrap_all(compose);
1910 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1911 gtkaspell_highlight_all(compose->gtkaspell);
1912 gtkaspell_unblock_check(compose->gtkaspell);
1914 SIGNAL_UNBLOCK(textbuf);
1916 cursor_pos = quote_fmt_get_cursor_pos();
1917 if (cursor_pos == -1)
1918 gtk_widget_grab_focus(compose->header_last->entry);
1920 gtk_widget_grab_focus(compose->text);
1922 if (!no_extedit && prefs_common.auto_exteditor)
1923 compose_exec_ext_editor(compose);
1926 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1927 gchar *folderidentifier;
1929 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1930 folderidentifier = folder_item_get_identifier(msginfo->folder);
1931 compose_set_save_to(compose, folderidentifier);
1932 g_free(folderidentifier);
1935 undo_unblock(compose->undostruct);
1937 compose->modified = FALSE;
1938 compose_set_title(compose);
1940 compose->updating = FALSE;
1941 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1942 SCROLL_TO_CURSOR(compose);
1944 if (compose->deferred_destroy) {
1945 compose_destroy(compose);
1949 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1954 #undef INSERT_FW_HEADER
1956 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1959 GtkTextView *textview;
1960 GtkTextBuffer *textbuf;
1964 gboolean single_mail = TRUE;
1966 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1968 if (g_slist_length(msginfo_list) > 1)
1969 single_mail = FALSE;
1971 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1972 if (((MsgInfo *)msginfo->data)->folder == NULL)
1975 /* guess account from first selected message */
1977 !(account = compose_find_account(msginfo_list->data)))
1978 account = cur_account;
1980 cm_return_val_if_fail(account != NULL, NULL);
1982 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1983 if (msginfo->data) {
1984 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1985 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1989 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1990 g_warning("no msginfo_list");
1994 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1996 compose->updating = TRUE;
1998 /* override from name according to folder properties */
1999 if (msginfo_list->data) {
2000 MsgInfo *msginfo = msginfo_list->data;
2002 if (msginfo->folder && msginfo->folder->prefs &&
2003 msginfo->folder->prefs->forward_with_format &&
2004 msginfo->folder->prefs->forward_override_from_format &&
2005 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2010 /* decode \-escape sequences in the internal representation of the quote format */
2011 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2012 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2016 compose->gtkaspell);
2018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2020 quote_fmt_scan_string(tmp);
2023 buf = quote_fmt_get_buffer();
2025 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2027 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2028 quote_fmt_reset_vartable();
2034 textview = GTK_TEXT_VIEW(compose->text);
2035 textbuf = gtk_text_view_get_buffer(textview);
2036 compose_create_tags(textview, compose);
2038 undo_block(compose->undostruct);
2039 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2040 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2042 if (!is_file_exist(msgfile))
2043 g_warning("%s: file does not exist", msgfile);
2045 compose_attach_append(compose, msgfile, msgfile,
2046 "message/rfc822", NULL);
2051 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2052 if (info->subject && *info->subject) {
2053 gchar *buf, *buf2, *p;
2055 buf = p = g_strdup(info->subject);
2056 p += subject_get_prefix_length(p);
2057 memmove(buf, p, strlen(p) + 1);
2059 buf2 = g_strdup_printf("Fw: %s", buf);
2060 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2066 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2067 _("Fw: multiple emails"));
2070 SIGNAL_BLOCK(textbuf);
2072 if (account->auto_sig)
2073 compose_insert_sig(compose, FALSE);
2075 compose_wrap_all(compose);
2077 SIGNAL_UNBLOCK(textbuf);
2079 gtk_text_buffer_get_start_iter(textbuf, &iter);
2080 gtk_text_buffer_place_cursor(textbuf, &iter);
2082 if (prefs_common.auto_exteditor)
2083 compose_exec_ext_editor(compose);
2085 gtk_widget_grab_focus(compose->header_last->entry);
2086 undo_unblock(compose->undostruct);
2087 compose->modified = FALSE;
2088 compose_set_title(compose);
2090 compose->updating = FALSE;
2091 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2092 SCROLL_TO_CURSOR(compose);
2094 if (compose->deferred_destroy) {
2095 compose_destroy(compose);
2099 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2104 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2106 GtkTextIter start = *iter;
2107 GtkTextIter end_iter;
2108 int start_pos = gtk_text_iter_get_offset(&start);
2110 if (!compose->account->sig_sep)
2113 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2114 start_pos+strlen(compose->account->sig_sep));
2116 /* check sig separator */
2117 str = gtk_text_iter_get_text(&start, &end_iter);
2118 if (!strcmp(str, compose->account->sig_sep)) {
2120 /* check end of line (\n) */
2121 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2122 start_pos+strlen(compose->account->sig_sep));
2123 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2124 start_pos+strlen(compose->account->sig_sep)+1);
2125 tmp = gtk_text_iter_get_text(&start, &end_iter);
2126 if (!strcmp(tmp,"\n")) {
2138 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2140 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2141 Compose *compose = (Compose *)data;
2142 FolderItem *old_item = NULL;
2143 FolderItem *new_item = NULL;
2144 gchar *old_id, *new_id;
2146 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2147 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2150 old_item = hookdata->item;
2151 new_item = hookdata->item2;
2153 old_id = folder_item_get_identifier(old_item);
2154 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2156 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2157 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2158 compose->targetinfo->folder = new_item;
2161 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2162 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2163 compose->replyinfo->folder = new_item;
2166 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2167 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2168 compose->fwdinfo->folder = new_item;
2176 static void compose_colorize_signature(Compose *compose)
2178 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2180 GtkTextIter end_iter;
2181 gtk_text_buffer_get_start_iter(buffer, &iter);
2182 while (gtk_text_iter_forward_line(&iter))
2183 if (compose_is_sig_separator(compose, buffer, &iter)) {
2184 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2185 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2189 #define BLOCK_WRAP() { \
2190 prev_autowrap = compose->autowrap; \
2191 buffer = gtk_text_view_get_buffer( \
2192 GTK_TEXT_VIEW(compose->text)); \
2193 compose->autowrap = FALSE; \
2195 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2196 G_CALLBACK(compose_changed_cb), \
2198 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2199 G_CALLBACK(text_inserted), \
2202 #define UNBLOCK_WRAP() { \
2203 compose->autowrap = prev_autowrap; \
2204 if (compose->autowrap) { \
2205 gint old = compose->draft_timeout_tag; \
2206 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2207 compose_wrap_all(compose); \
2208 compose->draft_timeout_tag = old; \
2211 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2212 G_CALLBACK(compose_changed_cb), \
2214 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2215 G_CALLBACK(text_inserted), \
2219 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2221 Compose *compose = NULL;
2222 PrefsAccount *account = NULL;
2223 GtkTextView *textview;
2224 GtkTextBuffer *textbuf;
2228 gchar buf[BUFFSIZE];
2229 gboolean use_signing = FALSE;
2230 gboolean use_encryption = FALSE;
2231 gchar *privacy_system = NULL;
2232 int priority = PRIORITY_NORMAL;
2233 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2234 gboolean autowrap = prefs_common.autowrap;
2235 gboolean autoindent = prefs_common.auto_indent;
2236 HeaderEntry *manual_headers = NULL;
2238 cm_return_val_if_fail(msginfo != NULL, NULL);
2239 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2241 if (compose_put_existing_to_front(msginfo)) {
2245 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2246 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2247 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2248 gchar queueheader_buf[BUFFSIZE];
2251 /* Select Account from queue headers */
2252 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2253 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2254 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2255 account = account_find_from_id(id);
2257 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2258 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2259 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2260 account = account_find_from_id(id);
2262 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2263 sizeof(queueheader_buf), "NAID:")) {
2264 id = atoi(&queueheader_buf[strlen("NAID:")]);
2265 account = account_find_from_id(id);
2267 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2268 sizeof(queueheader_buf), "MAID:")) {
2269 id = atoi(&queueheader_buf[strlen("MAID:")]);
2270 account = account_find_from_id(id);
2272 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2273 sizeof(queueheader_buf), "S:")) {
2274 account = account_find_from_address(queueheader_buf, FALSE);
2276 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2277 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2278 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2279 use_signing = param;
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2284 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2285 use_signing = param;
2288 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2289 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2290 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2291 use_encryption = param;
2293 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2294 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2295 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2296 use_encryption = param;
2298 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2299 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2300 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2303 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2304 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2305 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2308 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2309 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2310 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2312 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2313 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2314 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2316 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2317 sizeof(queueheader_buf), "X-Priority: ")) {
2318 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2321 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2322 sizeof(queueheader_buf), "RMID:")) {
2323 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2324 if (tokens[0] && tokens[1] && tokens[2]) {
2325 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2326 if (orig_item != NULL) {
2327 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2332 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2333 sizeof(queueheader_buf), "FMID:")) {
2334 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2335 if (tokens[0] && tokens[1] && tokens[2]) {
2336 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2337 if (orig_item != NULL) {
2338 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2343 /* Get manual headers */
2344 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2345 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2346 if (*listmh != '\0') {
2347 debug_print("Got manual headers: %s\n", listmh);
2348 manual_headers = procheader_entries_from_str(listmh);
2353 account = msginfo->folder->folder->account;
2356 if (!account && prefs_common.reedit_account_autosel) {
2357 gchar from[BUFFSIZE];
2358 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2359 extract_address(from);
2360 account = account_find_from_address(from, FALSE);
2364 account = cur_account;
2366 cm_return_val_if_fail(account != NULL, NULL);
2368 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2370 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2371 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2372 compose->autowrap = autowrap;
2373 compose->replyinfo = replyinfo;
2374 compose->fwdinfo = fwdinfo;
2376 compose->updating = TRUE;
2377 compose->priority = priority;
2379 if (privacy_system != NULL) {
2380 compose->privacy_system = privacy_system;
2381 compose_use_signing(compose, use_signing);
2382 compose_use_encryption(compose, use_encryption);
2383 compose_update_privacy_system_menu_item(compose, FALSE);
2385 activate_privacy_system(compose, account, FALSE);
2388 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2390 compose_extract_original_charset(compose);
2392 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2393 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2394 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2395 gchar queueheader_buf[BUFFSIZE];
2397 /* Set message save folder */
2398 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2399 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2400 compose_set_save_to(compose, &queueheader_buf[4]);
2402 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2403 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2405 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2410 if (compose_parse_header(compose, msginfo) < 0) {
2411 compose->updating = FALSE;
2412 compose_destroy(compose);
2415 compose_reedit_set_entry(compose, msginfo);
2417 textview = GTK_TEXT_VIEW(compose->text);
2418 textbuf = gtk_text_view_get_buffer(textview);
2419 compose_create_tags(textview, compose);
2421 mark = gtk_text_buffer_get_insert(textbuf);
2422 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2424 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2425 G_CALLBACK(compose_changed_cb),
2428 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2429 fp = procmime_get_first_encrypted_text_content(msginfo);
2431 compose_force_encryption(compose, account, TRUE, NULL);
2434 fp = procmime_get_first_text_content(msginfo);
2437 g_warning("Can't get text part");
2441 gboolean prev_autowrap;
2442 GtkTextBuffer *buffer;
2444 while (fgets(buf, sizeof(buf), fp) != NULL) {
2446 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2452 compose_attach_parts(compose, msginfo);
2454 compose_colorize_signature(compose);
2456 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2457 G_CALLBACK(compose_changed_cb),
2460 if (manual_headers != NULL) {
2461 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2462 procheader_entries_free(manual_headers);
2463 compose->updating = FALSE;
2464 compose_destroy(compose);
2467 procheader_entries_free(manual_headers);
2470 gtk_widget_grab_focus(compose->text);
2472 if (prefs_common.auto_exteditor) {
2473 compose_exec_ext_editor(compose);
2475 compose->modified = FALSE;
2476 compose_set_title(compose);
2478 compose->updating = FALSE;
2479 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2480 SCROLL_TO_CURSOR(compose);
2482 if (compose->deferred_destroy) {
2483 compose_destroy(compose);
2487 compose->sig_str = account_get_signature_str(compose->account);
2489 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2494 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2501 cm_return_val_if_fail(msginfo != NULL, NULL);
2504 account = account_get_reply_account(msginfo,
2505 prefs_common.reply_account_autosel);
2506 cm_return_val_if_fail(account != NULL, NULL);
2508 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2510 compose->updating = TRUE;
2512 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2513 compose->replyinfo = NULL;
2514 compose->fwdinfo = NULL;
2516 compose_show_first_last_header(compose, TRUE);
2518 gtk_widget_grab_focus(compose->header_last->entry);
2520 filename = procmsg_get_message_file(msginfo);
2522 if (filename == NULL) {
2523 compose->updating = FALSE;
2524 compose_destroy(compose);
2529 compose->redirect_filename = filename;
2531 /* Set save folder */
2532 item = msginfo->folder;
2533 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2534 gchar *folderidentifier;
2536 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2537 folderidentifier = folder_item_get_identifier(item);
2538 compose_set_save_to(compose, folderidentifier);
2539 g_free(folderidentifier);
2542 compose_attach_parts(compose, msginfo);
2544 if (msginfo->subject)
2545 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2547 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2549 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2550 _("The body of the \"Redirect\" template has an error at line %d."));
2551 quote_fmt_reset_vartable();
2552 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2554 compose_colorize_signature(compose);
2557 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2558 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2559 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2562 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2563 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2564 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2566 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2569 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2571 if (compose->toolbar->draft_btn)
2572 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2573 if (compose->toolbar->insert_btn)
2574 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2575 if (compose->toolbar->attach_btn)
2576 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2577 if (compose->toolbar->sig_btn)
2578 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2579 if (compose->toolbar->exteditor_btn)
2580 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2581 if (compose->toolbar->linewrap_current_btn)
2582 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2583 if (compose->toolbar->linewrap_all_btn)
2584 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2586 compose->modified = FALSE;
2587 compose_set_title(compose);
2588 compose->updating = FALSE;
2589 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2590 SCROLL_TO_CURSOR(compose);
2592 if (compose->deferred_destroy) {
2593 compose_destroy(compose);
2597 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2602 const GList *compose_get_compose_list(void)
2604 return compose_list;
2607 void compose_entry_append(Compose *compose, const gchar *address,
2608 ComposeEntryType type, ComposePrefType pref_type)
2610 const gchar *header;
2612 gboolean in_quote = FALSE;
2613 if (!address || *address == '\0') return;
2620 header = N_("Bcc:");
2622 case COMPOSE_REPLYTO:
2623 header = N_("Reply-To:");
2625 case COMPOSE_NEWSGROUPS:
2626 header = N_("Newsgroups:");
2628 case COMPOSE_FOLLOWUPTO:
2629 header = N_( "Followup-To:");
2631 case COMPOSE_INREPLYTO:
2632 header = N_( "In-Reply-To:");
2639 header = prefs_common_translated_header_name(header);
2641 cur = begin = (gchar *)address;
2643 /* we separate the line by commas, but not if we're inside a quoted
2645 while (*cur != '\0') {
2647 in_quote = !in_quote;
2648 if (*cur == ',' && !in_quote) {
2649 gchar *tmp = g_strdup(begin);
2651 tmp[cur-begin]='\0';
2654 while (*tmp == ' ' || *tmp == '\t')
2656 compose_add_header_entry(compose, header, tmp, pref_type);
2657 compose_entry_indicate(compose, tmp);
2664 gchar *tmp = g_strdup(begin);
2666 tmp[cur-begin]='\0';
2667 while (*tmp == ' ' || *tmp == '\t')
2669 compose_add_header_entry(compose, header, tmp, pref_type);
2670 compose_entry_indicate(compose, tmp);
2675 static void compose_entry_indicate(Compose *compose, const gchar *mailto)
2680 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2681 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2682 if (gtk_entry_get_text(entry) &&
2683 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2684 gtk_widget_modify_base(
2685 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2686 GTK_STATE_NORMAL, &default_header_bgcolor);
2687 gtk_widget_modify_text(
2688 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2689 GTK_STATE_NORMAL, &default_header_color);
2694 void compose_toolbar_cb(gint action, gpointer data)
2696 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2697 Compose *compose = (Compose*)toolbar_item->parent;
2699 cm_return_if_fail(compose != NULL);
2703 compose_send_cb(NULL, compose);
2706 compose_send_later_cb(NULL, compose);
2709 compose_draft(compose, COMPOSE_QUIT_EDITING);
2712 compose_insert_file_cb(NULL, compose);
2715 compose_attach_cb(NULL, compose);
2718 compose_insert_sig(compose, FALSE);
2721 compose_insert_sig(compose, TRUE);
2724 compose_ext_editor_cb(NULL, compose);
2726 case A_LINEWRAP_CURRENT:
2727 compose_beautify_paragraph(compose, NULL, TRUE);
2729 case A_LINEWRAP_ALL:
2730 compose_wrap_all_full(compose, TRUE);
2733 compose_address_cb(NULL, compose);
2736 case A_CHECK_SPELLING:
2737 compose_check_all(NULL, compose);
2745 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2750 gchar *subject = NULL;
2754 gchar **attach = NULL;
2755 gchar *inreplyto = NULL;
2756 MailField mfield = NO_FIELD_PRESENT;
2758 /* get mailto parts but skip from */
2759 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2762 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2763 mfield = TO_FIELD_PRESENT;
2766 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2768 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2770 if (!g_utf8_validate (subject, -1, NULL)) {
2771 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2775 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2777 mfield = SUBJECT_FIELD_PRESENT;
2780 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2781 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2784 gboolean prev_autowrap = compose->autowrap;
2786 compose->autowrap = FALSE;
2788 mark = gtk_text_buffer_get_insert(buffer);
2789 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2791 if (!g_utf8_validate (body, -1, NULL)) {
2792 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2793 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2796 gtk_text_buffer_insert(buffer, &iter, body, -1);
2798 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2800 compose->autowrap = prev_autowrap;
2801 if (compose->autowrap)
2802 compose_wrap_all(compose);
2803 mfield = BODY_FIELD_PRESENT;
2807 gint i = 0, att = 0;
2808 gchar *warn_files = NULL;
2809 while (attach[i] != NULL) {
2810 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2811 if (utf8_filename) {
2812 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2813 gchar *tmp = g_strdup_printf("%s%s\n",
2814 warn_files?warn_files:"",
2820 g_free(utf8_filename);
2822 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2827 alertpanel_notice(ngettext(
2828 "The following file has been attached: \n%s",
2829 "The following files have been attached: \n%s", att), warn_files);
2834 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2847 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2849 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2850 {"Cc:", NULL, TRUE},
2851 {"References:", NULL, FALSE},
2852 {"Bcc:", NULL, TRUE},
2853 {"Newsgroups:", NULL, TRUE},
2854 {"Followup-To:", NULL, TRUE},
2855 {"List-Post:", NULL, FALSE},
2856 {"X-Priority:", NULL, FALSE},
2857 {NULL, NULL, FALSE}};
2873 cm_return_val_if_fail(msginfo != NULL, -1);
2875 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2876 procheader_get_header_fields(fp, hentry);
2879 if (hentry[H_REPLY_TO].body != NULL) {
2880 if (hentry[H_REPLY_TO].body[0] != '\0') {
2882 conv_unmime_header(hentry[H_REPLY_TO].body,
2885 g_free(hentry[H_REPLY_TO].body);
2886 hentry[H_REPLY_TO].body = NULL;
2888 if (hentry[H_CC].body != NULL) {
2889 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2890 g_free(hentry[H_CC].body);
2891 hentry[H_CC].body = NULL;
2893 if (hentry[H_REFERENCES].body != NULL) {
2894 if (compose->mode == COMPOSE_REEDIT)
2895 compose->references = hentry[H_REFERENCES].body;
2897 compose->references = compose_parse_references
2898 (hentry[H_REFERENCES].body, msginfo->msgid);
2899 g_free(hentry[H_REFERENCES].body);
2901 hentry[H_REFERENCES].body = NULL;
2903 if (hentry[H_BCC].body != NULL) {
2904 if (compose->mode == COMPOSE_REEDIT)
2906 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2907 g_free(hentry[H_BCC].body);
2908 hentry[H_BCC].body = NULL;
2910 if (hentry[H_NEWSGROUPS].body != NULL) {
2911 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2912 hentry[H_NEWSGROUPS].body = NULL;
2914 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2915 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2916 compose->followup_to =
2917 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2920 g_free(hentry[H_FOLLOWUP_TO].body);
2921 hentry[H_FOLLOWUP_TO].body = NULL;
2923 if (hentry[H_LIST_POST].body != NULL) {
2924 gchar *to = NULL, *start = NULL;
2926 extract_address(hentry[H_LIST_POST].body);
2927 if (hentry[H_LIST_POST].body[0] != '\0') {
2928 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2930 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2931 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2934 g_free(compose->ml_post);
2935 compose->ml_post = to;
2938 g_free(hentry[H_LIST_POST].body);
2939 hentry[H_LIST_POST].body = NULL;
2942 /* CLAWS - X-Priority */
2943 if (compose->mode == COMPOSE_REEDIT)
2944 if (hentry[H_X_PRIORITY].body != NULL) {
2947 priority = atoi(hentry[H_X_PRIORITY].body);
2948 g_free(hentry[H_X_PRIORITY].body);
2950 hentry[H_X_PRIORITY].body = NULL;
2952 if (priority < PRIORITY_HIGHEST ||
2953 priority > PRIORITY_LOWEST)
2954 priority = PRIORITY_NORMAL;
2956 compose->priority = priority;
2959 if (compose->mode == COMPOSE_REEDIT) {
2960 if (msginfo->inreplyto && *msginfo->inreplyto)
2961 compose->inreplyto = g_strdup(msginfo->inreplyto);
2963 if (msginfo->msgid && *msginfo->msgid)
2964 compose->msgid = g_strdup(msginfo->msgid);
2966 if (msginfo->msgid && *msginfo->msgid)
2967 compose->inreplyto = g_strdup(msginfo->msgid);
2969 if (!compose->references) {
2970 if (msginfo->msgid && *msginfo->msgid) {
2971 if (msginfo->inreplyto && *msginfo->inreplyto)
2972 compose->references =
2973 g_strdup_printf("<%s>\n\t<%s>",
2977 compose->references =
2978 g_strconcat("<", msginfo->msgid, ">",
2980 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2981 compose->references =
2982 g_strconcat("<", msginfo->inreplyto, ">",
2991 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2996 cm_return_val_if_fail(msginfo != NULL, -1);
2998 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2999 procheader_get_header_fields(fp, entries);
3003 while (he != NULL && he->name != NULL) {
3005 GtkListStore *model = NULL;
3007 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3008 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3009 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3010 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3011 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3018 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3020 GSList *ref_id_list, *cur;
3024 ref_id_list = references_list_append(NULL, ref);
3025 if (!ref_id_list) return NULL;
3026 if (msgid && *msgid)
3027 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3032 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3033 /* "<" + Message-ID + ">" + CR+LF+TAB */
3034 len += strlen((gchar *)cur->data) + 5;
3036 if (len > MAX_REFERENCES_LEN) {
3037 /* remove second message-ID */
3038 if (ref_id_list && ref_id_list->next &&
3039 ref_id_list->next->next) {
3040 g_free(ref_id_list->next->data);
3041 ref_id_list = g_slist_remove
3042 (ref_id_list, ref_id_list->next->data);
3044 slist_free_strings_full(ref_id_list);
3051 new_ref = g_string_new("");
3052 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3053 if (new_ref->len > 0)
3054 g_string_append(new_ref, "\n\t");
3055 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3058 slist_free_strings_full(ref_id_list);
3060 new_ref_str = new_ref->str;
3061 g_string_free(new_ref, FALSE);
3066 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3067 const gchar *fmt, const gchar *qmark,
3068 const gchar *body, gboolean rewrap,
3069 gboolean need_unescape,
3070 const gchar *err_msg)
3072 MsgInfo* dummyinfo = NULL;
3073 gchar *quote_str = NULL;
3075 gboolean prev_autowrap;
3076 const gchar *trimmed_body = body;
3077 gint cursor_pos = -1;
3078 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3079 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3084 SIGNAL_BLOCK(buffer);
3087 dummyinfo = compose_msginfo_new_from_compose(compose);
3088 msginfo = dummyinfo;
3091 if (qmark != NULL) {
3093 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3094 compose->gtkaspell);
3096 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3098 quote_fmt_scan_string(qmark);
3101 buf = quote_fmt_get_buffer();
3103 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3105 Xstrdup_a(quote_str, buf, goto error)
3108 if (fmt && *fmt != '\0') {
3111 while (*trimmed_body == '\n')
3115 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3116 compose->gtkaspell);
3118 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3120 if (need_unescape) {
3123 /* decode \-escape sequences in the internal representation of the quote format */
3124 tmp = g_malloc(strlen(fmt)+1);
3125 pref_get_unescaped_pref(tmp, fmt);
3126 quote_fmt_scan_string(tmp);
3130 quote_fmt_scan_string(fmt);
3134 buf = quote_fmt_get_buffer();
3136 gint line = quote_fmt_get_line();
3137 alertpanel_error(err_msg, line);
3143 prev_autowrap = compose->autowrap;
3144 compose->autowrap = FALSE;
3146 mark = gtk_text_buffer_get_insert(buffer);
3147 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3148 if (g_utf8_validate(buf, -1, NULL)) {
3149 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3151 gchar *tmpout = NULL;
3152 tmpout = conv_codeset_strdup
3153 (buf, conv_get_locale_charset_str_no_utf8(),
3155 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3157 tmpout = g_malloc(strlen(buf)*2+1);
3158 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3160 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3164 cursor_pos = quote_fmt_get_cursor_pos();
3165 if (cursor_pos == -1)
3166 cursor_pos = gtk_text_iter_get_offset(&iter);
3167 compose->set_cursor_pos = cursor_pos;
3169 gtk_text_buffer_get_start_iter(buffer, &iter);
3170 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3171 gtk_text_buffer_place_cursor(buffer, &iter);
3173 compose->autowrap = prev_autowrap;
3174 if (compose->autowrap && rewrap)
3175 compose_wrap_all(compose);
3182 SIGNAL_UNBLOCK(buffer);
3184 procmsg_msginfo_free( &dummyinfo );
3189 /* if ml_post is of type addr@host and from is of type
3190 * addr-anything@host, return TRUE
3192 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3194 gchar *left_ml = NULL;
3195 gchar *right_ml = NULL;
3196 gchar *left_from = NULL;
3197 gchar *right_from = NULL;
3198 gboolean result = FALSE;
3200 if (!ml_post || !from)
3203 left_ml = g_strdup(ml_post);
3204 if (strstr(left_ml, "@")) {
3205 right_ml = strstr(left_ml, "@")+1;
3206 *(strstr(left_ml, "@")) = '\0';
3209 left_from = g_strdup(from);
3210 if (strstr(left_from, "@")) {
3211 right_from = strstr(left_from, "@")+1;
3212 *(strstr(left_from, "@")) = '\0';
3215 if (right_ml && right_from
3216 && !strncmp(left_from, left_ml, strlen(left_ml))
3217 && !strcmp(right_from, right_ml)) {
3226 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3227 gboolean respect_default_to)
3231 if (!folder || !folder->prefs)
3234 if (respect_default_to && folder->prefs->enable_default_to) {
3235 compose_entry_append(compose, folder->prefs->default_to,
3236 COMPOSE_TO, PREF_FOLDER);
3237 compose_entry_indicate(compose, folder->prefs->default_to);
3239 if (folder->prefs->enable_default_cc) {
3240 compose_entry_append(compose, folder->prefs->default_cc,
3241 COMPOSE_CC, PREF_FOLDER);
3242 compose_entry_indicate(compose, folder->prefs->default_cc);
3244 if (folder->prefs->enable_default_bcc) {
3245 compose_entry_append(compose, folder->prefs->default_bcc,
3246 COMPOSE_BCC, PREF_FOLDER);
3247 compose_entry_indicate(compose, folder->prefs->default_bcc);
3249 if (folder->prefs->enable_default_replyto) {
3250 compose_entry_append(compose, folder->prefs->default_replyto,
3251 COMPOSE_REPLYTO, PREF_FOLDER);
3252 compose_entry_indicate(compose, folder->prefs->default_replyto);
3256 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3261 if (!compose || !msginfo)
3264 if (msginfo->subject && *msginfo->subject) {
3265 buf = p = g_strdup(msginfo->subject);
3266 p += subject_get_prefix_length(p);
3267 memmove(buf, p, strlen(p) + 1);
3269 buf2 = g_strdup_printf("Re: %s", buf);
3270 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3275 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3278 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3279 gboolean to_all, gboolean to_ml,
3281 gboolean followup_and_reply_to)
3283 GSList *cc_list = NULL;
3286 gchar *replyto = NULL;
3287 gchar *ac_email = NULL;
3289 gboolean reply_to_ml = FALSE;
3290 gboolean default_reply_to = FALSE;
3292 cm_return_if_fail(compose->account != NULL);
3293 cm_return_if_fail(msginfo != NULL);
3295 reply_to_ml = to_ml && compose->ml_post;
3297 default_reply_to = msginfo->folder &&
3298 msginfo->folder->prefs->enable_default_reply_to;
3300 if (compose->account->protocol != A_NNTP) {
3301 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3303 if (reply_to_ml && !default_reply_to) {
3305 gboolean is_subscr = is_subscription(compose->ml_post,
3308 /* normal answer to ml post with a reply-to */
3309 compose_entry_append(compose,
3311 COMPOSE_TO, PREF_ML);
3312 if (compose->replyto)
3313 compose_entry_append(compose,
3315 COMPOSE_CC, PREF_ML);
3317 /* answer to subscription confirmation */
3318 if (compose->replyto)
3319 compose_entry_append(compose,
3321 COMPOSE_TO, PREF_ML);
3322 else if (msginfo->from)
3323 compose_entry_append(compose,
3325 COMPOSE_TO, PREF_ML);
3328 else if (!(to_all || to_sender) && default_reply_to) {
3329 compose_entry_append(compose,
3330 msginfo->folder->prefs->default_reply_to,
3331 COMPOSE_TO, PREF_FOLDER);
3332 compose_entry_indicate(compose,
3333 msginfo->folder->prefs->default_reply_to);
3339 compose_entry_append(compose, msginfo->from,
3340 COMPOSE_TO, PREF_NONE);
3342 Xstrdup_a(tmp1, msginfo->from, return);
3343 extract_address(tmp1);
3344 compose_entry_append(compose,
3345 (!account_find_from_address(tmp1, FALSE))
3348 COMPOSE_TO, PREF_NONE);
3349 if (compose->replyto)
3350 compose_entry_append(compose,
3352 COMPOSE_CC, PREF_NONE);
3354 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3355 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3356 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3357 if (compose->replyto) {
3358 compose_entry_append(compose,
3360 COMPOSE_TO, PREF_NONE);
3362 compose_entry_append(compose,
3363 msginfo->from ? msginfo->from : "",
3364 COMPOSE_TO, PREF_NONE);
3367 /* replying to own mail, use original recp */
3368 compose_entry_append(compose,
3369 msginfo->to ? msginfo->to : "",
3370 COMPOSE_TO, PREF_NONE);
3371 compose_entry_append(compose,
3372 msginfo->cc ? msginfo->cc : "",
3373 COMPOSE_CC, PREF_NONE);
3378 if (to_sender || (compose->followup_to &&
3379 !strncmp(compose->followup_to, "poster", 6)))
3380 compose_entry_append
3382 (compose->replyto ? compose->replyto :
3383 msginfo->from ? msginfo->from : ""),
3384 COMPOSE_TO, PREF_NONE);
3386 else if (followup_and_reply_to || to_all) {
3387 compose_entry_append
3389 (compose->replyto ? compose->replyto :
3390 msginfo->from ? msginfo->from : ""),
3391 COMPOSE_TO, PREF_NONE);
3393 compose_entry_append
3395 compose->followup_to ? compose->followup_to :
3396 compose->newsgroups ? compose->newsgroups : "",
3397 COMPOSE_NEWSGROUPS, PREF_NONE);
3399 compose_entry_append
3401 msginfo->cc ? msginfo->cc : "",
3402 COMPOSE_CC, PREF_NONE);
3405 compose_entry_append
3407 compose->followup_to ? compose->followup_to :
3408 compose->newsgroups ? compose->newsgroups : "",
3409 COMPOSE_NEWSGROUPS, PREF_NONE);
3411 compose_reply_set_subject(compose, msginfo);
3413 if (to_ml && compose->ml_post) return;
3414 if (!to_all || compose->account->protocol == A_NNTP) return;
3416 if (compose->replyto) {
3417 Xstrdup_a(replyto, compose->replyto, return);
3418 extract_address(replyto);
3420 if (msginfo->from) {
3421 Xstrdup_a(from, msginfo->from, return);
3422 extract_address(from);
3425 if (replyto && from)
3426 cc_list = address_list_append_with_comments(cc_list, from);
3427 if (to_all && msginfo->folder &&
3428 msginfo->folder->prefs->enable_default_reply_to)
3429 cc_list = address_list_append_with_comments(cc_list,
3430 msginfo->folder->prefs->default_reply_to);
3431 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3432 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3434 ac_email = g_utf8_strdown(compose->account->address, -1);
3437 for (cur = cc_list; cur != NULL; cur = cur->next) {
3438 gchar *addr = g_utf8_strdown(cur->data, -1);
3439 extract_address(addr);
3441 if (strcmp(ac_email, addr))
3442 compose_entry_append(compose, (gchar *)cur->data,
3443 COMPOSE_CC, PREF_NONE);
3445 debug_print("Cc address same as compose account's, ignoring\n");
3450 slist_free_strings_full(cc_list);
3456 #define SET_ENTRY(entry, str) \
3459 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3462 #define SET_ADDRESS(type, str) \
3465 compose_entry_append(compose, str, type, PREF_NONE); \
3468 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3470 cm_return_if_fail(msginfo != NULL);
3472 SET_ENTRY(subject_entry, msginfo->subject);
3473 SET_ENTRY(from_name, msginfo->from);
3474 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3475 SET_ADDRESS(COMPOSE_CC, compose->cc);
3476 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3477 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3478 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3479 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3481 compose_update_priority_menu_item(compose);
3482 compose_update_privacy_system_menu_item(compose, FALSE);
3483 compose_show_first_last_header(compose, TRUE);
3489 static void compose_insert_sig(Compose *compose, gboolean replace)
3491 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3492 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3494 GtkTextIter iter, iter_end;
3495 gint cur_pos, ins_pos;
3496 gboolean prev_autowrap;
3497 gboolean found = FALSE;
3498 gboolean exists = FALSE;
3500 cm_return_if_fail(compose->account != NULL);
3504 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3505 G_CALLBACK(compose_changed_cb),
3508 mark = gtk_text_buffer_get_insert(buffer);
3509 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3510 cur_pos = gtk_text_iter_get_offset (&iter);
3513 gtk_text_buffer_get_end_iter(buffer, &iter);
3515 exists = (compose->sig_str != NULL);
3518 GtkTextIter first_iter, start_iter, end_iter;
3520 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3522 if (!exists || compose->sig_str[0] == '\0')
3525 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3526 compose->signature_tag);
3529 /* include previous \n\n */
3530 gtk_text_iter_backward_chars(&first_iter, 1);
3531 start_iter = first_iter;
3532 end_iter = first_iter;
3534 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3535 compose->signature_tag);
3536 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3537 compose->signature_tag);
3539 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3545 g_free(compose->sig_str);
3546 compose->sig_str = account_get_signature_str(compose->account);
3548 cur_pos = gtk_text_iter_get_offset(&iter);
3550 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3551 g_free(compose->sig_str);
3552 compose->sig_str = NULL;
3554 if (compose->sig_inserted == FALSE)
3555 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3556 compose->sig_inserted = TRUE;
3558 cur_pos = gtk_text_iter_get_offset(&iter);
3559 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3561 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3562 gtk_text_iter_forward_chars(&iter, 1);
3563 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3564 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3566 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3567 cur_pos = gtk_text_buffer_get_char_count (buffer);
3570 /* put the cursor where it should be
3571 * either where the quote_fmt says, either where it was */
3572 if (compose->set_cursor_pos < 0)
3573 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3575 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3576 compose->set_cursor_pos);
3578 compose->set_cursor_pos = -1;
3579 gtk_text_buffer_place_cursor(buffer, &iter);
3580 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3581 G_CALLBACK(compose_changed_cb),
3587 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3590 GtkTextBuffer *buffer;
3593 const gchar *cur_encoding;
3594 gchar buf[BUFFSIZE];
3597 gboolean prev_autowrap;
3600 GString *file_contents = NULL;
3601 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3603 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3605 /* get the size of the file we are about to insert */
3606 ret = g_stat(file, &file_stat);
3608 gchar *shortfile = g_path_get_basename(file);
3609 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3611 return COMPOSE_INSERT_NO_FILE;
3612 } else if (prefs_common.warn_large_insert == TRUE) {
3614 /* ask user for confirmation if the file is large */
3615 if (prefs_common.warn_large_insert_size < 0 ||
3616 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3620 msg = g_strdup_printf(_("You are about to insert a file of %s "
3621 "in the message body. Are you sure you want to do that?"),
3622 to_human_readable(file_stat.st_size));
3623 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3624 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3627 /* do we ask for confirmation next time? */
3628 if (aval & G_ALERTDISABLE) {
3629 /* no confirmation next time, disable feature in preferences */
3630 aval &= ~G_ALERTDISABLE;
3631 prefs_common.warn_large_insert = FALSE;
3634 /* abort file insertion if user canceled action */
3635 if (aval != G_ALERTALTERNATE) {
3636 return COMPOSE_INSERT_NO_FILE;
3642 if ((fp = g_fopen(file, "rb")) == NULL) {
3643 FILE_OP_ERROR(file, "fopen");
3644 return COMPOSE_INSERT_READ_ERROR;
3647 prev_autowrap = compose->autowrap;
3648 compose->autowrap = FALSE;
3650 text = GTK_TEXT_VIEW(compose->text);
3651 buffer = gtk_text_view_get_buffer(text);
3652 mark = gtk_text_buffer_get_insert(buffer);
3653 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3655 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3656 G_CALLBACK(text_inserted),
3659 cur_encoding = conv_get_locale_charset_str_no_utf8();
3661 file_contents = g_string_new("");
3662 while (fgets(buf, sizeof(buf), fp) != NULL) {
3665 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3666 str = g_strdup(buf);
3668 codeconv_set_strict(TRUE);
3669 str = conv_codeset_strdup
3670 (buf, cur_encoding, CS_INTERNAL);
3671 codeconv_set_strict(FALSE);
3674 result = COMPOSE_INSERT_INVALID_CHARACTER;
3680 /* strip <CR> if DOS/Windows file,
3681 replace <CR> with <LF> if Macintosh file. */
3684 if (len > 0 && str[len - 1] != '\n') {
3686 if (str[len] == '\r') str[len] = '\n';
3689 file_contents = g_string_append(file_contents, str);
3693 if (result == COMPOSE_INSERT_SUCCESS) {
3694 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3696 compose_changed_cb(NULL, compose);
3697 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3698 G_CALLBACK(text_inserted),
3700 compose->autowrap = prev_autowrap;
3701 if (compose->autowrap)
3702 compose_wrap_all(compose);
3705 g_string_free(file_contents, TRUE);
3711 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3712 const gchar *filename,
3713 const gchar *content_type,
3714 const gchar *charset)
3722 GtkListStore *store;
3724 gboolean has_binary = FALSE;
3726 if (!is_file_exist(file)) {
3727 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3728 gboolean result = FALSE;
3729 if (file_from_uri && is_file_exist(file_from_uri)) {
3730 result = compose_attach_append(
3731 compose, file_from_uri,
3732 filename, content_type,
3735 g_free(file_from_uri);
3738 alertpanel_error("File %s doesn't exist\n", filename);
3741 if ((size = get_file_size(file)) < 0) {
3742 alertpanel_error("Can't get file size of %s\n", filename);
3746 /* In batch mode, we allow 0-length files to be attached no questions asked */
3747 if (size == 0 && !compose->batch) {
3748 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3749 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3750 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3751 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3754 if (aval != G_ALERTALTERNATE) {
3758 if ((fp = g_fopen(file, "rb")) == NULL) {
3759 alertpanel_error(_("Can't read %s."), filename);
3764 ainfo = g_new0(AttachInfo, 1);
3765 auto_ainfo = g_auto_pointer_new_with_free
3766 (ainfo, (GFreeFunc) compose_attach_info_free);
3767 ainfo->file = g_strdup(file);
3770 ainfo->content_type = g_strdup(content_type);
3771 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3773 MsgFlags flags = {0, 0};
3775 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3776 ainfo->encoding = ENC_7BIT;
3778 ainfo->encoding = ENC_8BIT;
3780 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3781 if (msginfo && msginfo->subject)
3782 name = g_strdup(msginfo->subject);
3784 name = g_path_get_basename(filename ? filename : file);
3786 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3788 procmsg_msginfo_free(&msginfo);
3790 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3791 ainfo->charset = g_strdup(charset);
3792 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3794 ainfo->encoding = ENC_BASE64;
3796 name = g_path_get_basename(filename ? filename : file);
3797 ainfo->name = g_strdup(name);
3801 ainfo->content_type = procmime_get_mime_type(file);
3802 if (!ainfo->content_type) {
3803 ainfo->content_type =
3804 g_strdup("application/octet-stream");
3805 ainfo->encoding = ENC_BASE64;
3806 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3808 procmime_get_encoding_for_text_file(file, &has_binary);
3810 ainfo->encoding = ENC_BASE64;
3811 name = g_path_get_basename(filename ? filename : file);
3812 ainfo->name = g_strdup(name);
3816 if (ainfo->name != NULL
3817 && !strcmp(ainfo->name, ".")) {
3818 g_free(ainfo->name);
3822 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3823 g_free(ainfo->content_type);
3824 ainfo->content_type = g_strdup("application/octet-stream");
3825 g_free(ainfo->charset);
3826 ainfo->charset = NULL;
3829 ainfo->size = (goffset)size;
3830 size_text = to_human_readable((goffset)size);
3832 store = GTK_LIST_STORE(gtk_tree_view_get_model
3833 (GTK_TREE_VIEW(compose->attach_clist)));
3835 gtk_list_store_append(store, &iter);
3836 gtk_list_store_set(store, &iter,
3837 COL_MIMETYPE, ainfo->content_type,
3838 COL_SIZE, size_text,
3839 COL_NAME, ainfo->name,
3840 COL_CHARSET, ainfo->charset,
3842 COL_AUTODATA, auto_ainfo,
3845 g_auto_pointer_free(auto_ainfo);
3846 compose_attach_update_label(compose);
3850 static void compose_use_signing(Compose *compose, gboolean use_signing)
3852 compose->use_signing = use_signing;
3853 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3856 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3858 compose->use_encryption = use_encryption;
3859 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3862 #define NEXT_PART_NOT_CHILD(info) \
3864 node = info->node; \
3865 while (node->children) \
3866 node = g_node_last_child(node); \
3867 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3870 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3874 MimeInfo *firsttext = NULL;
3875 MimeInfo *encrypted = NULL;
3878 const gchar *partname = NULL;
3880 mimeinfo = procmime_scan_message(msginfo);
3881 if (!mimeinfo) return;
3883 if (mimeinfo->node->children == NULL) {
3884 procmime_mimeinfo_free_all(&mimeinfo);
3888 /* find first content part */
3889 child = (MimeInfo *) mimeinfo->node->children->data;
3890 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3891 child = (MimeInfo *)child->node->children->data;
3894 if (child->type == MIMETYPE_TEXT) {
3896 debug_print("First text part found\n");
3897 } else if (compose->mode == COMPOSE_REEDIT &&
3898 child->type == MIMETYPE_APPLICATION &&
3899 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3900 encrypted = (MimeInfo *)child->node->parent->data;
3903 child = (MimeInfo *) mimeinfo->node->children->data;
3904 while (child != NULL) {
3907 if (child == encrypted) {
3908 /* skip this part of tree */
3909 NEXT_PART_NOT_CHILD(child);
3913 if (child->type == MIMETYPE_MULTIPART) {
3914 /* get the actual content */
3915 child = procmime_mimeinfo_next(child);
3919 if (child == firsttext) {
3920 child = procmime_mimeinfo_next(child);
3924 outfile = procmime_get_tmp_file_name(child);
3925 if ((err = procmime_get_part(outfile, child)) < 0)
3926 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3928 gchar *content_type;
3930 content_type = procmime_get_content_type_str(child->type, child->subtype);
3932 /* if we meet a pgp signature, we don't attach it, but
3933 * we force signing. */
3934 if ((strcmp(content_type, "application/pgp-signature") &&
3935 strcmp(content_type, "application/pkcs7-signature") &&
3936 strcmp(content_type, "application/x-pkcs7-signature"))
3937 || compose->mode == COMPOSE_REDIRECT) {
3938 partname = procmime_mimeinfo_get_parameter(child, "filename");
3939 if (partname == NULL)
3940 partname = procmime_mimeinfo_get_parameter(child, "name");
3941 if (partname == NULL)
3943 compose_attach_append(compose, outfile,
3944 partname, content_type,
3945 procmime_mimeinfo_get_parameter(child, "charset"));
3947 compose_force_signing(compose, compose->account, NULL);
3949 g_free(content_type);
3952 NEXT_PART_NOT_CHILD(child);
3954 procmime_mimeinfo_free_all(&mimeinfo);
3957 #undef NEXT_PART_NOT_CHILD
3962 WAIT_FOR_INDENT_CHAR,
3963 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3966 /* return indent length, we allow:
3967 indent characters followed by indent characters or spaces/tabs,
3968 alphabets and numbers immediately followed by indent characters,
3969 and the repeating sequences of the above
3970 If quote ends with multiple spaces, only the first one is included. */
3971 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3972 const GtkTextIter *start, gint *len)
3974 GtkTextIter iter = *start;
3978 IndentState state = WAIT_FOR_INDENT_CHAR;
3981 gint alnum_count = 0;
3982 gint space_count = 0;
3985 if (prefs_common.quote_chars == NULL) {
3989 while (!gtk_text_iter_ends_line(&iter)) {
3990 wc = gtk_text_iter_get_char(&iter);
3991 if (g_unichar_iswide(wc))
3993 clen = g_unichar_to_utf8(wc, ch);
3997 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3998 is_space = g_unichar_isspace(wc);
4000 if (state == WAIT_FOR_INDENT_CHAR) {
4001 if (!is_indent && !g_unichar_isalnum(wc))
4004 quote_len += alnum_count + space_count + 1;
4005 alnum_count = space_count = 0;
4006 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4009 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4010 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4014 else if (is_indent) {
4015 quote_len += alnum_count + space_count + 1;
4016 alnum_count = space_count = 0;
4019 state = WAIT_FOR_INDENT_CHAR;
4023 gtk_text_iter_forward_char(&iter);
4026 if (quote_len > 0 && space_count > 0)
4032 if (quote_len > 0) {
4034 gtk_text_iter_forward_chars(&iter, quote_len);
4035 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4041 /* return >0 if the line is itemized */
4042 static int compose_itemized_length(GtkTextBuffer *buffer,
4043 const GtkTextIter *start)
4045 GtkTextIter iter = *start;
4050 if (gtk_text_iter_ends_line(&iter))
4055 wc = gtk_text_iter_get_char(&iter);
4056 if (!g_unichar_isspace(wc))
4058 gtk_text_iter_forward_char(&iter);
4059 if (gtk_text_iter_ends_line(&iter))
4063 clen = g_unichar_to_utf8(wc, ch);
4067 if (!strchr("*-+", ch[0]))
4070 gtk_text_iter_forward_char(&iter);
4071 if (gtk_text_iter_ends_line(&iter))
4073 wc = gtk_text_iter_get_char(&iter);
4074 if (g_unichar_isspace(wc)) {
4080 /* return the string at the start of the itemization */
4081 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4082 const GtkTextIter *start)
4084 GtkTextIter iter = *start;
4087 GString *item_chars = g_string_new("");
4090 if (gtk_text_iter_ends_line(&iter))
4095 wc = gtk_text_iter_get_char(&iter);
4096 if (!g_unichar_isspace(wc))
4098 gtk_text_iter_forward_char(&iter);
4099 if (gtk_text_iter_ends_line(&iter))
4101 g_string_append_unichar(item_chars, wc);
4104 str = item_chars->str;
4105 g_string_free(item_chars, FALSE);
4109 /* return the number of spaces at a line's start */
4110 static int compose_left_offset_length(GtkTextBuffer *buffer,
4111 const GtkTextIter *start)
4113 GtkTextIter iter = *start;
4116 if (gtk_text_iter_ends_line(&iter))
4120 wc = gtk_text_iter_get_char(&iter);
4121 if (!g_unichar_isspace(wc))
4124 gtk_text_iter_forward_char(&iter);
4125 if (gtk_text_iter_ends_line(&iter))
4129 gtk_text_iter_forward_char(&iter);
4130 if (gtk_text_iter_ends_line(&iter))
4135 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4136 const GtkTextIter *start,
4137 GtkTextIter *break_pos,
4141 GtkTextIter iter = *start, line_end = *start;
4142 PangoLogAttr *attrs;
4149 gboolean can_break = FALSE;
4150 gboolean do_break = FALSE;
4151 gboolean was_white = FALSE;
4152 gboolean prev_dont_break = FALSE;
4154 gtk_text_iter_forward_to_line_end(&line_end);
4155 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4156 len = g_utf8_strlen(str, -1);
4160 g_warning("compose_get_line_break_pos: len = 0!");
4164 /* g_print("breaking line: %d: %s (len = %d)\n",
4165 gtk_text_iter_get_line(&iter), str, len); */
4167 attrs = g_new(PangoLogAttr, len + 1);
4169 pango_default_break(str, -1, NULL, attrs, len + 1);
4173 /* skip quote and leading spaces */
4174 for (i = 0; *p != '\0' && i < len; i++) {
4177 wc = g_utf8_get_char(p);
4178 if (i >= quote_len && !g_unichar_isspace(wc))
4180 if (g_unichar_iswide(wc))
4182 else if (*p == '\t')
4186 p = g_utf8_next_char(p);
4189 for (; *p != '\0' && i < len; i++) {
4190 PangoLogAttr *attr = attrs + i;
4194 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4197 was_white = attr->is_white;
4199 /* don't wrap URI */
4200 if ((uri_len = get_uri_len(p)) > 0) {
4202 if (pos > 0 && col > max_col) {
4212 wc = g_utf8_get_char(p);
4213 if (g_unichar_iswide(wc)) {
4215 if (prev_dont_break && can_break && attr->is_line_break)
4217 } else if (*p == '\t')
4221 if (pos > 0 && col > max_col) {
4226 if (*p == '-' || *p == '/')
4227 prev_dont_break = TRUE;
4229 prev_dont_break = FALSE;
4231 p = g_utf8_next_char(p);
4235 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4240 *break_pos = *start;
4241 gtk_text_iter_set_line_offset(break_pos, pos);
4246 static gboolean compose_join_next_line(Compose *compose,
4247 GtkTextBuffer *buffer,
4249 const gchar *quote_str)
4251 GtkTextIter iter_ = *iter, cur, prev, next, end;
4252 PangoLogAttr attrs[3];
4254 gchar *next_quote_str;
4257 gboolean keep_cursor = FALSE;
4259 if (!gtk_text_iter_forward_line(&iter_) ||
4260 gtk_text_iter_ends_line(&iter_)) {
4263 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4265 if ((quote_str || next_quote_str) &&
4266 strcmp2(quote_str, next_quote_str) != 0) {
4267 g_free(next_quote_str);
4270 g_free(next_quote_str);
4273 if (quote_len > 0) {
4274 gtk_text_iter_forward_chars(&end, quote_len);
4275 if (gtk_text_iter_ends_line(&end)) {
4280 /* don't join itemized lines */
4281 if (compose_itemized_length(buffer, &end) > 0) {
4285 /* don't join signature separator */
4286 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4289 /* delete quote str */
4291 gtk_text_buffer_delete(buffer, &iter_, &end);
4293 /* don't join line breaks put by the user */
4295 gtk_text_iter_backward_char(&cur);
4296 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4297 gtk_text_iter_forward_char(&cur);
4301 gtk_text_iter_forward_char(&cur);
4302 /* delete linebreak and extra spaces */
4303 while (gtk_text_iter_backward_char(&cur)) {
4304 wc1 = gtk_text_iter_get_char(&cur);
4305 if (!g_unichar_isspace(wc1))
4310 while (!gtk_text_iter_ends_line(&cur)) {
4311 wc1 = gtk_text_iter_get_char(&cur);
4312 if (!g_unichar_isspace(wc1))
4314 gtk_text_iter_forward_char(&cur);
4317 if (!gtk_text_iter_equal(&prev, &next)) {
4320 mark = gtk_text_buffer_get_insert(buffer);
4321 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4322 if (gtk_text_iter_equal(&prev, &cur))
4324 gtk_text_buffer_delete(buffer, &prev, &next);
4328 /* insert space if required */
4329 gtk_text_iter_backward_char(&prev);
4330 wc1 = gtk_text_iter_get_char(&prev);
4331 wc2 = gtk_text_iter_get_char(&next);
4332 gtk_text_iter_forward_char(&next);
4333 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4334 pango_default_break(str, -1, NULL, attrs, 3);
4335 if (!attrs[1].is_line_break ||
4336 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4337 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4339 gtk_text_iter_backward_char(&iter_);
4340 gtk_text_buffer_place_cursor(buffer, &iter_);
4349 #define ADD_TXT_POS(bp_, ep_, pti_) \
4350 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4351 last = last->next; \
4352 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4353 last->next = NULL; \
4355 g_warning("alloc error scanning URIs"); \
4358 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4360 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4361 GtkTextBuffer *buffer;
4362 GtkTextIter iter, break_pos, end_of_line;
4363 gchar *quote_str = NULL;
4365 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4366 gboolean prev_autowrap = compose->autowrap;
4367 gint startq_offset = -1, noq_offset = -1;
4368 gint uri_start = -1, uri_stop = -1;
4369 gint nouri_start = -1, nouri_stop = -1;
4370 gint num_blocks = 0;
4371 gint quotelevel = -1;
4372 gboolean modified = force;
4373 gboolean removed = FALSE;
4374 gboolean modified_before_remove = FALSE;
4376 gboolean start = TRUE;
4377 gint itemized_len = 0, rem_item_len = 0;
4378 gchar *itemized_chars = NULL;
4379 gboolean item_continuation = FALSE;
4384 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4388 compose->autowrap = FALSE;
4390 buffer = gtk_text_view_get_buffer(text);
4391 undo_wrapping(compose->undostruct, TRUE);
4396 mark = gtk_text_buffer_get_insert(buffer);
4397 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4401 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4402 if (gtk_text_iter_ends_line(&iter)) {
4403 while (gtk_text_iter_ends_line(&iter) &&
4404 gtk_text_iter_forward_line(&iter))
4407 while (gtk_text_iter_backward_line(&iter)) {
4408 if (gtk_text_iter_ends_line(&iter)) {
4409 gtk_text_iter_forward_line(&iter);
4415 /* move to line start */
4416 gtk_text_iter_set_line_offset(&iter, 0);
4419 itemized_len = compose_itemized_length(buffer, &iter);
4421 if (!itemized_len) {
4422 itemized_len = compose_left_offset_length(buffer, &iter);
4423 item_continuation = TRUE;
4427 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4429 /* go until paragraph end (empty line) */
4430 while (start || !gtk_text_iter_ends_line(&iter)) {
4431 gchar *scanpos = NULL;
4432 /* parse table - in order of priority */
4434 const gchar *needle; /* token */
4436 /* token search function */
4437 gchar *(*search) (const gchar *haystack,
4438 const gchar *needle);
4439 /* part parsing function */
4440 gboolean (*parse) (const gchar *start,
4441 const gchar *scanpos,
4445 /* part to URI function */
4446 gchar *(*build_uri) (const gchar *bp,
4450 static struct table parser[] = {
4451 {"http://", strcasestr, get_uri_part, make_uri_string},
4452 {"https://", strcasestr, get_uri_part, make_uri_string},
4453 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4454 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4455 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4456 {"www.", strcasestr, get_uri_part, make_http_string},
4457 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4458 {"@", strcasestr, get_email_part, make_email_string}
4460 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4461 gint last_index = PARSE_ELEMS;
4463 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4467 if (!prev_autowrap && num_blocks == 0) {
4469 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4470 G_CALLBACK(text_inserted),
4473 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4476 uri_start = uri_stop = -1;
4478 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4481 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4482 if (startq_offset == -1)
4483 startq_offset = gtk_text_iter_get_offset(&iter);
4484 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4485 if (quotelevel > 2) {
4486 /* recycle colors */
4487 if (prefs_common.recycle_quote_colors)
4496 if (startq_offset == -1)
4497 noq_offset = gtk_text_iter_get_offset(&iter);
4501 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4504 if (gtk_text_iter_ends_line(&iter)) {
4506 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4507 prefs_common.linewrap_len,
4509 GtkTextIter prev, next, cur;
4510 if (prev_autowrap != FALSE || force) {
4511 compose->automatic_break = TRUE;
4513 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4514 compose->automatic_break = FALSE;
4515 if (itemized_len && compose->autoindent) {
4516 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4517 if (!item_continuation)
4518 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4520 } else if (quote_str && wrap_quote) {
4521 compose->automatic_break = TRUE;
4523 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4524 compose->automatic_break = FALSE;
4525 if (itemized_len && compose->autoindent) {
4526 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4527 if (!item_continuation)
4528 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4532 /* remove trailing spaces */
4534 rem_item_len = itemized_len;
4535 while (compose->autoindent && rem_item_len-- > 0)
4536 gtk_text_iter_backward_char(&cur);
4537 gtk_text_iter_backward_char(&cur);
4540 while (!gtk_text_iter_starts_line(&cur)) {
4543 gtk_text_iter_backward_char(&cur);
4544 wc = gtk_text_iter_get_char(&cur);
4545 if (!g_unichar_isspace(wc))
4549 if (!gtk_text_iter_equal(&prev, &next)) {
4550 gtk_text_buffer_delete(buffer, &prev, &next);
4552 gtk_text_iter_forward_char(&break_pos);
4556 gtk_text_buffer_insert(buffer, &break_pos,
4560 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4562 /* move iter to current line start */
4563 gtk_text_iter_set_line_offset(&iter, 0);
4570 /* move iter to next line start */
4576 if (!prev_autowrap && num_blocks > 0) {
4578 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4579 G_CALLBACK(text_inserted),
4583 while (!gtk_text_iter_ends_line(&end_of_line)) {
4584 gtk_text_iter_forward_char(&end_of_line);
4586 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4588 nouri_start = gtk_text_iter_get_offset(&iter);
4589 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4591 walk_pos = gtk_text_iter_get_offset(&iter);
4592 /* FIXME: this looks phony. scanning for anything in the parse table */
4593 for (n = 0; n < PARSE_ELEMS; n++) {
4596 tmp = parser[n].search(walk, parser[n].needle);
4598 if (scanpos == NULL || tmp < scanpos) {
4607 /* check if URI can be parsed */
4608 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4609 (const gchar **)&ep, FALSE)
4610 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4614 strlen(parser[last_index].needle);
4617 uri_start = walk_pos + (bp - o_walk);
4618 uri_stop = walk_pos + (ep - o_walk);
4622 gtk_text_iter_forward_line(&iter);
4625 if (startq_offset != -1) {
4626 GtkTextIter startquote, endquote;
4627 gtk_text_buffer_get_iter_at_offset(
4628 buffer, &startquote, startq_offset);
4631 switch (quotelevel) {
4633 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4634 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4635 gtk_text_buffer_apply_tag_by_name(
4636 buffer, "quote0", &startquote, &endquote);
4637 gtk_text_buffer_remove_tag_by_name(
4638 buffer, "quote1", &startquote, &endquote);
4639 gtk_text_buffer_remove_tag_by_name(
4640 buffer, "quote2", &startquote, &endquote);
4645 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4646 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4647 gtk_text_buffer_apply_tag_by_name(
4648 buffer, "quote1", &startquote, &endquote);
4649 gtk_text_buffer_remove_tag_by_name(
4650 buffer, "quote0", &startquote, &endquote);
4651 gtk_text_buffer_remove_tag_by_name(
4652 buffer, "quote2", &startquote, &endquote);
4657 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4658 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4659 gtk_text_buffer_apply_tag_by_name(
4660 buffer, "quote2", &startquote, &endquote);
4661 gtk_text_buffer_remove_tag_by_name(
4662 buffer, "quote0", &startquote, &endquote);
4663 gtk_text_buffer_remove_tag_by_name(
4664 buffer, "quote1", &startquote, &endquote);
4670 } else if (noq_offset != -1) {
4671 GtkTextIter startnoquote, endnoquote;
4672 gtk_text_buffer_get_iter_at_offset(
4673 buffer, &startnoquote, noq_offset);
4676 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4677 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4678 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4679 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4680 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4681 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4682 gtk_text_buffer_remove_tag_by_name(
4683 buffer, "quote0", &startnoquote, &endnoquote);
4684 gtk_text_buffer_remove_tag_by_name(
4685 buffer, "quote1", &startnoquote, &endnoquote);
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer, "quote2", &startnoquote, &endnoquote);
4693 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4694 GtkTextIter nouri_start_iter, nouri_end_iter;
4695 gtk_text_buffer_get_iter_at_offset(
4696 buffer, &nouri_start_iter, nouri_start);
4697 gtk_text_buffer_get_iter_at_offset(
4698 buffer, &nouri_end_iter, nouri_stop);
4699 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4700 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4701 gtk_text_buffer_remove_tag_by_name(
4702 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4703 modified_before_remove = modified;
4708 if (uri_start >= 0 && uri_stop > 0) {
4709 GtkTextIter uri_start_iter, uri_end_iter, back;
4710 gtk_text_buffer_get_iter_at_offset(
4711 buffer, &uri_start_iter, uri_start);
4712 gtk_text_buffer_get_iter_at_offset(
4713 buffer, &uri_end_iter, uri_stop);
4714 back = uri_end_iter;
4715 gtk_text_iter_backward_char(&back);
4716 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4717 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4718 gtk_text_buffer_apply_tag_by_name(
4719 buffer, "link", &uri_start_iter, &uri_end_iter);
4721 if (removed && !modified_before_remove) {
4727 // debug_print("not modified, out after %d lines\n", lines);
4731 // debug_print("modified, out after %d lines\n", lines);
4733 g_free(itemized_chars);
4736 undo_wrapping(compose->undostruct, FALSE);
4737 compose->autowrap = prev_autowrap;
4742 void compose_action_cb(void *data)
4744 Compose *compose = (Compose *)data;
4745 compose_wrap_all(compose);
4748 static void compose_wrap_all(Compose *compose)
4750 compose_wrap_all_full(compose, FALSE);
4753 static void compose_wrap_all_full(Compose *compose, gboolean force)
4755 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4756 GtkTextBuffer *buffer;
4758 gboolean modified = TRUE;
4760 buffer = gtk_text_view_get_buffer(text);
4762 gtk_text_buffer_get_start_iter(buffer, &iter);
4764 undo_wrapping(compose->undostruct, TRUE);
4766 while (!gtk_text_iter_is_end(&iter) && modified)
4767 modified = compose_beautify_paragraph(compose, &iter, force);
4769 undo_wrapping(compose->undostruct, FALSE);
4773 static void compose_set_title(Compose *compose)
4779 edited = compose->modified ? _(" [Edited]") : "";
4781 subject = gtk_editable_get_chars(
4782 GTK_EDITABLE(compose->subject_entry), 0, -1);
4784 #ifndef GENERIC_UMPC
4785 if (subject && strlen(subject))
4786 str = g_strdup_printf(_("%s - Compose message%s"),
4789 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4791 str = g_strdup(_("Compose message"));
4794 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4800 * compose_current_mail_account:
4802 * Find a current mail account (the currently selected account, or the
4803 * default account, if a news account is currently selected). If a
4804 * mail account cannot be found, display an error message.
4806 * Return value: Mail account, or NULL if not found.
4808 static PrefsAccount *
4809 compose_current_mail_account(void)
4813 if (cur_account && cur_account->protocol != A_NNTP)
4816 ac = account_get_default();
4817 if (!ac || ac->protocol == A_NNTP) {
4818 alertpanel_error(_("Account for sending mail is not specified.\n"
4819 "Please select a mail account before sending."));
4826 #define QUOTE_IF_REQUIRED(out, str) \
4828 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4832 len = strlen(str) + 3; \
4833 if ((__tmp = alloca(len)) == NULL) { \
4834 g_warning("can't allocate memory"); \
4835 g_string_free(header, TRUE); \
4838 g_snprintf(__tmp, len, "\"%s\"", str); \
4843 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4844 g_warning("can't allocate memory"); \
4845 g_string_free(header, TRUE); \
4848 strcpy(__tmp, str); \
4854 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4856 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4860 len = strlen(str) + 3; \
4861 if ((__tmp = alloca(len)) == NULL) { \
4862 g_warning("can't allocate memory"); \
4865 g_snprintf(__tmp, len, "\"%s\"", str); \
4870 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4871 g_warning("can't allocate memory"); \
4874 strcpy(__tmp, str); \
4880 static void compose_select_account(Compose *compose, PrefsAccount *account,
4883 gchar *from = NULL, *header = NULL;
4884 ComposeHeaderEntry *header_entry;
4885 #if GTK_CHECK_VERSION(2, 24, 0)
4889 cm_return_if_fail(account != NULL);
4891 compose->account = account;
4892 if (account->name && *account->name) {
4894 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4895 qbuf = escape_internal_quotes(buf, '"');
4896 from = g_strdup_printf("%s <%s>",
4897 qbuf, account->address);
4900 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4902 from = g_strdup_printf("<%s>",
4904 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4909 compose_set_title(compose);
4911 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4912 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4914 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4915 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4916 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4918 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4920 activate_privacy_system(compose, account, FALSE);
4922 if (!init && compose->mode != COMPOSE_REDIRECT) {
4923 undo_block(compose->undostruct);
4924 compose_insert_sig(compose, TRUE);
4925 undo_unblock(compose->undostruct);
4928 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4929 #if !GTK_CHECK_VERSION(2, 24, 0)
4930 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4932 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4933 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4934 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4937 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4938 if (account->protocol == A_NNTP) {
4939 if (!strcmp(header, _("To:")))
4940 combobox_select_by_text(
4941 GTK_COMBO_BOX(header_entry->combo),
4944 if (!strcmp(header, _("Newsgroups:")))
4945 combobox_select_by_text(
4946 GTK_COMBO_BOX(header_entry->combo),
4954 /* use account's dict info if set */
4955 if (compose->gtkaspell) {
4956 if (account->enable_default_dictionary)
4957 gtkaspell_change_dict(compose->gtkaspell,
4958 account->default_dictionary, FALSE);
4959 if (account->enable_default_alt_dictionary)
4960 gtkaspell_change_alt_dict(compose->gtkaspell,
4961 account->default_alt_dictionary);
4962 if (account->enable_default_dictionary
4963 || account->enable_default_alt_dictionary)
4964 compose_spell_menu_changed(compose);
4969 gboolean compose_check_for_valid_recipient(Compose *compose) {
4970 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4971 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4972 gboolean recipient_found = FALSE;
4976 /* free to and newsgroup list */
4977 slist_free_strings_full(compose->to_list);
4978 compose->to_list = NULL;
4980 slist_free_strings_full(compose->newsgroup_list);
4981 compose->newsgroup_list = NULL;
4983 /* search header entries for to and newsgroup entries */
4984 for (list = compose->header_list; list; list = list->next) {
4987 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4988 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4991 if (entry[0] != '\0') {
4992 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4993 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4994 compose->to_list = address_list_append(compose->to_list, entry);
4995 recipient_found = TRUE;
4998 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4999 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
5000 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
5001 recipient_found = TRUE;
5008 return recipient_found;
5011 static gboolean compose_check_for_set_recipients(Compose *compose)
5013 if (compose->account->set_autocc && compose->account->auto_cc) {
5014 gboolean found_other = FALSE;
5016 /* search header entries for to and newsgroup entries */
5017 for (list = compose->header_list; list; list = list->next) {
5020 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5021 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5024 if (strcmp(entry, compose->account->auto_cc)
5025 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5036 if (compose->batch) {
5037 gtk_widget_show_all(compose->window);
5039 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5040 prefs_common_translated_header_name("Cc"));
5041 aval = alertpanel(_("Send"),
5043 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5045 if (aval != G_ALERTALTERNATE)
5049 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5050 gboolean found_other = FALSE;
5052 /* search header entries for to and newsgroup entries */
5053 for (list = compose->header_list; list; list = list->next) {
5056 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5057 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5060 if (strcmp(entry, compose->account->auto_bcc)
5061 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5072 if (compose->batch) {
5073 gtk_widget_show_all(compose->window);
5075 text = g_strdup_printf(_("The only recipient is the default '%s' address. Send anyway?"),
5076 prefs_common_translated_header_name("Bcc"));
5077 aval = alertpanel(_("Send"),
5079 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5081 if (aval != G_ALERTALTERNATE)
5088 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5092 if (compose_check_for_valid_recipient(compose) == FALSE) {
5093 if (compose->batch) {
5094 gtk_widget_show_all(compose->window);
5096 alertpanel_error(_("Recipient is not specified."));
5100 if (compose_check_for_set_recipients(compose) == FALSE) {
5104 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5105 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5106 if (*str == '\0' && check_everything == TRUE &&
5107 compose->mode != COMPOSE_REDIRECT) {
5109 gchar *button_label;
5112 if (compose->sending)
5113 button_label = g_strconcat("+", _("_Send"), NULL);
5115 button_label = g_strconcat("+", _("_Queue"), NULL);
5116 message = g_strdup_printf(_("Subject is empty. %s"),
5117 compose->sending?_("Send it anyway?"):
5118 _("Queue it anyway?"));
5120 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5121 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5122 ALERT_QUESTION, G_ALERTDEFAULT);
5124 if (aval & G_ALERTDISABLE) {
5125 aval &= ~G_ALERTDISABLE;
5126 prefs_common.warn_empty_subj = FALSE;
5128 if (aval != G_ALERTALTERNATE)
5133 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5139 gint compose_send(Compose *compose)
5142 FolderItem *folder = NULL;
5144 gchar *msgpath = NULL;
5145 gboolean discard_window = FALSE;
5146 gchar *errstr = NULL;
5147 gchar *tmsgid = NULL;
5148 MainWindow *mainwin = mainwindow_get_mainwindow();
5149 gboolean queued_removed = FALSE;
5151 if (prefs_common.send_dialog_invisible
5152 || compose->batch == TRUE)
5153 discard_window = TRUE;
5155 compose_allow_user_actions (compose, FALSE);
5156 compose->sending = TRUE;
5158 if (compose_check_entries(compose, TRUE) == FALSE) {
5159 if (compose->batch) {
5160 gtk_widget_show_all(compose->window);
5166 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5169 if (compose->batch) {
5170 gtk_widget_show_all(compose->window);
5173 alertpanel_error(_("Could not queue message for sending:\n\n"
5174 "Charset conversion failed."));
5175 } else if (val == -5) {
5176 alertpanel_error(_("Could not queue message for sending:\n\n"
5177 "Couldn't get recipient encryption key."));
5178 } else if (val == -6) {
5180 } else if (val == -3) {
5181 if (privacy_peek_error())
5182 alertpanel_error(_("Could not queue message for sending:\n\n"
5183 "Signature failed: %s"), privacy_get_error());
5184 } else if (val == -2 && errno != 0) {
5185 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5187 alertpanel_error(_("Could not queue message for sending."));
5192 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5193 if (discard_window) {
5194 compose->sending = FALSE;
5195 compose_close(compose);
5196 /* No more compose access in the normal codepath
5197 * after this point! */
5202 alertpanel_error(_("The message was queued but could not be "
5203 "sent.\nUse \"Send queued messages\" from "
5204 "the main window to retry."));
5205 if (!discard_window) {
5212 if (msgpath == NULL) {
5213 msgpath = folder_item_fetch_msg(folder, msgnum);
5214 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5217 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5218 claws_unlink(msgpath);
5221 if (!discard_window) {
5223 if (!queued_removed)
5224 folder_item_remove_msg(folder, msgnum);
5225 folder_item_scan(folder);
5227 /* make sure we delete that */
5228 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5230 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5231 folder_item_remove_msg(folder, tmp->msgnum);
5232 procmsg_msginfo_free(&tmp);
5239 if (!queued_removed)
5240 folder_item_remove_msg(folder, msgnum);
5241 folder_item_scan(folder);
5243 /* make sure we delete that */
5244 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5246 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5247 folder_item_remove_msg(folder, tmp->msgnum);
5248 procmsg_msginfo_free(&tmp);
5251 if (!discard_window) {
5252 compose->sending = FALSE;
5253 compose_allow_user_actions (compose, TRUE);
5254 compose_close(compose);
5258 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5259 "the main window to retry."), errstr);
5262 alertpanel_error_log(_("The message was queued but could not be "
5263 "sent.\nUse \"Send queued messages\" from "
5264 "the main window to retry."));
5266 if (!discard_window) {
5275 toolbar_main_set_sensitive(mainwin);
5276 main_window_set_menu_sensitive(mainwin);
5282 compose_allow_user_actions (compose, TRUE);
5283 compose->sending = FALSE;
5284 compose->modified = TRUE;
5285 toolbar_main_set_sensitive(mainwin);
5286 main_window_set_menu_sensitive(mainwin);
5291 static gboolean compose_use_attach(Compose *compose)
5293 GtkTreeModel *model = gtk_tree_view_get_model
5294 (GTK_TREE_VIEW(compose->attach_clist));
5295 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5298 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5301 gchar buf[BUFFSIZE];
5303 gboolean first_to_address;
5304 gboolean first_cc_address;
5306 ComposeHeaderEntry *headerentry;
5307 const gchar *headerentryname;
5308 const gchar *cc_hdr;
5309 const gchar *to_hdr;
5310 gboolean err = FALSE;
5312 debug_print("Writing redirect header\n");
5314 cc_hdr = prefs_common_translated_header_name("Cc:");
5315 to_hdr = prefs_common_translated_header_name("To:");
5317 first_to_address = TRUE;
5318 for (list = compose->header_list; list; list = list->next) {
5319 headerentry = ((ComposeHeaderEntry *)list->data);
5320 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5322 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5323 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5324 Xstrdup_a(str, entstr, return -1);
5326 if (str[0] != '\0') {
5327 compose_convert_header
5328 (compose, buf, sizeof(buf), str,
5329 strlen("Resent-To") + 2, TRUE);
5331 if (first_to_address) {
5332 err |= (fprintf(fp, "Resent-To: ") < 0);
5333 first_to_address = FALSE;
5335 err |= (fprintf(fp, ",") < 0);
5337 err |= (fprintf(fp, "%s", buf) < 0);
5341 if (!first_to_address) {
5342 err |= (fprintf(fp, "\n") < 0);
5345 first_cc_address = TRUE;
5346 for (list = compose->header_list; list; list = list->next) {
5347 headerentry = ((ComposeHeaderEntry *)list->data);
5348 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5350 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5351 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5352 Xstrdup_a(str, strg, return -1);
5354 if (str[0] != '\0') {
5355 compose_convert_header
5356 (compose, buf, sizeof(buf), str,
5357 strlen("Resent-Cc") + 2, TRUE);
5359 if (first_cc_address) {
5360 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5361 first_cc_address = FALSE;
5363 err |= (fprintf(fp, ",") < 0);
5365 err |= (fprintf(fp, "%s", buf) < 0);
5369 if (!first_cc_address) {
5370 err |= (fprintf(fp, "\n") < 0);
5373 return (err ? -1:0);
5376 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5378 gchar buf[BUFFSIZE];
5380 const gchar *entstr;
5381 /* struct utsname utsbuf; */
5382 gboolean err = FALSE;
5384 cm_return_val_if_fail(fp != NULL, -1);
5385 cm_return_val_if_fail(compose->account != NULL, -1);
5386 cm_return_val_if_fail(compose->account->address != NULL, -1);
5389 if (prefs_common.hide_timezone)
5390 get_rfc822_date_hide_tz(buf, sizeof(buf));
5392 get_rfc822_date(buf, sizeof(buf));
5393 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5396 if (compose->account->name && *compose->account->name) {
5397 compose_convert_header
5398 (compose, buf, sizeof(buf), compose->account->name,
5399 strlen("From: "), TRUE);
5400 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5401 buf, compose->account->address) < 0);
5403 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5406 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5407 if (*entstr != '\0') {
5408 Xstrdup_a(str, entstr, return -1);
5411 compose_convert_header(compose, buf, sizeof(buf), str,
5412 strlen("Subject: "), FALSE);
5413 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5417 /* Resent-Message-ID */
5418 if (compose->account->gen_msgid) {
5419 gchar *addr = prefs_account_generate_msgid(compose->account);
5420 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5422 g_free(compose->msgid);
5423 compose->msgid = addr;
5425 compose->msgid = NULL;
5428 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5431 /* separator between header and body */
5432 err |= (fputs("\n", fp) == EOF);
5434 return (err ? -1:0);
5437 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5441 gchar buf[BUFFSIZE];
5443 gboolean skip = FALSE;
5444 gboolean err = FALSE;
5445 gchar *not_included[]={
5446 "Return-Path:", "Delivered-To:", "Received:",
5447 "Subject:", "X-UIDL:", "AF:",
5448 "NF:", "PS:", "SRH:",
5449 "SFN:", "DSR:", "MID:",
5450 "CFG:", "PT:", "S:",
5451 "RQ:", "SSV:", "NSV:",
5452 "SSH:", "R:", "MAID:",
5453 "NAID:", "RMID:", "FMID:",
5454 "SCF:", "RRCPT:", "NG:",
5455 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5456 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5457 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5458 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5459 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5462 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5463 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5467 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5469 for (i = 0; not_included[i] != NULL; i++) {
5470 if (g_ascii_strncasecmp(buf, not_included[i],
5471 strlen(not_included[i])) == 0) {
5478 if (fputs(buf, fdest) == -1)
5481 if (!prefs_common.redirect_keep_from) {
5482 if (g_ascii_strncasecmp(buf, "From:",
5483 strlen("From:")) == 0) {
5484 err |= (fputs(" (by way of ", fdest) == EOF);
5485 if (compose->account->name
5486 && *compose->account->name) {
5487 compose_convert_header
5488 (compose, buf, sizeof(buf),
5489 compose->account->name,
5492 err |= (fprintf(fdest, "%s <%s>",
5494 compose->account->address) < 0);
5496 err |= (fprintf(fdest, "%s",
5497 compose->account->address) < 0);
5498 err |= (fputs(")", fdest) == EOF);
5502 if (fputs("\n", fdest) == -1)
5509 if (compose_redirect_write_headers(compose, fdest))
5512 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5513 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5526 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5528 GtkTextBuffer *buffer;
5529 GtkTextIter start, end;
5530 gchar *chars, *tmp_enc_file, *content;
5532 const gchar *out_codeset;
5533 EncodingType encoding = ENC_UNKNOWN;
5534 MimeInfo *mimemsg, *mimetext;
5536 const gchar *src_codeset = CS_INTERNAL;
5537 gchar *from_addr = NULL;
5538 gchar *from_name = NULL;
5541 if (action == COMPOSE_WRITE_FOR_SEND) {
5542 attach_parts = TRUE;
5544 /* We're sending the message, generate a Message-ID
5546 if (compose->msgid == NULL &&
5547 compose->account->gen_msgid) {
5548 compose->msgid = prefs_account_generate_msgid(compose->account);
5552 /* create message MimeInfo */
5553 mimemsg = procmime_mimeinfo_new();
5554 mimemsg->type = MIMETYPE_MESSAGE;
5555 mimemsg->subtype = g_strdup("rfc822");
5556 mimemsg->content = MIMECONTENT_MEM;
5557 mimemsg->tmp = TRUE; /* must free content later */
5558 mimemsg->data.mem = compose_get_header(compose);
5560 /* Create text part MimeInfo */
5561 /* get all composed text */
5562 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5563 gtk_text_buffer_get_start_iter(buffer, &start);
5564 gtk_text_buffer_get_end_iter(buffer, &end);
5565 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5567 out_codeset = conv_get_charset_str(compose->out_encoding);
5569 if (!out_codeset && is_ascii_str(chars)) {
5570 out_codeset = CS_US_ASCII;
5571 } else if (prefs_common.outgoing_fallback_to_ascii &&
5572 is_ascii_str(chars)) {
5573 out_codeset = CS_US_ASCII;
5574 encoding = ENC_7BIT;
5578 gchar *test_conv_global_out = NULL;
5579 gchar *test_conv_reply = NULL;
5581 /* automatic mode. be automatic. */
5582 codeconv_set_strict(TRUE);
5584 out_codeset = conv_get_outgoing_charset_str();
5586 debug_print("trying to convert to %s\n", out_codeset);
5587 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5590 if (!test_conv_global_out && compose->orig_charset
5591 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5592 out_codeset = compose->orig_charset;
5593 debug_print("failure; trying to convert to %s\n", out_codeset);
5594 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5597 if (!test_conv_global_out && !test_conv_reply) {
5599 out_codeset = CS_INTERNAL;
5600 debug_print("failure; finally using %s\n", out_codeset);
5602 g_free(test_conv_global_out);
5603 g_free(test_conv_reply);
5604 codeconv_set_strict(FALSE);
5607 if (encoding == ENC_UNKNOWN) {
5608 if (prefs_common.encoding_method == CTE_BASE64)
5609 encoding = ENC_BASE64;
5610 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5611 encoding = ENC_QUOTED_PRINTABLE;
5612 else if (prefs_common.encoding_method == CTE_8BIT)
5613 encoding = ENC_8BIT;
5615 encoding = procmime_get_encoding_for_charset(out_codeset);
5618 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5619 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5621 if (action == COMPOSE_WRITE_FOR_SEND) {
5622 codeconv_set_strict(TRUE);
5623 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5624 codeconv_set_strict(FALSE);
5629 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5630 "to the specified %s charset.\n"
5631 "Send it as %s?"), out_codeset, src_codeset);
5632 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5633 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5634 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5637 if (aval != G_ALERTALTERNATE) {
5642 out_codeset = src_codeset;
5648 out_codeset = src_codeset;
5653 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5654 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5655 strstr(buf, "\nFrom ") != NULL) {
5656 encoding = ENC_QUOTED_PRINTABLE;
5660 mimetext = procmime_mimeinfo_new();
5661 mimetext->content = MIMECONTENT_MEM;
5662 mimetext->tmp = TRUE; /* must free content later */
5663 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5664 * and free the data, which we need later. */
5665 mimetext->data.mem = g_strdup(buf);
5666 mimetext->type = MIMETYPE_TEXT;
5667 mimetext->subtype = g_strdup("plain");
5668 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5669 g_strdup(out_codeset));
5671 /* protect trailing spaces when signing message */
5672 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5673 privacy_system_can_sign(compose->privacy_system)) {
5674 encoding = ENC_QUOTED_PRINTABLE;
5678 debug_print("main text: %Id bytes encoded as %s in %d\n",
5680 debug_print("main text: %zd bytes encoded as %s in %d\n",
5682 strlen(buf), out_codeset, encoding);
5684 /* check for line length limit */
5685 if (action == COMPOSE_WRITE_FOR_SEND &&
5686 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5687 check_line_length(buf, 1000, &line) < 0) {
5690 msg = g_strdup_printf
5691 (_("Line %d exceeds the line length limit (998 bytes).\n"
5692 "The contents of the message might be broken on the way to the delivery.\n"
5694 "Send it anyway?"), line + 1);
5695 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5697 if (aval != G_ALERTALTERNATE) {
5703 if (encoding != ENC_UNKNOWN)
5704 procmime_encode_content(mimetext, encoding);
5706 /* append attachment parts */
5707 if (compose_use_attach(compose) && attach_parts) {
5708 MimeInfo *mimempart;
5709 gchar *boundary = NULL;
5710 mimempart = procmime_mimeinfo_new();
5711 mimempart->content = MIMECONTENT_EMPTY;
5712 mimempart->type = MIMETYPE_MULTIPART;
5713 mimempart->subtype = g_strdup("mixed");
5717 boundary = generate_mime_boundary(NULL);
5718 } while (strstr(buf, boundary) != NULL);
5720 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5723 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5725 g_node_append(mimempart->node, mimetext->node);
5726 g_node_append(mimemsg->node, mimempart->node);
5728 if (compose_add_attachments(compose, mimempart) < 0)
5731 g_node_append(mimemsg->node, mimetext->node);
5735 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5736 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5737 /* extract name and address */
5738 if (strstr(spec, " <") && strstr(spec, ">")) {
5739 from_addr = g_strdup(strrchr(spec, '<')+1);
5740 *(strrchr(from_addr, '>')) = '\0';
5741 from_name = g_strdup(spec);
5742 *(strrchr(from_name, '<')) = '\0';
5749 /* sign message if sending */
5750 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5751 privacy_system_can_sign(compose->privacy_system))
5752 if (!privacy_sign(compose->privacy_system, mimemsg,
5753 compose->account, from_addr)) {
5761 if (compose->use_encryption) {
5762 if (compose->encdata != NULL &&
5763 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5765 /* First, write an unencrypted copy and save it to outbox, if
5766 * user wants that. */
5767 if (compose->account->save_encrypted_as_clear_text) {
5768 debug_print("saving sent message unencrypted...\n");
5769 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5773 /* fp now points to a file with headers written,
5774 * let's make a copy. */
5776 content = file_read_stream_to_str(fp);
5778 str_write_to_file(content, tmp_enc_file);
5781 /* Now write the unencrypted body. */
5782 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5783 procmime_write_mimeinfo(mimemsg, tmpfp);
5786 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5788 outbox = folder_get_default_outbox();
5790 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5791 claws_unlink(tmp_enc_file);
5793 g_warning("Can't open file '%s'", tmp_enc_file);
5796 g_warning("couldn't get tempfile");
5799 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5800 debug_print("Couldn't encrypt mime structure: %s.\n",
5801 privacy_get_error());
5802 alertpanel_error(_("Couldn't encrypt the email: %s"),
5803 privacy_get_error());
5808 procmime_write_mimeinfo(mimemsg, fp);
5810 procmime_mimeinfo_free_all(&mimemsg);
5815 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5817 GtkTextBuffer *buffer;
5818 GtkTextIter start, end;
5823 if ((fp = g_fopen(file, "wb")) == NULL) {
5824 FILE_OP_ERROR(file, "fopen");
5828 /* chmod for security */
5829 if (change_file_mode_rw(fp, file) < 0) {
5830 FILE_OP_ERROR(file, "chmod");
5831 g_warning("can't change file mode");
5834 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5835 gtk_text_buffer_get_start_iter(buffer, &start);
5836 gtk_text_buffer_get_end_iter(buffer, &end);
5837 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5839 chars = conv_codeset_strdup
5840 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5849 len = strlen(chars);
5850 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5851 FILE_OP_ERROR(file, "fwrite");
5860 if (fclose(fp) == EOF) {
5861 FILE_OP_ERROR(file, "fclose");
5868 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5871 MsgInfo *msginfo = compose->targetinfo;
5873 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5874 if (!msginfo) return -1;
5876 if (!force && MSG_IS_LOCKED(msginfo->flags))
5879 item = msginfo->folder;
5880 cm_return_val_if_fail(item != NULL, -1);
5882 if (procmsg_msg_exist(msginfo) &&
5883 (folder_has_parent_of_type(item, F_QUEUE) ||
5884 folder_has_parent_of_type(item, F_DRAFT)
5885 || msginfo == compose->autosaved_draft)) {
5886 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5887 g_warning("can't remove the old message");
5890 debug_print("removed reedit target %d\n", msginfo->msgnum);
5897 static void compose_remove_draft(Compose *compose)
5900 MsgInfo *msginfo = compose->targetinfo;
5901 drafts = account_get_special_folder(compose->account, F_DRAFT);
5903 if (procmsg_msg_exist(msginfo)) {
5904 folder_item_remove_msg(drafts, msginfo->msgnum);
5909 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5910 gboolean remove_reedit_target)
5912 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5915 static gboolean compose_warn_encryption(Compose *compose)
5917 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5918 AlertValue val = G_ALERTALTERNATE;
5920 if (warning == NULL)
5923 val = alertpanel_full(_("Encryption warning"), warning,
5924 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5925 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5926 if (val & G_ALERTDISABLE) {
5927 val &= ~G_ALERTDISABLE;
5928 if (val == G_ALERTALTERNATE)
5929 privacy_inhibit_encrypt_warning(compose->privacy_system,
5933 if (val == G_ALERTALTERNATE) {
5940 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5941 gchar **msgpath, gboolean check_subject,
5942 gboolean remove_reedit_target)
5949 PrefsAccount *mailac = NULL, *newsac = NULL;
5950 gboolean err = FALSE;
5952 debug_print("queueing message...\n");
5953 cm_return_val_if_fail(compose->account != NULL, -1);
5955 if (compose_check_entries(compose, check_subject) == FALSE) {
5956 if (compose->batch) {
5957 gtk_widget_show_all(compose->window);
5962 if (!compose->to_list && !compose->newsgroup_list) {
5963 g_warning("can't get recipient list.");
5967 if (compose->to_list) {
5968 if (compose->account->protocol != A_NNTP)
5969 mailac = compose->account;
5970 else if (cur_account && cur_account->protocol != A_NNTP)
5971 mailac = cur_account;
5972 else if (!(mailac = compose_current_mail_account())) {
5973 alertpanel_error(_("No account for sending mails available!"));
5978 if (compose->newsgroup_list) {
5979 if (compose->account->protocol == A_NNTP)
5980 newsac = compose->account;
5982 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5987 /* write queue header */
5988 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5989 G_DIR_SEPARATOR, compose, (guint) rand());
5990 debug_print("queuing to %s\n", tmp);
5991 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5992 FILE_OP_ERROR(tmp, "fopen");
5997 if (change_file_mode_rw(fp, tmp) < 0) {
5998 FILE_OP_ERROR(tmp, "chmod");
5999 g_warning("can't change file mode");
6002 /* queueing variables */
6003 err |= (fprintf(fp, "AF:\n") < 0);
6004 err |= (fprintf(fp, "NF:0\n") < 0);
6005 err |= (fprintf(fp, "PS:10\n") < 0);
6006 err |= (fprintf(fp, "SRH:1\n") < 0);
6007 err |= (fprintf(fp, "SFN:\n") < 0);
6008 err |= (fprintf(fp, "DSR:\n") < 0);
6010 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6012 err |= (fprintf(fp, "MID:\n") < 0);
6013 err |= (fprintf(fp, "CFG:\n") < 0);
6014 err |= (fprintf(fp, "PT:0\n") < 0);
6015 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6016 err |= (fprintf(fp, "RQ:\n") < 0);
6018 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6020 err |= (fprintf(fp, "SSV:\n") < 0);
6022 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6024 err |= (fprintf(fp, "NSV:\n") < 0);
6025 err |= (fprintf(fp, "SSH:\n") < 0);
6026 /* write recepient list */
6027 if (compose->to_list) {
6028 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6029 for (cur = compose->to_list->next; cur != NULL;
6031 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6032 err |= (fprintf(fp, "\n") < 0);
6034 /* write newsgroup list */
6035 if (compose->newsgroup_list) {
6036 err |= (fprintf(fp, "NG:") < 0);
6037 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6038 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6039 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6040 err |= (fprintf(fp, "\n") < 0);
6042 /* Sylpheed account IDs */
6044 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6046 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6049 if (compose->privacy_system != NULL) {
6050 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6051 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6052 if (compose->use_encryption) {
6053 if (!compose_warn_encryption(compose)) {
6059 if (mailac && mailac->encrypt_to_self) {
6060 GSList *tmp_list = g_slist_copy(compose->to_list);
6061 tmp_list = g_slist_append(tmp_list, compose->account->address);
6062 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6063 g_slist_free(tmp_list);
6065 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6067 if (compose->encdata != NULL) {
6068 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6069 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6070 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6071 compose->encdata) < 0);
6072 } /* else we finally dont want to encrypt */
6074 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6075 /* and if encdata was null, it means there's been a problem in
6078 g_warning("failed to write queue message");
6087 /* Save copy folder */
6088 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6089 gchar *savefolderid;
6091 savefolderid = compose_get_save_to(compose);
6092 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6093 g_free(savefolderid);
6095 /* Save copy folder */
6096 if (compose->return_receipt) {
6097 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6099 /* Message-ID of message replying to */
6100 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6101 gchar *folderid = NULL;
6103 if (compose->replyinfo->folder)
6104 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6105 if (folderid == NULL)
6106 folderid = g_strdup("NULL");
6108 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6111 /* Message-ID of message forwarding to */
6112 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6113 gchar *folderid = NULL;
6115 if (compose->fwdinfo->folder)
6116 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6117 if (folderid == NULL)
6118 folderid = g_strdup("NULL");
6120 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6124 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6125 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6127 /* end of headers */
6128 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6130 if (compose->redirect_filename != NULL) {
6131 if (compose_redirect_write_to_file(compose, fp) < 0) {
6139 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6143 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6147 g_warning("failed to write queue message");
6153 if (fclose(fp) == EOF) {
6154 FILE_OP_ERROR(tmp, "fclose");
6160 if (item && *item) {
6163 queue = account_get_special_folder(compose->account, F_QUEUE);
6166 g_warning("can't find queue folder");
6171 folder_item_scan(queue);
6172 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6173 g_warning("can't queue the message");
6179 if (msgpath == NULL) {
6185 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6186 compose_remove_reedit_target(compose, FALSE);
6189 if ((msgnum != NULL) && (item != NULL)) {
6197 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6200 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6203 gchar *type, *subtype;
6204 GtkTreeModel *model;
6207 model = gtk_tree_view_get_model(tree_view);
6209 if (!gtk_tree_model_get_iter_first(model, &iter))
6212 gtk_tree_model_get(model, &iter,
6216 if (!is_file_exist(ainfo->file)) {
6217 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6218 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6219 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6221 if (val == G_ALERTDEFAULT) {
6226 if (g_stat(ainfo->file, &statbuf) < 0)
6229 mimepart = procmime_mimeinfo_new();
6230 mimepart->content = MIMECONTENT_FILE;
6231 mimepart->data.filename = g_strdup(ainfo->file);
6232 mimepart->tmp = FALSE; /* or we destroy our attachment */
6233 mimepart->offset = 0;
6234 mimepart->length = statbuf.st_size;
6236 type = g_strdup(ainfo->content_type);
6238 if (!strchr(type, '/')) {
6240 type = g_strdup("application/octet-stream");
6243 subtype = strchr(type, '/') + 1;
6244 *(subtype - 1) = '\0';
6245 mimepart->type = procmime_get_media_type(type);
6246 mimepart->subtype = g_strdup(subtype);
6249 if (mimepart->type == MIMETYPE_MESSAGE &&
6250 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6251 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6252 } else if (mimepart->type == MIMETYPE_TEXT) {
6253 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6254 /* Text parts with no name come from multipart/alternative
6255 * forwards. Make sure the recipient won't look at the
6256 * original HTML part by mistake. */
6257 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6258 ainfo->name = g_strdup_printf(_("Original %s part"),
6262 g_hash_table_insert(mimepart->typeparameters,
6263 g_strdup("charset"), g_strdup(ainfo->charset));
6265 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6266 if (mimepart->type == MIMETYPE_APPLICATION &&
6267 !strcmp2(mimepart->subtype, "octet-stream"))
6268 g_hash_table_insert(mimepart->typeparameters,
6269 g_strdup("name"), g_strdup(ainfo->name));
6270 g_hash_table_insert(mimepart->dispositionparameters,
6271 g_strdup("filename"), g_strdup(ainfo->name));
6272 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6275 if (mimepart->type == MIMETYPE_MESSAGE
6276 || mimepart->type == MIMETYPE_MULTIPART)
6277 ainfo->encoding = ENC_BINARY;
6278 else if (compose->use_signing) {
6279 if (ainfo->encoding == ENC_7BIT)
6280 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6281 else if (ainfo->encoding == ENC_8BIT)
6282 ainfo->encoding = ENC_BASE64;
6287 procmime_encode_content(mimepart, ainfo->encoding);
6289 g_node_append(parent->node, mimepart->node);
6290 } while (gtk_tree_model_iter_next(model, &iter));
6295 static gchar *compose_quote_list_of_addresses(gchar *str)
6297 GSList *list = NULL, *item = NULL;
6298 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6300 list = address_list_append_with_comments(list, str);
6301 for (item = list; item != NULL; item = item->next) {
6302 gchar *spec = item->data;
6303 gchar *endofname = strstr(spec, " <");
6304 if (endofname != NULL) {
6307 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6308 qqname = escape_internal_quotes(qname, '"');
6310 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6311 gchar *addr = g_strdup(endofname);
6312 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6313 faddr = g_strconcat(name, addr, NULL);
6316 debug_print("new auto-quoted address: '%s'\n", faddr);
6320 result = g_strdup((faddr != NULL)? faddr: spec);
6322 result = g_strconcat(result,
6324 (faddr != NULL)? faddr: spec,
6327 if (faddr != NULL) {
6332 slist_free_strings_full(list);
6337 #define IS_IN_CUSTOM_HEADER(header) \
6338 (compose->account->add_customhdr && \
6339 custom_header_find(compose->account->customhdr_list, header) != NULL)
6341 static void compose_add_headerfield_from_headerlist(Compose *compose,
6343 const gchar *fieldname,
6344 const gchar *seperator)
6346 gchar *str, *fieldname_w_colon;
6347 gboolean add_field = FALSE;
6349 ComposeHeaderEntry *headerentry;
6350 const gchar *headerentryname;
6351 const gchar *trans_fieldname;
6354 if (IS_IN_CUSTOM_HEADER(fieldname))
6357 debug_print("Adding %s-fields\n", fieldname);
6359 fieldstr = g_string_sized_new(64);
6361 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6362 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6364 for (list = compose->header_list; list; list = list->next) {
6365 headerentry = ((ComposeHeaderEntry *)list->data);
6366 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6368 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6369 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6371 str = compose_quote_list_of_addresses(ustr);
6373 if (str != NULL && str[0] != '\0') {
6375 g_string_append(fieldstr, seperator);
6376 g_string_append(fieldstr, str);
6385 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6386 compose_convert_header
6387 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6388 strlen(fieldname) + 2, TRUE);
6389 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6393 g_free(fieldname_w_colon);
6394 g_string_free(fieldstr, TRUE);
6399 static gchar *compose_get_manual_headers_info(Compose *compose)
6401 GString *sh_header = g_string_new(" ");
6403 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6405 for (list = compose->header_list; list; list = list->next) {
6406 ComposeHeaderEntry *headerentry;
6409 gchar *headername_wcolon;
6410 const gchar *headername_trans;
6412 gboolean standard_header = FALSE;
6414 headerentry = ((ComposeHeaderEntry *)list->data);
6416 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6418 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6423 if (!strstr(tmp, ":")) {
6424 headername_wcolon = g_strconcat(tmp, ":", NULL);
6425 headername = g_strdup(tmp);
6427 headername_wcolon = g_strdup(tmp);
6428 headername = g_strdup(strtok(tmp, ":"));
6432 string = std_headers;
6433 while (*string != NULL) {
6434 headername_trans = prefs_common_translated_header_name(*string);
6435 if (!strcmp(headername_trans, headername_wcolon))
6436 standard_header = TRUE;
6439 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6440 g_string_append_printf(sh_header, "%s ", headername);
6442 g_free(headername_wcolon);
6444 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6445 return g_string_free(sh_header, FALSE);
6448 static gchar *compose_get_header(Compose *compose)
6450 gchar buf[BUFFSIZE];
6451 const gchar *entry_str;
6455 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6457 gchar *from_name = NULL, *from_address = NULL;
6460 cm_return_val_if_fail(compose->account != NULL, NULL);
6461 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6463 header = g_string_sized_new(64);
6466 if (prefs_common.hide_timezone)
6467 get_rfc822_date_hide_tz(buf, sizeof(buf));
6469 get_rfc822_date(buf, sizeof(buf));
6470 g_string_append_printf(header, "Date: %s\n", buf);
6474 if (compose->account->name && *compose->account->name) {
6476 QUOTE_IF_REQUIRED(buf, compose->account->name);
6477 tmp = g_strdup_printf("%s <%s>",
6478 buf, compose->account->address);
6480 tmp = g_strdup_printf("%s",
6481 compose->account->address);
6483 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6484 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6486 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6487 from_address = g_strdup(compose->account->address);
6489 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6490 /* extract name and address */
6491 if (strstr(spec, " <") && strstr(spec, ">")) {
6492 from_address = g_strdup(strrchr(spec, '<')+1);
6493 *(strrchr(from_address, '>')) = '\0';
6494 from_name = g_strdup(spec);
6495 *(strrchr(from_name, '<')) = '\0';
6498 from_address = g_strdup(spec);
6505 if (from_name && *from_name) {
6507 compose_convert_header
6508 (compose, buf, sizeof(buf), from_name,
6509 strlen("From: "), TRUE);
6510 QUOTE_IF_REQUIRED(name, buf);
6511 qname = escape_internal_quotes(name, '"');
6513 g_string_append_printf(header, "From: %s <%s>\n",
6514 qname, from_address);
6515 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6516 compose->return_receipt) {
6517 compose_convert_header(compose, buf, sizeof(buf), from_name,
6518 strlen("Disposition-Notification-To: "),
6520 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6525 g_string_append_printf(header, "From: %s\n", from_address);
6526 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6527 compose->return_receipt)
6528 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6532 g_free(from_address);
6535 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6538 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6541 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6545 * If this account is a NNTP account remove Bcc header from
6546 * message body since it otherwise will be publicly shown
6548 if (compose->account->protocol != A_NNTP)
6549 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6552 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6554 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6557 compose_convert_header(compose, buf, sizeof(buf), str,
6558 strlen("Subject: "), FALSE);
6559 g_string_append_printf(header, "Subject: %s\n", buf);
6565 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6566 g_string_append_printf(header, "Message-ID: <%s>\n",
6570 if (compose->remove_references == FALSE) {
6572 if (compose->inreplyto && compose->to_list)
6573 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6576 if (compose->references)
6577 g_string_append_printf(header, "References: %s\n", compose->references);
6581 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6584 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6587 if (compose->account->organization &&
6588 strlen(compose->account->organization) &&
6589 !IS_IN_CUSTOM_HEADER("Organization")) {
6590 compose_convert_header(compose, buf, sizeof(buf),
6591 compose->account->organization,
6592 strlen("Organization: "), FALSE);
6593 g_string_append_printf(header, "Organization: %s\n", buf);
6596 /* Program version and system info */
6597 if (compose->account->gen_xmailer &&
6598 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6599 !compose->newsgroup_list) {
6600 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6602 gtk_major_version, gtk_minor_version, gtk_micro_version,
6605 if (compose->account->gen_xmailer &&
6606 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6607 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6609 gtk_major_version, gtk_minor_version, gtk_micro_version,
6613 /* custom headers */
6614 if (compose->account->add_customhdr) {
6617 for (cur = compose->account->customhdr_list; cur != NULL;
6619 CustomHeader *chdr = (CustomHeader *)cur->data;
6621 if (custom_header_is_allowed(chdr->name)
6622 && chdr->value != NULL
6623 && *(chdr->value) != '\0') {
6624 compose_convert_header
6625 (compose, buf, sizeof(buf),
6627 strlen(chdr->name) + 2, FALSE);
6628 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6633 /* Automatic Faces and X-Faces */
6634 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6635 g_string_append_printf(header, "X-Face: %s\n", buf);
6637 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6638 g_string_append_printf(header, "X-Face: %s\n", buf);
6640 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6641 g_string_append_printf(header, "Face: %s\n", buf);
6643 else if (get_default_face (buf, sizeof(buf)) == 0) {
6644 g_string_append_printf(header, "Face: %s\n", buf);
6648 switch (compose->priority) {
6649 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6650 "X-Priority: 1 (Highest)\n");
6652 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6653 "X-Priority: 2 (High)\n");
6655 case PRIORITY_NORMAL: break;
6656 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6657 "X-Priority: 4 (Low)\n");
6659 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6660 "X-Priority: 5 (Lowest)\n");
6662 default: debug_print("compose: priority unknown : %d\n",
6666 /* get special headers */
6667 for (list = compose->header_list; list; list = list->next) {
6668 ComposeHeaderEntry *headerentry;
6671 gchar *headername_wcolon;
6672 const gchar *headername_trans;
6675 gboolean standard_header = FALSE;
6677 headerentry = ((ComposeHeaderEntry *)list->data);
6679 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6681 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6686 if (!strstr(tmp, ":")) {
6687 headername_wcolon = g_strconcat(tmp, ":", NULL);
6688 headername = g_strdup(tmp);
6690 headername_wcolon = g_strdup(tmp);
6691 headername = g_strdup(strtok(tmp, ":"));
6695 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6696 Xstrdup_a(headervalue, entry_str, return NULL);
6697 subst_char(headervalue, '\r', ' ');
6698 subst_char(headervalue, '\n', ' ');
6699 string = std_headers;
6700 while (*string != NULL) {
6701 headername_trans = prefs_common_translated_header_name(*string);
6702 if (!strcmp(headername_trans, headername_wcolon))
6703 standard_header = TRUE;
6706 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6707 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6710 g_free(headername_wcolon);
6714 g_string_free(header, FALSE);
6719 #undef IS_IN_CUSTOM_HEADER
6721 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6722 gint header_len, gboolean addr_field)
6724 gchar *tmpstr = NULL;
6725 const gchar *out_codeset = NULL;
6727 cm_return_if_fail(src != NULL);
6728 cm_return_if_fail(dest != NULL);
6730 if (len < 1) return;
6732 tmpstr = g_strdup(src);
6734 subst_char(tmpstr, '\n', ' ');
6735 subst_char(tmpstr, '\r', ' ');
6738 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6739 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6740 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6745 codeconv_set_strict(TRUE);
6746 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6747 conv_get_charset_str(compose->out_encoding));
6748 codeconv_set_strict(FALSE);
6750 if (!dest || *dest == '\0') {
6751 gchar *test_conv_global_out = NULL;
6752 gchar *test_conv_reply = NULL;
6754 /* automatic mode. be automatic. */
6755 codeconv_set_strict(TRUE);
6757 out_codeset = conv_get_outgoing_charset_str();
6759 debug_print("trying to convert to %s\n", out_codeset);
6760 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6763 if (!test_conv_global_out && compose->orig_charset
6764 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6765 out_codeset = compose->orig_charset;
6766 debug_print("failure; trying to convert to %s\n", out_codeset);
6767 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6770 if (!test_conv_global_out && !test_conv_reply) {
6772 out_codeset = CS_INTERNAL;
6773 debug_print("finally using %s\n", out_codeset);
6775 g_free(test_conv_global_out);
6776 g_free(test_conv_reply);
6777 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6779 codeconv_set_strict(FALSE);
6784 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6788 cm_return_if_fail(user_data != NULL);
6790 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6791 g_strstrip(address);
6792 if (*address != '\0') {
6793 gchar *name = procheader_get_fromname(address);
6794 extract_address(address);
6795 #ifndef USE_ALT_ADDRBOOK
6796 addressbook_add_contact(name, address, NULL, NULL);
6798 debug_print("%s: %s\n", name, address);
6799 if (addressadd_selection(name, address, NULL, NULL)) {
6800 debug_print( "addressbook_add_contact - added\n" );
6807 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6809 GtkWidget *menuitem;
6812 cm_return_if_fail(menu != NULL);
6813 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6815 menuitem = gtk_separator_menu_item_new();
6816 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6817 gtk_widget_show(menuitem);
6819 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6820 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6822 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6823 g_strstrip(address);
6824 if (*address == '\0') {
6825 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6828 g_signal_connect(G_OBJECT(menuitem), "activate",
6829 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6830 gtk_widget_show(menuitem);
6833 void compose_add_extra_header(gchar *header, GtkListStore *model)
6836 if (strcmp(header, "")) {
6837 COMBOBOX_ADD(model, header, COMPOSE_TO);
6841 void compose_add_extra_header_entries(GtkListStore *model)
6845 gchar buf[BUFFSIZE];
6848 if (extra_headers == NULL) {
6849 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6850 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6851 debug_print("extra headers file not found\n");
6852 goto extra_headers_done;
6854 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6855 lastc = strlen(buf) - 1; /* remove trailing control chars */
6856 while (lastc >= 0 && buf[lastc] != ':')
6857 buf[lastc--] = '\0';
6858 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6859 buf[lastc] = '\0'; /* remove trailing : for comparison */
6860 if (custom_header_is_allowed(buf)) {
6862 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6865 g_message("disallowed extra header line: %s\n", buf);
6869 g_message("invalid extra header line: %s\n", buf);
6875 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6876 extra_headers = g_slist_reverse(extra_headers);
6878 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6881 static void compose_create_header_entry(Compose *compose)
6883 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6890 const gchar *header = NULL;
6891 ComposeHeaderEntry *headerentry;
6892 gboolean standard_header = FALSE;
6893 GtkListStore *model;
6896 headerentry = g_new0(ComposeHeaderEntry, 1);
6898 /* Combo box model */
6899 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6900 #if !GTK_CHECK_VERSION(2, 24, 0)
6901 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6903 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6905 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6907 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6909 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6910 COMPOSE_NEWSGROUPS);
6911 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6913 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6914 COMPOSE_FOLLOWUPTO);
6915 compose_add_extra_header_entries(model);
6918 #if GTK_CHECK_VERSION(2, 24, 0)
6919 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6920 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6921 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6922 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6923 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6925 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6926 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6927 G_CALLBACK(compose_grab_focus_cb), compose);
6928 gtk_widget_show(combo);
6930 /* Putting only the combobox child into focus chain of its parent causes
6931 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6932 * This eliminates need to pres Tab twice in order to really get from the
6933 * combobox to next widget. */
6935 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6936 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6939 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6940 compose->header_nextrow, compose->header_nextrow+1,
6941 GTK_SHRINK, GTK_FILL, 0, 0);
6942 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6943 const gchar *last_header_entry = gtk_entry_get_text(
6944 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6946 while (*string != NULL) {
6947 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6948 standard_header = TRUE;
6951 if (standard_header)
6952 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6954 if (!compose->header_last || !standard_header) {
6955 switch(compose->account->protocol) {
6957 header = prefs_common_translated_header_name("Newsgroups:");
6960 header = prefs_common_translated_header_name("To:");
6965 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6967 gtk_editable_set_editable(
6968 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6969 prefs_common.type_any_header);
6971 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6972 G_CALLBACK(compose_grab_focus_cb), compose);
6974 /* Entry field with cleanup button */
6975 button = gtk_button_new();
6976 gtk_button_set_image(GTK_BUTTON(button),
6977 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6978 gtk_widget_show(button);
6979 CLAWS_SET_TIP(button,
6980 _("Delete entry contents"));
6981 entry = gtk_entry_new();
6982 gtk_widget_show(entry);
6983 CLAWS_SET_TIP(entry,
6984 _("Use <tab> to autocomplete from addressbook"));
6985 hbox = gtk_hbox_new (FALSE, 0);
6986 gtk_widget_show(hbox);
6987 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6988 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6989 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6990 compose->header_nextrow, compose->header_nextrow+1,
6991 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6993 g_signal_connect(G_OBJECT(entry), "key-press-event",
6994 G_CALLBACK(compose_headerentry_key_press_event_cb),
6996 g_signal_connect(G_OBJECT(entry), "changed",
6997 G_CALLBACK(compose_headerentry_changed_cb),
6999 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7000 G_CALLBACK(compose_grab_focus_cb), compose);
7002 g_signal_connect(G_OBJECT(button), "clicked",
7003 G_CALLBACK(compose_headerentry_button_clicked_cb),
7007 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7008 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7009 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7010 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7011 G_CALLBACK(compose_header_drag_received_cb),
7013 g_signal_connect(G_OBJECT(entry), "drag-drop",
7014 G_CALLBACK(compose_drag_drop),
7016 g_signal_connect(G_OBJECT(entry), "populate-popup",
7017 G_CALLBACK(compose_entry_popup_extend),
7020 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7022 headerentry->compose = compose;
7023 headerentry->combo = combo;
7024 headerentry->entry = entry;
7025 headerentry->button = button;
7026 headerentry->hbox = hbox;
7027 headerentry->headernum = compose->header_nextrow;
7028 headerentry->type = PREF_NONE;
7030 compose->header_nextrow++;
7031 compose->header_last = headerentry;
7032 compose->header_list =
7033 g_slist_append(compose->header_list,
7037 static void compose_add_header_entry(Compose *compose, const gchar *header,
7038 gchar *text, ComposePrefType pref_type)
7040 ComposeHeaderEntry *last_header = compose->header_last;
7041 gchar *tmp = g_strdup(text), *email;
7042 gboolean replyto_hdr;
7044 replyto_hdr = (!strcasecmp(header,
7045 prefs_common_translated_header_name("Reply-To:")) ||
7047 prefs_common_translated_header_name("Followup-To:")) ||
7049 prefs_common_translated_header_name("In-Reply-To:")));
7051 extract_address(tmp);
7052 email = g_utf8_strdown(tmp, -1);
7054 if (replyto_hdr == FALSE &&
7055 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7057 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7058 header, text, (gint) pref_type);
7064 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7065 gtk_entry_set_text(GTK_ENTRY(
7066 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7068 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7069 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7070 last_header->type = pref_type;
7072 if (replyto_hdr == FALSE)
7073 g_hash_table_insert(compose->email_hashtable, email,
7074 GUINT_TO_POINTER(1));
7081 static void compose_destroy_headerentry(Compose *compose,
7082 ComposeHeaderEntry *headerentry)
7084 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7087 extract_address(text);
7088 email = g_utf8_strdown(text, -1);
7089 g_hash_table_remove(compose->email_hashtable, email);
7093 gtk_widget_destroy(headerentry->combo);
7094 gtk_widget_destroy(headerentry->entry);
7095 gtk_widget_destroy(headerentry->button);
7096 gtk_widget_destroy(headerentry->hbox);
7097 g_free(headerentry);
7100 static void compose_remove_header_entries(Compose *compose)
7103 for (list = compose->header_list; list; list = list->next)
7104 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7106 compose->header_last = NULL;
7107 g_slist_free(compose->header_list);
7108 compose->header_list = NULL;
7109 compose->header_nextrow = 1;
7110 compose_create_header_entry(compose);
7113 static GtkWidget *compose_create_header(Compose *compose)
7115 GtkWidget *from_optmenu_hbox;
7116 GtkWidget *header_table_main;
7117 GtkWidget *header_scrolledwin;
7118 GtkWidget *header_table;
7120 /* parent with account selection and from header */
7121 header_table_main = gtk_table_new(2, 2, FALSE);
7122 gtk_widget_show(header_table_main);
7123 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7125 from_optmenu_hbox = compose_account_option_menu_create(compose);
7126 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7127 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7129 /* child with header labels and entries */
7130 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7131 gtk_widget_show(header_scrolledwin);
7132 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7134 header_table = gtk_table_new(2, 2, FALSE);
7135 gtk_widget_show(header_table);
7136 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7137 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7138 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7139 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7140 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7142 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7143 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7145 compose->header_table = header_table;
7146 compose->header_list = NULL;
7147 compose->header_nextrow = 0;
7149 compose_create_header_entry(compose);
7151 compose->table = NULL;
7153 return header_table_main;
7156 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7158 Compose *compose = (Compose *)data;
7159 GdkEventButton event;
7162 event.time = gtk_get_current_event_time();
7164 return attach_button_pressed(compose->attach_clist, &event, compose);
7167 static GtkWidget *compose_create_attach(Compose *compose)
7169 GtkWidget *attach_scrwin;
7170 GtkWidget *attach_clist;
7172 GtkListStore *store;
7173 GtkCellRenderer *renderer;
7174 GtkTreeViewColumn *column;
7175 GtkTreeSelection *selection;
7177 /* attachment list */
7178 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7179 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7180 GTK_POLICY_AUTOMATIC,
7181 GTK_POLICY_AUTOMATIC);
7182 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7184 store = gtk_list_store_new(N_ATTACH_COLS,
7190 G_TYPE_AUTO_POINTER,
7192 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7193 (GTK_TREE_MODEL(store)));
7194 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7195 g_object_unref(store);
7197 renderer = gtk_cell_renderer_text_new();
7198 column = gtk_tree_view_column_new_with_attributes
7199 (_("Mime type"), renderer, "text",
7200 COL_MIMETYPE, NULL);
7201 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7203 renderer = gtk_cell_renderer_text_new();
7204 column = gtk_tree_view_column_new_with_attributes
7205 (_("Size"), renderer, "text",
7207 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7209 renderer = gtk_cell_renderer_text_new();
7210 column = gtk_tree_view_column_new_with_attributes
7211 (_("Name"), renderer, "text",
7213 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7215 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7216 prefs_common.use_stripes_everywhere);
7217 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7218 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7220 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7221 G_CALLBACK(attach_selected), compose);
7222 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7223 G_CALLBACK(attach_button_pressed), compose);
7224 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7225 G_CALLBACK(popup_attach_button_pressed), compose);
7226 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7227 G_CALLBACK(attach_key_pressed), compose);
7230 gtk_drag_dest_set(attach_clist,
7231 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7232 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7233 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7234 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7235 G_CALLBACK(compose_attach_drag_received_cb),
7237 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7238 G_CALLBACK(compose_drag_drop),
7241 compose->attach_scrwin = attach_scrwin;
7242 compose->attach_clist = attach_clist;
7244 return attach_scrwin;
7247 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7248 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7250 static GtkWidget *compose_create_others(Compose *compose)
7253 GtkWidget *savemsg_checkbtn;
7254 GtkWidget *savemsg_combo;
7255 GtkWidget *savemsg_select;
7258 gchar *folderidentifier;
7260 /* Table for settings */
7261 table = gtk_table_new(3, 1, FALSE);
7262 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7263 gtk_widget_show(table);
7264 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7267 /* Save Message to folder */
7268 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7269 gtk_widget_show(savemsg_checkbtn);
7270 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7271 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7272 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7274 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7275 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7277 #if !GTK_CHECK_VERSION(2, 24, 0)
7278 savemsg_combo = gtk_combo_box_entry_new_text();
7280 savemsg_combo = gtk_combo_box_text_new_with_entry();
7282 compose->savemsg_checkbtn = savemsg_checkbtn;
7283 compose->savemsg_combo = savemsg_combo;
7284 gtk_widget_show(savemsg_combo);
7286 if (prefs_common.compose_save_to_history)
7287 #if !GTK_CHECK_VERSION(2, 24, 0)
7288 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7289 prefs_common.compose_save_to_history);
7291 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7292 prefs_common.compose_save_to_history);
7294 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7295 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7296 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7297 G_CALLBACK(compose_grab_focus_cb), compose);
7298 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7299 folderidentifier = folder_item_get_identifier(account_get_special_folder
7300 (compose->account, F_OUTBOX));
7301 compose_set_save_to(compose, folderidentifier);
7302 g_free(folderidentifier);
7305 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7306 gtk_widget_show(savemsg_select);
7307 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7308 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7309 G_CALLBACK(compose_savemsg_select_cb),
7315 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7317 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7318 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7321 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7326 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7329 path = folder_item_get_identifier(dest);
7331 compose_set_save_to(compose, path);
7335 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7336 GdkAtom clip, GtkTextIter *insert_place);
7339 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7343 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7345 if (event->button == 3) {
7347 GtkTextIter sel_start, sel_end;
7348 gboolean stuff_selected;
7350 /* move the cursor to allow GtkAspell to check the word
7351 * under the mouse */
7352 if (event->x && event->y) {
7353 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7354 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7356 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7359 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7360 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7363 stuff_selected = gtk_text_buffer_get_selection_bounds(
7365 &sel_start, &sel_end);
7367 gtk_text_buffer_place_cursor (buffer, &iter);
7368 /* reselect stuff */
7370 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7371 gtk_text_buffer_select_range(buffer,
7372 &sel_start, &sel_end);
7374 return FALSE; /* pass the event so that the right-click goes through */
7377 if (event->button == 2) {
7382 /* get the middle-click position to paste at the correct place */
7383 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7384 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7386 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7389 entry_paste_clipboard(compose, text,
7390 prefs_common.linewrap_pastes,
7391 GDK_SELECTION_PRIMARY, &iter);
7399 static void compose_spell_menu_changed(void *data)
7401 Compose *compose = (Compose *)data;
7403 GtkWidget *menuitem;
7404 GtkWidget *parent_item;
7405 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7408 if (compose->gtkaspell == NULL)
7411 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7412 "/Menu/Spelling/Options");
7414 /* setting the submenu removes /Spelling/Options from the factory
7415 * so we need to save it */
7417 if (parent_item == NULL) {
7418 parent_item = compose->aspell_options_menu;
7419 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7421 compose->aspell_options_menu = parent_item;
7423 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7425 spell_menu = g_slist_reverse(spell_menu);
7426 for (items = spell_menu;
7427 items; items = items->next) {
7428 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7429 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7430 gtk_widget_show(GTK_WIDGET(menuitem));
7432 g_slist_free(spell_menu);
7434 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7435 gtk_widget_show(parent_item);
7438 static void compose_dict_changed(void *data)
7440 Compose *compose = (Compose *) data;
7442 if(!compose->gtkaspell)
7444 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7447 gtkaspell_highlight_all(compose->gtkaspell);
7448 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7452 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7454 Compose *compose = (Compose *)data;
7455 GdkEventButton event;
7458 event.time = gtk_get_current_event_time();
7462 return text_clicked(compose->text, &event, compose);
7465 static gboolean compose_force_window_origin = TRUE;
7466 static Compose *compose_create(PrefsAccount *account,
7475 GtkWidget *handlebox;
7477 GtkWidget *notebook;
7479 GtkWidget *attach_hbox;
7480 GtkWidget *attach_lab1;
7481 GtkWidget *attach_lab2;
7486 GtkWidget *subject_hbox;
7487 GtkWidget *subject_frame;
7488 GtkWidget *subject_entry;
7492 GtkWidget *edit_vbox;
7493 GtkWidget *ruler_hbox;
7495 GtkWidget *scrolledwin;
7497 GtkTextBuffer *buffer;
7498 GtkClipboard *clipboard;
7500 UndoMain *undostruct;
7502 GtkWidget *popupmenu;
7503 GtkWidget *tmpl_menu;
7504 GtkActionGroup *action_group = NULL;
7507 GtkAspell * gtkaspell = NULL;
7510 static GdkGeometry geometry;
7512 cm_return_val_if_fail(account != NULL, NULL);
7514 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7515 &default_header_bgcolor);
7516 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7517 &default_header_color);
7519 debug_print("Creating compose window...\n");
7520 compose = g_new0(Compose, 1);
7522 compose->batch = batch;
7523 compose->account = account;
7524 compose->folder = folder;
7526 compose->mutex = cm_mutex_new();
7527 compose->set_cursor_pos = -1;
7529 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7531 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7532 gtk_widget_set_size_request(window, prefs_common.compose_width,
7533 prefs_common.compose_height);
7535 if (!geometry.max_width) {
7536 geometry.max_width = gdk_screen_width();
7537 geometry.max_height = gdk_screen_height();
7540 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7541 &geometry, GDK_HINT_MAX_SIZE);
7542 if (!geometry.min_width) {
7543 geometry.min_width = 600;
7544 geometry.min_height = 440;
7546 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7547 &geometry, GDK_HINT_MIN_SIZE);
7549 #ifndef GENERIC_UMPC
7550 if (compose_force_window_origin)
7551 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7552 prefs_common.compose_y);
7554 g_signal_connect(G_OBJECT(window), "delete_event",
7555 G_CALLBACK(compose_delete_cb), compose);
7556 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7557 gtk_widget_realize(window);
7559 gtkut_widget_set_composer_icon(window);
7561 vbox = gtk_vbox_new(FALSE, 0);
7562 gtk_container_add(GTK_CONTAINER(window), vbox);
7564 compose->ui_manager = gtk_ui_manager_new();
7565 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7566 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7567 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7568 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7569 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7570 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7571 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7572 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7573 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7574 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7693 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)
7694 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)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7700 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)
7701 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)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7706 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)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7710 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)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7716 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)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7723 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)
7724 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)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7737 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)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7746 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7750 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7755 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7756 gtk_widget_show_all(menubar);
7758 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7759 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7761 if (prefs_common.toolbar_detachable) {
7762 handlebox = gtk_handle_box_new();
7764 handlebox = gtk_hbox_new(FALSE, 0);
7766 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7768 gtk_widget_realize(handlebox);
7769 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7772 vbox2 = gtk_vbox_new(FALSE, 2);
7773 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7774 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7777 notebook = gtk_notebook_new();
7778 gtk_widget_show(notebook);
7780 /* header labels and entries */
7781 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7782 compose_create_header(compose),
7783 gtk_label_new_with_mnemonic(_("Hea_der")));
7784 /* attachment list */
7785 attach_hbox = gtk_hbox_new(FALSE, 0);
7786 gtk_widget_show(attach_hbox);
7788 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7789 gtk_widget_show(attach_lab1);
7790 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7792 attach_lab2 = gtk_label_new("");
7793 gtk_widget_show(attach_lab2);
7794 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7796 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7797 compose_create_attach(compose),
7800 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7801 compose_create_others(compose),
7802 gtk_label_new_with_mnemonic(_("Othe_rs")));
7805 subject_hbox = gtk_hbox_new(FALSE, 0);
7806 gtk_widget_show(subject_hbox);
7808 subject_frame = gtk_frame_new(NULL);
7809 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7810 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7811 gtk_widget_show(subject_frame);
7813 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7814 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7815 gtk_widget_show(subject);
7817 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7818 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7819 gtk_widget_show(label);
7822 subject_entry = claws_spell_entry_new();
7824 subject_entry = gtk_entry_new();
7826 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7827 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7828 G_CALLBACK(compose_grab_focus_cb), compose);
7829 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7830 gtk_widget_show(subject_entry);
7831 compose->subject_entry = subject_entry;
7832 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7834 edit_vbox = gtk_vbox_new(FALSE, 0);
7836 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7839 ruler_hbox = gtk_hbox_new(FALSE, 0);
7840 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7842 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7843 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7844 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7848 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7849 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7850 GTK_POLICY_AUTOMATIC,
7851 GTK_POLICY_AUTOMATIC);
7852 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7854 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7856 text = gtk_text_view_new();
7857 if (prefs_common.show_compose_margin) {
7858 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7859 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7861 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7862 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7863 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7864 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7865 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7867 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7868 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7869 G_CALLBACK(compose_edit_size_alloc),
7871 g_signal_connect(G_OBJECT(buffer), "changed",
7872 G_CALLBACK(compose_changed_cb), compose);
7873 g_signal_connect(G_OBJECT(text), "grab_focus",
7874 G_CALLBACK(compose_grab_focus_cb), compose);
7875 g_signal_connect(G_OBJECT(buffer), "insert_text",
7876 G_CALLBACK(text_inserted), compose);
7877 g_signal_connect(G_OBJECT(text), "button_press_event",
7878 G_CALLBACK(text_clicked), compose);
7879 g_signal_connect(G_OBJECT(text), "popup-menu",
7880 G_CALLBACK(compose_popup_menu), compose);
7881 g_signal_connect(G_OBJECT(subject_entry), "changed",
7882 G_CALLBACK(compose_changed_cb), compose);
7883 g_signal_connect(G_OBJECT(subject_entry), "activate",
7884 G_CALLBACK(compose_subject_entry_activated), compose);
7887 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7888 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7889 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7890 g_signal_connect(G_OBJECT(text), "drag_data_received",
7891 G_CALLBACK(compose_insert_drag_received_cb),
7893 g_signal_connect(G_OBJECT(text), "drag-drop",
7894 G_CALLBACK(compose_drag_drop),
7896 g_signal_connect(G_OBJECT(text), "key-press-event",
7897 G_CALLBACK(completion_set_focus_to_subject),
7899 gtk_widget_show_all(vbox);
7901 /* pane between attach clist and text */
7902 paned = gtk_vpaned_new();
7903 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7904 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7905 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7906 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7907 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7908 G_CALLBACK(compose_notebook_size_alloc), paned);
7910 gtk_widget_show_all(paned);
7913 if (prefs_common.textfont) {
7914 PangoFontDescription *font_desc;
7916 font_desc = pango_font_description_from_string
7917 (prefs_common.textfont);
7919 gtk_widget_modify_font(text, font_desc);
7920 pango_font_description_free(font_desc);
7924 gtk_action_group_add_actions(action_group, compose_popup_entries,
7925 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7926 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7927 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7928 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7929 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7930 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7931 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7933 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7935 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7939 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7941 undostruct = undo_init(text);
7942 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7945 address_completion_start(window);
7947 compose->window = window;
7948 compose->vbox = vbox;
7949 compose->menubar = menubar;
7950 compose->handlebox = handlebox;
7952 compose->vbox2 = vbox2;
7954 compose->paned = paned;
7956 compose->attach_label = attach_lab2;
7958 compose->notebook = notebook;
7959 compose->edit_vbox = edit_vbox;
7960 compose->ruler_hbox = ruler_hbox;
7961 compose->ruler = ruler;
7962 compose->scrolledwin = scrolledwin;
7963 compose->text = text;
7965 compose->focused_editable = NULL;
7967 compose->popupmenu = popupmenu;
7969 compose->tmpl_menu = tmpl_menu;
7971 compose->mode = mode;
7972 compose->rmode = mode;
7974 compose->targetinfo = NULL;
7975 compose->replyinfo = NULL;
7976 compose->fwdinfo = NULL;
7978 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7979 g_str_equal, (GDestroyNotify) g_free, NULL);
7981 compose->replyto = NULL;
7983 compose->bcc = NULL;
7984 compose->followup_to = NULL;
7986 compose->ml_post = NULL;
7988 compose->inreplyto = NULL;
7989 compose->references = NULL;
7990 compose->msgid = NULL;
7991 compose->boundary = NULL;
7993 compose->autowrap = prefs_common.autowrap;
7994 compose->autoindent = prefs_common.auto_indent;
7995 compose->use_signing = FALSE;
7996 compose->use_encryption = FALSE;
7997 compose->privacy_system = NULL;
7998 compose->encdata = NULL;
8000 compose->modified = FALSE;
8002 compose->return_receipt = FALSE;
8004 compose->to_list = NULL;
8005 compose->newsgroup_list = NULL;
8007 compose->undostruct = undostruct;
8009 compose->sig_str = NULL;
8011 compose->exteditor_file = NULL;
8012 compose->exteditor_pid = -1;
8013 compose->exteditor_tag = -1;
8014 compose->exteditor_socket = NULL;
8015 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8017 compose->folder_update_callback_id =
8018 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8019 compose_update_folder_hook,
8020 (gpointer) compose);
8023 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8024 if (mode != COMPOSE_REDIRECT) {
8025 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8026 strcmp(prefs_common.dictionary, "")) {
8027 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8028 prefs_common.alt_dictionary,
8029 conv_get_locale_charset_str(),
8030 prefs_common.misspelled_col,
8031 prefs_common.check_while_typing,
8032 prefs_common.recheck_when_changing_dict,
8033 prefs_common.use_alternate,
8034 prefs_common.use_both_dicts,
8035 GTK_TEXT_VIEW(text),
8036 GTK_WINDOW(compose->window),
8037 compose_dict_changed,
8038 compose_spell_menu_changed,
8041 alertpanel_error(_("Spell checker could not "
8043 gtkaspell_checkers_strerror());
8044 gtkaspell_checkers_reset_error();
8046 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8050 compose->gtkaspell = gtkaspell;
8051 compose_spell_menu_changed(compose);
8052 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8055 compose_select_account(compose, account, TRUE);
8057 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8058 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8060 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8061 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8063 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8064 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8066 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8067 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8069 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8070 if (account->protocol != A_NNTP)
8071 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8072 prefs_common_translated_header_name("To:"));
8074 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8075 prefs_common_translated_header_name("Newsgroups:"));
8077 #ifndef USE_ALT_ADDRBOOK
8078 addressbook_set_target_compose(compose);
8080 if (mode != COMPOSE_REDIRECT)
8081 compose_set_template_menu(compose);
8083 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8086 compose_list = g_list_append(compose_list, compose);
8088 if (!prefs_common.show_ruler)
8089 gtk_widget_hide(ruler_hbox);
8091 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8094 compose->priority = PRIORITY_NORMAL;
8095 compose_update_priority_menu_item(compose);
8097 compose_set_out_encoding(compose);
8100 compose_update_actions_menu(compose);
8102 /* Privacy Systems menu */
8103 compose_update_privacy_systems_menu(compose);
8105 activate_privacy_system(compose, account, TRUE);
8106 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8108 gtk_widget_realize(window);
8110 gtk_widget_show(window);
8116 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8121 GtkWidget *optmenubox;
8122 GtkWidget *fromlabel;
8125 GtkWidget *from_name = NULL;
8127 gint num = 0, def_menu = 0;
8129 accounts = account_get_list();
8130 cm_return_val_if_fail(accounts != NULL, NULL);
8132 optmenubox = gtk_event_box_new();
8133 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8134 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8136 hbox = gtk_hbox_new(FALSE, 4);
8137 from_name = gtk_entry_new();
8139 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8140 G_CALLBACK(compose_grab_focus_cb), compose);
8141 g_signal_connect_after(G_OBJECT(from_name), "activate",
8142 G_CALLBACK(from_name_activate_cb), optmenu);
8144 for (; accounts != NULL; accounts = accounts->next, num++) {
8145 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8146 gchar *name, *from = NULL;
8148 if (ac == compose->account) def_menu = num;
8150 name = g_markup_printf_escaped("<i>%s</i>",
8153 if (ac == compose->account) {
8154 if (ac->name && *ac->name) {
8156 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8157 from = g_strdup_printf("%s <%s>",
8159 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8161 from = g_strdup_printf("%s",
8163 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8165 if (cur_account != compose->account) {
8166 gtk_widget_modify_base(
8167 GTK_WIDGET(from_name),
8168 GTK_STATE_NORMAL, &default_header_bgcolor);
8169 gtk_widget_modify_text(
8170 GTK_WIDGET(from_name),
8171 GTK_STATE_NORMAL, &default_header_color);
8174 COMBOBOX_ADD(menu, name, ac->account_id);
8179 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8181 g_signal_connect(G_OBJECT(optmenu), "changed",
8182 G_CALLBACK(account_activated),
8184 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8185 G_CALLBACK(compose_entry_popup_extend),
8188 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8189 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8191 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8192 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8193 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8195 /* Putting only the GtkEntry into focus chain of parent hbox causes
8196 * the account selector combobox next to it to be unreachable when
8197 * navigating widgets in GtkTable with up/down arrow keys.
8198 * Note: gtk_widget_set_can_focus() was not enough. */
8200 l = g_list_prepend(l, from_name);
8201 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8204 CLAWS_SET_TIP(optmenubox,
8205 _("Account to use for this email"));
8206 CLAWS_SET_TIP(from_name,
8207 _("Sender address to be used"));
8209 compose->account_combo = optmenu;
8210 compose->from_name = from_name;
8215 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8217 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8218 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8219 Compose *compose = (Compose *) data;
8221 compose->priority = value;
8225 static void compose_reply_change_mode(Compose *compose,
8228 gboolean was_modified = compose->modified;
8230 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8232 cm_return_if_fail(compose->replyinfo != NULL);
8234 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8236 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8238 if (action == COMPOSE_REPLY_TO_ALL)
8240 if (action == COMPOSE_REPLY_TO_SENDER)
8242 if (action == COMPOSE_REPLY_TO_LIST)
8245 compose_remove_header_entries(compose);
8246 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8247 if (compose->account->set_autocc && compose->account->auto_cc)
8248 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8250 if (compose->account->set_autobcc && compose->account->auto_bcc)
8251 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8253 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8254 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8255 compose_show_first_last_header(compose, TRUE);
8256 compose->modified = was_modified;
8257 compose_set_title(compose);
8260 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8262 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8263 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8264 Compose *compose = (Compose *) data;
8267 compose_reply_change_mode(compose, value);
8270 static void compose_update_priority_menu_item(Compose * compose)
8272 GtkWidget *menuitem = NULL;
8273 switch (compose->priority) {
8274 case PRIORITY_HIGHEST:
8275 menuitem = gtk_ui_manager_get_widget
8276 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8279 menuitem = gtk_ui_manager_get_widget
8280 (compose->ui_manager, "/Menu/Options/Priority/High");
8282 case PRIORITY_NORMAL:
8283 menuitem = gtk_ui_manager_get_widget
8284 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8287 menuitem = gtk_ui_manager_get_widget
8288 (compose->ui_manager, "/Menu/Options/Priority/Low");
8290 case PRIORITY_LOWEST:
8291 menuitem = gtk_ui_manager_get_widget
8292 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8295 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8298 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8300 Compose *compose = (Compose *) data;
8302 gboolean can_sign = FALSE, can_encrypt = FALSE;
8304 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8306 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8309 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8310 g_free(compose->privacy_system);
8311 compose->privacy_system = NULL;
8312 g_free(compose->encdata);
8313 compose->encdata = NULL;
8314 if (systemid != NULL) {
8315 compose->privacy_system = g_strdup(systemid);
8317 can_sign = privacy_system_can_sign(systemid);
8318 can_encrypt = privacy_system_can_encrypt(systemid);
8321 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8323 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8327 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8329 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8330 GtkWidget *menuitem = NULL;
8331 GList *children, *amenu;
8332 gboolean can_sign = FALSE, can_encrypt = FALSE;
8333 gboolean found = FALSE;
8335 if (compose->privacy_system != NULL) {
8337 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8338 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8339 cm_return_if_fail(menuitem != NULL);
8341 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8344 while (amenu != NULL) {
8345 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8346 if (systemid != NULL) {
8347 if (strcmp(systemid, compose->privacy_system) == 0 &&
8348 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8349 menuitem = GTK_WIDGET(amenu->data);
8351 can_sign = privacy_system_can_sign(systemid);
8352 can_encrypt = privacy_system_can_encrypt(systemid);
8356 } else if (strlen(compose->privacy_system) == 0 &&
8357 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8358 menuitem = GTK_WIDGET(amenu->data);
8361 can_encrypt = FALSE;
8366 amenu = amenu->next;
8368 g_list_free(children);
8369 if (menuitem != NULL)
8370 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8372 if (warn && !found && strlen(compose->privacy_system)) {
8373 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8374 "will not be able to sign or encrypt this message."),
8375 compose->privacy_system);
8379 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8380 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8383 static void compose_set_out_encoding(Compose *compose)
8385 CharSet out_encoding;
8386 const gchar *branch = NULL;
8387 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8389 switch(out_encoding) {
8390 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8391 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8392 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8393 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8394 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8395 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8396 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8397 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8398 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8399 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8400 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8401 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8402 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8403 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8404 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8405 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8406 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8407 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8408 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8409 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8410 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8411 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8412 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8413 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8414 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8415 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8416 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8417 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8418 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8419 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8420 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8421 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8422 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8423 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8425 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8428 static void compose_set_template_menu(Compose *compose)
8430 GSList *tmpl_list, *cur;
8434 tmpl_list = template_get_config();
8436 menu = gtk_menu_new();
8438 gtk_menu_set_accel_group (GTK_MENU (menu),
8439 gtk_ui_manager_get_accel_group(compose->ui_manager));
8440 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8441 Template *tmpl = (Template *)cur->data;
8442 gchar *accel_path = NULL;
8443 item = gtk_menu_item_new_with_label(tmpl->name);
8444 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8445 g_signal_connect(G_OBJECT(item), "activate",
8446 G_CALLBACK(compose_template_activate_cb),
8448 g_object_set_data(G_OBJECT(item), "template", tmpl);
8449 gtk_widget_show(item);
8450 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8451 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8455 gtk_widget_show(menu);
8456 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8459 void compose_update_actions_menu(Compose *compose)
8461 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8464 static void compose_update_privacy_systems_menu(Compose *compose)
8466 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8467 GSList *systems, *cur;
8469 GtkWidget *system_none;
8471 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8472 GtkWidget *privacy_menu = gtk_menu_new();
8474 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8475 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8477 g_signal_connect(G_OBJECT(system_none), "activate",
8478 G_CALLBACK(compose_set_privacy_system_cb), compose);
8480 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8481 gtk_widget_show(system_none);
8483 systems = privacy_get_system_ids();
8484 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8485 gchar *systemid = cur->data;
8487 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8488 widget = gtk_radio_menu_item_new_with_label(group,
8489 privacy_system_get_name(systemid));
8490 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8491 g_strdup(systemid), g_free);
8492 g_signal_connect(G_OBJECT(widget), "activate",
8493 G_CALLBACK(compose_set_privacy_system_cb), compose);
8495 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8496 gtk_widget_show(widget);
8499 g_slist_free(systems);
8500 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8501 gtk_widget_show_all(privacy_menu);
8502 gtk_widget_show_all(privacy_menuitem);
8505 void compose_reflect_prefs_all(void)
8510 for (cur = compose_list; cur != NULL; cur = cur->next) {
8511 compose = (Compose *)cur->data;
8512 compose_set_template_menu(compose);
8516 void compose_reflect_prefs_pixmap_theme(void)
8521 for (cur = compose_list; cur != NULL; cur = cur->next) {
8522 compose = (Compose *)cur->data;
8523 toolbar_update(TOOLBAR_COMPOSE, compose);
8527 static const gchar *compose_quote_char_from_context(Compose *compose)
8529 const gchar *qmark = NULL;
8531 cm_return_val_if_fail(compose != NULL, NULL);
8533 switch (compose->mode) {
8534 /* use forward-specific quote char */
8535 case COMPOSE_FORWARD:
8536 case COMPOSE_FORWARD_AS_ATTACH:
8537 case COMPOSE_FORWARD_INLINE:
8538 if (compose->folder && compose->folder->prefs &&
8539 compose->folder->prefs->forward_with_format)
8540 qmark = compose->folder->prefs->forward_quotemark;
8541 else if (compose->account->forward_with_format)
8542 qmark = compose->account->forward_quotemark;
8544 qmark = prefs_common.fw_quotemark;
8547 /* use reply-specific quote char in all other modes */
8549 if (compose->folder && compose->folder->prefs &&
8550 compose->folder->prefs->reply_with_format)
8551 qmark = compose->folder->prefs->reply_quotemark;
8552 else if (compose->account->reply_with_format)
8553 qmark = compose->account->reply_quotemark;
8555 qmark = prefs_common.quotemark;
8559 if (qmark == NULL || *qmark == '\0')
8565 static void compose_template_apply(Compose *compose, Template *tmpl,
8569 GtkTextBuffer *buffer;
8573 gchar *parsed_str = NULL;
8574 gint cursor_pos = 0;
8575 const gchar *err_msg = _("The body of the template has an error at line %d.");
8578 /* process the body */
8580 text = GTK_TEXT_VIEW(compose->text);
8581 buffer = gtk_text_view_get_buffer(text);
8584 qmark = compose_quote_char_from_context(compose);
8586 if (compose->replyinfo != NULL) {
8589 gtk_text_buffer_set_text(buffer, "", -1);
8590 mark = gtk_text_buffer_get_insert(buffer);
8591 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8593 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8594 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8596 } else if (compose->fwdinfo != NULL) {
8599 gtk_text_buffer_set_text(buffer, "", -1);
8600 mark = gtk_text_buffer_get_insert(buffer);
8601 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8603 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8604 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8607 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8609 GtkTextIter start, end;
8612 gtk_text_buffer_get_start_iter(buffer, &start);
8613 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8614 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8616 /* clear the buffer now */
8618 gtk_text_buffer_set_text(buffer, "", -1);
8620 parsed_str = compose_quote_fmt(compose, dummyinfo,
8621 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8622 procmsg_msginfo_free( &dummyinfo );
8628 gtk_text_buffer_set_text(buffer, "", -1);
8629 mark = gtk_text_buffer_get_insert(buffer);
8630 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8633 if (replace && parsed_str && compose->account->auto_sig)
8634 compose_insert_sig(compose, FALSE);
8636 if (replace && parsed_str) {
8637 gtk_text_buffer_get_start_iter(buffer, &iter);
8638 gtk_text_buffer_place_cursor(buffer, &iter);
8642 cursor_pos = quote_fmt_get_cursor_pos();
8643 compose->set_cursor_pos = cursor_pos;
8644 if (cursor_pos == -1)
8646 gtk_text_buffer_get_start_iter(buffer, &iter);
8647 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8648 gtk_text_buffer_place_cursor(buffer, &iter);
8651 /* process the other fields */
8653 compose_template_apply_fields(compose, tmpl);
8654 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8655 quote_fmt_reset_vartable();
8656 compose_changed_cb(NULL, compose);
8659 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8660 gtkaspell_highlight_all(compose->gtkaspell);
8664 static void compose_template_apply_fields_error(const gchar *header)
8669 tr = g_strdup(C_("'%s' stands for a header name",
8670 "Template '%s' format error."));
8671 text = g_strdup_printf(tr, prefs_common_translated_header_name(header));
8672 alertpanel_error(text);
8678 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8680 MsgInfo* dummyinfo = NULL;
8681 MsgInfo *msginfo = NULL;
8684 if (compose->replyinfo != NULL)
8685 msginfo = compose->replyinfo;
8686 else if (compose->fwdinfo != NULL)
8687 msginfo = compose->fwdinfo;
8689 dummyinfo = compose_msginfo_new_from_compose(compose);
8690 msginfo = dummyinfo;
8693 if (tmpl->from && *tmpl->from != '\0') {
8695 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8696 compose->gtkaspell);
8698 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8700 quote_fmt_scan_string(tmpl->from);
8703 buf = quote_fmt_get_buffer();
8705 compose_template_apply_fields_error("From");
8707 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8711 if (tmpl->to && *tmpl->to != '\0') {
8713 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8714 compose->gtkaspell);
8716 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8718 quote_fmt_scan_string(tmpl->to);
8721 buf = quote_fmt_get_buffer();
8723 compose_template_apply_fields_error("To");
8725 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8729 if (tmpl->cc && *tmpl->cc != '\0') {
8731 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8732 compose->gtkaspell);
8734 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8736 quote_fmt_scan_string(tmpl->cc);
8739 buf = quote_fmt_get_buffer();
8741 compose_template_apply_fields_error("Cc");
8743 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8747 if (tmpl->bcc && *tmpl->bcc != '\0') {
8749 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8750 compose->gtkaspell);
8752 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8754 quote_fmt_scan_string(tmpl->bcc);
8757 buf = quote_fmt_get_buffer();
8759 compose_template_apply_fields_error("Bcc");
8761 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8765 if (tmpl->replyto && *tmpl->replyto != '\0') {
8767 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8768 compose->gtkaspell);
8770 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8772 quote_fmt_scan_string(tmpl->replyto);
8775 buf = quote_fmt_get_buffer();
8777 compose_template_apply_fields_error("Reply-To");
8779 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8783 /* process the subject */
8784 if (tmpl->subject && *tmpl->subject != '\0') {
8786 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8787 compose->gtkaspell);
8789 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8791 quote_fmt_scan_string(tmpl->subject);
8794 buf = quote_fmt_get_buffer();
8796 compose_template_apply_fields_error("Subject");
8798 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8802 procmsg_msginfo_free( &dummyinfo );
8805 static void compose_destroy(Compose *compose)
8807 GtkAllocation allocation;
8808 GtkTextBuffer *buffer;
8809 GtkClipboard *clipboard;
8811 compose_list = g_list_remove(compose_list, compose);
8813 if (compose->updating) {
8814 debug_print("danger, not destroying anything now\n");
8815 compose->deferred_destroy = TRUE;
8819 /* NOTE: address_completion_end() does nothing with the window
8820 * however this may change. */
8821 address_completion_end(compose->window);
8823 slist_free_strings_full(compose->to_list);
8824 slist_free_strings_full(compose->newsgroup_list);
8825 slist_free_strings_full(compose->header_list);
8827 slist_free_strings_full(extra_headers);
8828 extra_headers = NULL;
8830 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8832 g_hash_table_destroy(compose->email_hashtable);
8834 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8835 compose->folder_update_callback_id);
8837 procmsg_msginfo_free(&(compose->targetinfo));
8838 procmsg_msginfo_free(&(compose->replyinfo));
8839 procmsg_msginfo_free(&(compose->fwdinfo));
8841 g_free(compose->replyto);
8842 g_free(compose->cc);
8843 g_free(compose->bcc);
8844 g_free(compose->newsgroups);
8845 g_free(compose->followup_to);
8847 g_free(compose->ml_post);
8849 g_free(compose->inreplyto);
8850 g_free(compose->references);
8851 g_free(compose->msgid);
8852 g_free(compose->boundary);
8854 g_free(compose->redirect_filename);
8855 if (compose->undostruct)
8856 undo_destroy(compose->undostruct);
8858 g_free(compose->sig_str);
8860 g_free(compose->exteditor_file);
8862 g_free(compose->orig_charset);
8864 g_free(compose->privacy_system);
8865 g_free(compose->encdata);
8867 #ifndef USE_ALT_ADDRBOOK
8868 if (addressbook_get_target_compose() == compose)
8869 addressbook_set_target_compose(NULL);
8872 if (compose->gtkaspell) {
8873 gtkaspell_delete(compose->gtkaspell);
8874 compose->gtkaspell = NULL;
8878 if (!compose->batch) {
8879 gtk_widget_get_allocation(compose->window, &allocation);
8880 prefs_common.compose_width = allocation.width;
8881 prefs_common.compose_height = allocation.height;
8884 if (!gtk_widget_get_parent(compose->paned))
8885 gtk_widget_destroy(compose->paned);
8886 gtk_widget_destroy(compose->popupmenu);
8888 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8889 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8890 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8892 gtk_widget_destroy(compose->window);
8893 toolbar_destroy(compose->toolbar);
8894 g_free(compose->toolbar);
8895 cm_mutex_free(compose->mutex);
8899 static void compose_attach_info_free(AttachInfo *ainfo)
8901 g_free(ainfo->file);
8902 g_free(ainfo->content_type);
8903 g_free(ainfo->name);
8904 g_free(ainfo->charset);
8908 static void compose_attach_update_label(Compose *compose)
8913 GtkTreeModel *model;
8918 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8919 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8920 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8924 while(gtk_tree_model_iter_next(model, &iter))
8927 text = g_strdup_printf("(%d)", i);
8928 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8932 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8934 Compose *compose = (Compose *)data;
8935 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8936 GtkTreeSelection *selection;
8938 GtkTreeModel *model;
8940 selection = gtk_tree_view_get_selection(tree_view);
8941 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8946 for (cur = sel; cur != NULL; cur = cur->next) {
8947 GtkTreePath *path = cur->data;
8948 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8951 gtk_tree_path_free(path);
8954 for (cur = sel; cur != NULL; cur = cur->next) {
8955 GtkTreeRowReference *ref = cur->data;
8956 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8959 if (gtk_tree_model_get_iter(model, &iter, path))
8960 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8962 gtk_tree_path_free(path);
8963 gtk_tree_row_reference_free(ref);
8967 compose_attach_update_label(compose);
8970 static struct _AttachProperty
8973 GtkWidget *mimetype_entry;
8974 GtkWidget *encoding_optmenu;
8975 GtkWidget *path_entry;
8976 GtkWidget *filename_entry;
8978 GtkWidget *cancel_btn;
8981 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8983 gtk_tree_path_free((GtkTreePath *)ptr);
8986 static void compose_attach_property(GtkAction *action, gpointer data)
8988 Compose *compose = (Compose *)data;
8989 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8991 GtkComboBox *optmenu;
8992 GtkTreeSelection *selection;
8994 GtkTreeModel *model;
8997 static gboolean cancelled;
8999 /* only if one selected */
9000 selection = gtk_tree_view_get_selection(tree_view);
9001 if (gtk_tree_selection_count_selected_rows(selection) != 1)
9004 sel = gtk_tree_selection_get_selected_rows(selection, &model);
9008 path = (GtkTreePath *) sel->data;
9009 gtk_tree_model_get_iter(model, &iter, path);
9010 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
9013 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9019 if (!attach_prop.window)
9020 compose_attach_property_create(&cancelled);
9021 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9022 gtk_widget_grab_focus(attach_prop.ok_btn);
9023 gtk_widget_show(attach_prop.window);
9024 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9025 GTK_WINDOW(compose->window));
9027 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9028 if (ainfo->encoding == ENC_UNKNOWN)
9029 combobox_select_by_data(optmenu, ENC_BASE64);
9031 combobox_select_by_data(optmenu, ainfo->encoding);
9033 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9034 ainfo->content_type ? ainfo->content_type : "");
9035 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9036 ainfo->file ? ainfo->file : "");
9037 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9038 ainfo->name ? ainfo->name : "");
9041 const gchar *entry_text;
9043 gchar *cnttype = NULL;
9050 gtk_widget_hide(attach_prop.window);
9051 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9056 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9057 if (*entry_text != '\0') {
9060 text = g_strstrip(g_strdup(entry_text));
9061 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9062 cnttype = g_strdup(text);
9065 alertpanel_error(_("Invalid MIME type."));
9071 ainfo->encoding = combobox_get_active_data(optmenu);
9073 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9074 if (*entry_text != '\0') {
9075 if (is_file_exist(entry_text) &&
9076 (size = get_file_size(entry_text)) > 0)
9077 file = g_strdup(entry_text);
9080 (_("File doesn't exist or is empty."));
9086 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9087 if (*entry_text != '\0') {
9088 g_free(ainfo->name);
9089 ainfo->name = g_strdup(entry_text);
9093 g_free(ainfo->content_type);
9094 ainfo->content_type = cnttype;
9097 g_free(ainfo->file);
9101 ainfo->size = (goffset)size;
9103 /* update tree store */
9104 text = to_human_readable(ainfo->size);
9105 gtk_tree_model_get_iter(model, &iter, path);
9106 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9107 COL_MIMETYPE, ainfo->content_type,
9109 COL_NAME, ainfo->name,
9110 COL_CHARSET, ainfo->charset,
9116 gtk_tree_path_free(path);
9119 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9121 label = gtk_label_new(str); \
9122 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9123 GTK_FILL, 0, 0, 0); \
9124 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9126 entry = gtk_entry_new(); \
9127 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9128 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9131 static void compose_attach_property_create(gboolean *cancelled)
9137 GtkWidget *mimetype_entry;
9140 GtkListStore *optmenu_menu;
9141 GtkWidget *path_entry;
9142 GtkWidget *filename_entry;
9145 GtkWidget *cancel_btn;
9146 GList *mime_type_list, *strlist;
9149 debug_print("Creating attach_property window...\n");
9151 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9152 gtk_widget_set_size_request(window, 480, -1);
9153 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9154 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9155 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9156 g_signal_connect(G_OBJECT(window), "delete_event",
9157 G_CALLBACK(attach_property_delete_event),
9159 g_signal_connect(G_OBJECT(window), "key_press_event",
9160 G_CALLBACK(attach_property_key_pressed),
9163 vbox = gtk_vbox_new(FALSE, 8);
9164 gtk_container_add(GTK_CONTAINER(window), vbox);
9166 table = gtk_table_new(4, 2, FALSE);
9167 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9168 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9169 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9171 label = gtk_label_new(_("MIME type"));
9172 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9174 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9175 #if !GTK_CHECK_VERSION(2, 24, 0)
9176 mimetype_entry = gtk_combo_box_entry_new_text();
9178 mimetype_entry = gtk_combo_box_text_new_with_entry();
9180 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9181 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9183 /* stuff with list */
9184 mime_type_list = procmime_get_mime_type_list();
9186 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9187 MimeType *type = (MimeType *) mime_type_list->data;
9190 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9192 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9195 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9196 (GCompareFunc)strcmp2);
9199 for (mime_type_list = strlist; mime_type_list != NULL;
9200 mime_type_list = mime_type_list->next) {
9201 #if !GTK_CHECK_VERSION(2, 24, 0)
9202 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9204 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9206 g_free(mime_type_list->data);
9208 g_list_free(strlist);
9209 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9210 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9212 label = gtk_label_new(_("Encoding"));
9213 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9215 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9217 hbox = gtk_hbox_new(FALSE, 0);
9218 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9219 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9221 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9222 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9224 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9225 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9226 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9227 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9228 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9230 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9232 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9233 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9235 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9236 &ok_btn, GTK_STOCK_OK,
9238 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9239 gtk_widget_grab_default(ok_btn);
9241 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9242 G_CALLBACK(attach_property_ok),
9244 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9245 G_CALLBACK(attach_property_cancel),
9248 gtk_widget_show_all(vbox);
9250 attach_prop.window = window;
9251 attach_prop.mimetype_entry = mimetype_entry;
9252 attach_prop.encoding_optmenu = optmenu;
9253 attach_prop.path_entry = path_entry;
9254 attach_prop.filename_entry = filename_entry;
9255 attach_prop.ok_btn = ok_btn;
9256 attach_prop.cancel_btn = cancel_btn;
9259 #undef SET_LABEL_AND_ENTRY
9261 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9267 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9273 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9274 gboolean *cancelled)
9282 static gboolean attach_property_key_pressed(GtkWidget *widget,
9284 gboolean *cancelled)
9286 if (event && event->keyval == GDK_KEY_Escape) {
9290 if (event && event->keyval == GDK_KEY_Return) {
9298 static void compose_exec_ext_editor(Compose *compose)
9303 GdkNativeWindow socket_wid = 0;
9307 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9308 G_DIR_SEPARATOR, compose);
9310 if (compose_get_ext_editor_uses_socket()) {
9311 /* Only allow one socket */
9312 if (compose->exteditor_socket != NULL) {
9313 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9314 /* Move the focus off of the socket */
9315 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9320 /* Create the receiving GtkSocket */
9321 socket = gtk_socket_new ();
9322 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9323 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9325 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9326 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9327 /* Realize the socket so that we can use its ID */
9328 gtk_widget_realize(socket);
9329 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9330 compose->exteditor_socket = socket;
9333 if (pipe(pipe_fds) < 0) {
9339 if ((pid = fork()) < 0) {
9346 /* close the write side of the pipe */
9349 compose->exteditor_file = g_strdup(tmp);
9350 compose->exteditor_pid = pid;
9352 compose_set_ext_editor_sensitive(compose, FALSE);
9355 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9357 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9359 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9363 } else { /* process-monitoring process */
9369 /* close the read side of the pipe */
9372 if (compose_write_body_to_file(compose, tmp) < 0) {
9373 fd_write_all(pipe_fds[1], "2\n", 2);
9377 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9379 fd_write_all(pipe_fds[1], "1\n", 2);
9383 /* wait until editor is terminated */
9384 waitpid(pid_ed, NULL, 0);
9386 fd_write_all(pipe_fds[1], "0\n", 2);
9393 #endif /* G_OS_UNIX */
9396 static gboolean compose_can_autosave(Compose *compose)
9398 if (compose->privacy_system && compose->use_encryption)
9399 return prefs_common.autosave && prefs_common.autosave_encrypted;
9401 return prefs_common.autosave;
9405 static gboolean compose_get_ext_editor_cmd_valid()
9407 gboolean has_s = FALSE;
9408 gboolean has_w = FALSE;
9409 const gchar *p = prefs_common_get_ext_editor_cmd();
9412 while ((p = strchr(p, '%'))) {
9418 } else if (*p == 'w') {
9429 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9436 cm_return_val_if_fail(file != NULL, -1);
9438 if ((pid = fork()) < 0) {
9443 if (pid != 0) return pid;
9445 /* grandchild process */
9447 if (setpgid(0, getppid()))
9450 if (compose_get_ext_editor_cmd_valid()) {
9451 if (compose_get_ext_editor_uses_socket()) {
9452 p = g_strdup(prefs_common_get_ext_editor_cmd());
9453 s = strstr(p, "%w");
9455 if (strstr(p, "%s") < s)
9456 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9458 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9461 g_snprintf(buf, sizeof(buf),
9462 prefs_common_get_ext_editor_cmd(), file);
9465 if (prefs_common_get_ext_editor_cmd())
9466 g_warning("External editor command-line is invalid: '%s'",
9467 prefs_common_get_ext_editor_cmd());
9468 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9471 cmdline = strsplit_with_quote(buf, " ", 1024);
9472 execvp(cmdline[0], cmdline);
9475 g_strfreev(cmdline);
9480 static gboolean compose_ext_editor_kill(Compose *compose)
9482 pid_t pgid = compose->exteditor_pid * -1;
9485 ret = kill(pgid, 0);
9487 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9491 msg = g_strdup_printf
9492 (_("The external editor is still working.\n"
9493 "Force terminating the process?\n"
9494 "process group id: %d"), -pgid);
9495 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9496 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9500 if (val == G_ALERTALTERNATE) {
9501 g_source_remove(compose->exteditor_tag);
9502 g_io_channel_shutdown(compose->exteditor_ch,
9504 g_io_channel_unref(compose->exteditor_ch);
9506 if (kill(pgid, SIGTERM) < 0) perror("kill");
9507 waitpid(compose->exteditor_pid, NULL, 0);
9509 g_warning("Terminated process group id: %d. "
9510 "Temporary file: %s", -pgid, compose->exteditor_file);
9512 compose_set_ext_editor_sensitive(compose, TRUE);
9514 g_free(compose->exteditor_file);
9515 compose->exteditor_file = NULL;
9516 compose->exteditor_pid = -1;
9517 compose->exteditor_ch = NULL;
9518 compose->exteditor_tag = -1;
9526 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9530 Compose *compose = (Compose *)data;
9533 debug_print("Compose: input from monitoring process\n");
9535 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9540 g_io_channel_shutdown(source, FALSE, NULL);
9541 g_io_channel_unref(source);
9543 waitpid(compose->exteditor_pid, NULL, 0);
9545 if (buf[0] == '0') { /* success */
9546 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9547 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9548 GtkTextIter start, end;
9551 gtk_text_buffer_set_text(buffer, "", -1);
9552 compose_insert_file(compose, compose->exteditor_file);
9553 compose_changed_cb(NULL, compose);
9555 /* Check if we should save the draft or not */
9556 if (compose_can_autosave(compose))
9557 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9559 if (claws_unlink(compose->exteditor_file) < 0)
9560 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9562 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9563 gtk_text_buffer_get_start_iter(buffer, &start);
9564 gtk_text_buffer_get_end_iter(buffer, &end);
9565 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9566 if (chars && strlen(chars) > 0)
9567 compose->modified = TRUE;
9569 } else if (buf[0] == '1') { /* failed */
9570 g_warning("Couldn't exec external editor");
9571 if (claws_unlink(compose->exteditor_file) < 0)
9572 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9573 } else if (buf[0] == '2') {
9574 g_warning("Couldn't write to file");
9575 } else if (buf[0] == '3') {
9576 g_warning("Pipe read failed");
9579 compose_set_ext_editor_sensitive(compose, TRUE);
9581 g_free(compose->exteditor_file);
9582 compose->exteditor_file = NULL;
9583 compose->exteditor_pid = -1;
9584 compose->exteditor_ch = NULL;
9585 compose->exteditor_tag = -1;
9586 if (compose->exteditor_socket) {
9587 gtk_widget_destroy(compose->exteditor_socket);
9588 compose->exteditor_socket = NULL;
9595 static char *ext_editor_menu_entries[] = {
9596 "Menu/Message/Send",
9597 "Menu/Message/SendLater",
9598 "Menu/Message/InsertFile",
9599 "Menu/Message/InsertSig",
9600 "Menu/Message/ReplaceSig",
9601 "Menu/Message/Save",
9602 "Menu/Message/Print",
9607 "Menu/Tools/ShowRuler",
9608 "Menu/Tools/Actions",
9613 static void compose_set_ext_editor_sensitive(Compose *compose,
9618 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9619 cm_menu_set_sensitive_full(compose->ui_manager,
9620 ext_editor_menu_entries[i], sensitive);
9623 if (compose_get_ext_editor_uses_socket()) {
9625 if (compose->exteditor_socket)
9626 gtk_widget_hide(compose->exteditor_socket);
9627 gtk_widget_show(compose->scrolledwin);
9628 if (prefs_common.show_ruler)
9629 gtk_widget_show(compose->ruler_hbox);
9630 /* Fix the focus, as it doesn't go anywhere when the
9631 * socket is hidden or destroyed */
9632 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9634 g_assert (compose->exteditor_socket != NULL);
9635 /* Fix the focus, as it doesn't go anywhere when the
9636 * edit box is hidden */
9637 if (gtk_widget_is_focus(compose->text))
9638 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9639 gtk_widget_hide(compose->scrolledwin);
9640 gtk_widget_hide(compose->ruler_hbox);
9641 gtk_widget_show(compose->exteditor_socket);
9644 gtk_widget_set_sensitive(compose->text, sensitive);
9646 if (compose->toolbar->send_btn)
9647 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9648 if (compose->toolbar->sendl_btn)
9649 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9650 if (compose->toolbar->draft_btn)
9651 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9652 if (compose->toolbar->insert_btn)
9653 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9654 if (compose->toolbar->sig_btn)
9655 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9656 if (compose->toolbar->exteditor_btn)
9657 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9658 if (compose->toolbar->linewrap_current_btn)
9659 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9660 if (compose->toolbar->linewrap_all_btn)
9661 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9664 static gboolean compose_get_ext_editor_uses_socket()
9666 return (prefs_common_get_ext_editor_cmd() &&
9667 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9670 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9672 compose->exteditor_socket = NULL;
9673 /* returning FALSE allows destruction of the socket */
9676 #endif /* G_OS_UNIX */
9679 * compose_undo_state_changed:
9681 * Change the sensivity of the menuentries undo and redo
9683 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9684 gint redo_state, gpointer data)
9686 Compose *compose = (Compose *)data;
9688 switch (undo_state) {
9689 case UNDO_STATE_TRUE:
9690 if (!undostruct->undo_state) {
9691 undostruct->undo_state = TRUE;
9692 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9695 case UNDO_STATE_FALSE:
9696 if (undostruct->undo_state) {
9697 undostruct->undo_state = FALSE;
9698 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9701 case UNDO_STATE_UNCHANGED:
9703 case UNDO_STATE_REFRESH:
9704 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9707 g_warning("Undo state not recognized");
9711 switch (redo_state) {
9712 case UNDO_STATE_TRUE:
9713 if (!undostruct->redo_state) {
9714 undostruct->redo_state = TRUE;
9715 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9718 case UNDO_STATE_FALSE:
9719 if (undostruct->redo_state) {
9720 undostruct->redo_state = FALSE;
9721 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9724 case UNDO_STATE_UNCHANGED:
9726 case UNDO_STATE_REFRESH:
9727 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9730 g_warning("Redo state not recognized");
9735 /* callback functions */
9737 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9738 GtkAllocation *allocation,
9741 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9744 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9745 * includes "non-client" (windows-izm) in calculation, so this calculation
9746 * may not be accurate.
9748 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9749 GtkAllocation *allocation,
9750 GtkSHRuler *shruler)
9752 if (prefs_common.show_ruler) {
9753 gint char_width = 0, char_height = 0;
9754 gint line_width_in_chars;
9756 gtkut_get_font_size(GTK_WIDGET(widget),
9757 &char_width, &char_height);
9758 line_width_in_chars =
9759 (allocation->width - allocation->x) / char_width;
9761 /* got the maximum */
9762 gtk_shruler_set_range(GTK_SHRULER(shruler),
9763 0.0, line_width_in_chars, 0);
9772 ComposePrefType type;
9773 gboolean entry_marked;
9776 static void account_activated(GtkComboBox *optmenu, gpointer data)
9778 Compose *compose = (Compose *)data;
9781 gchar *folderidentifier;
9782 gint account_id = 0;
9785 GSList *list, *saved_list = NULL;
9786 HeaderEntryState *state;
9788 /* Get ID of active account in the combo box */
9789 menu = gtk_combo_box_get_model(optmenu);
9790 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9791 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9793 ac = account_find_from_id(account_id);
9794 cm_return_if_fail(ac != NULL);
9796 if (ac != compose->account) {
9797 compose_select_account(compose, ac, FALSE);
9799 for (list = compose->header_list; list; list = list->next) {
9800 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9802 if (hentry->type == PREF_ACCOUNT || !list->next) {
9803 compose_destroy_headerentry(compose, hentry);
9806 state = g_malloc0(sizeof(HeaderEntryState));
9807 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9808 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9809 state->entry = gtk_editable_get_chars(
9810 GTK_EDITABLE(hentry->entry), 0, -1);
9811 state->type = hentry->type;
9813 saved_list = g_slist_append(saved_list, state);
9814 compose_destroy_headerentry(compose, hentry);
9817 compose->header_last = NULL;
9818 g_slist_free(compose->header_list);
9819 compose->header_list = NULL;
9820 compose->header_nextrow = 1;
9821 compose_create_header_entry(compose);
9823 if (ac->set_autocc && ac->auto_cc)
9824 compose_entry_append(compose, ac->auto_cc,
9825 COMPOSE_CC, PREF_ACCOUNT);
9826 if (ac->set_autobcc && ac->auto_bcc)
9827 compose_entry_append(compose, ac->auto_bcc,
9828 COMPOSE_BCC, PREF_ACCOUNT);
9829 if (ac->set_autoreplyto && ac->auto_replyto)
9830 compose_entry_append(compose, ac->auto_replyto,
9831 COMPOSE_REPLYTO, PREF_ACCOUNT);
9833 for (list = saved_list; list; list = list->next) {
9834 state = (HeaderEntryState *) list->data;
9836 compose_add_header_entry(compose, state->header,
9837 state->entry, state->type);
9839 g_free(state->header);
9840 g_free(state->entry);
9843 g_slist_free(saved_list);
9845 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9846 (ac->protocol == A_NNTP) ?
9847 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9850 /* Set message save folder */
9851 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9852 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9854 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9855 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9857 compose_set_save_to(compose, NULL);
9858 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9859 folderidentifier = folder_item_get_identifier(account_get_special_folder
9860 (compose->account, F_OUTBOX));
9861 compose_set_save_to(compose, folderidentifier);
9862 g_free(folderidentifier);
9866 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9867 GtkTreeViewColumn *column, Compose *compose)
9869 compose_attach_property(NULL, compose);
9872 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9875 Compose *compose = (Compose *)data;
9876 GtkTreeSelection *attach_selection;
9877 gint attach_nr_selected;
9880 if (!event) return FALSE;
9882 if (event->button == 3) {
9883 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9884 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9886 /* If no rows, or just one row is selected, right-click should
9887 * open menu relevant to the row being right-clicked on. We
9888 * achieve that by selecting the clicked row first. If more
9889 * than one row is selected, we shouldn't modify the selection,
9890 * as user may want to remove selected rows (attachments). */
9891 if (attach_nr_selected < 2) {
9892 gtk_tree_selection_unselect_all(attach_selection);
9893 attach_nr_selected = 0;
9894 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9895 event->x, event->y, &path, NULL, NULL, NULL);
9897 gtk_tree_selection_select_path(attach_selection, path);
9898 gtk_tree_path_free(path);
9899 attach_nr_selected++;
9903 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9904 /* Properties menu item makes no sense with more than one row
9905 * selected, the properties dialog can only edit one attachment. */
9906 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9908 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9909 NULL, NULL, event->button, event->time);
9916 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9919 Compose *compose = (Compose *)data;
9921 if (!event) return FALSE;
9923 switch (event->keyval) {
9924 case GDK_KEY_Delete:
9925 compose_attach_remove_selected(NULL, compose);
9931 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9933 toolbar_comp_set_sensitive(compose, allow);
9934 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9935 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9939 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9940 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9941 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9943 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9947 static void compose_send_cb(GtkAction *action, gpointer data)
9949 Compose *compose = (Compose *)data;
9952 if (compose->exteditor_tag != -1) {
9953 debug_print("ignoring send: external editor still open\n");
9957 if (prefs_common.work_offline &&
9958 !inc_offline_should_override(TRUE,
9959 _("Claws Mail needs network access in order "
9960 "to send this email.")))
9963 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9964 g_source_remove(compose->draft_timeout_tag);
9965 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9968 compose_send(compose);
9971 static void compose_send_later_cb(GtkAction *action, gpointer data)
9973 Compose *compose = (Compose *)data;
9977 compose_allow_user_actions(compose, FALSE);
9978 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9979 compose_allow_user_actions(compose, TRUE);
9983 compose_close(compose);
9984 } else if (val == -1) {
9985 alertpanel_error(_("Could not queue message."));
9986 } else if (val == -2) {
9987 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9988 } else if (val == -3) {
9989 if (privacy_peek_error())
9990 alertpanel_error(_("Could not queue message for sending:\n\n"
9991 "Signature failed: %s"), privacy_get_error());
9992 } else if (val == -4) {
9993 alertpanel_error(_("Could not queue message for sending:\n\n"
9994 "Charset conversion failed."));
9995 } else if (val == -5) {
9996 alertpanel_error(_("Could not queue message for sending:\n\n"
9997 "Couldn't get recipient encryption key."));
9998 } else if (val == -6) {
10001 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10004 #define DRAFTED_AT_EXIT "drafted_at_exit"
10005 static void compose_register_draft(MsgInfo *info)
10007 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10008 DRAFTED_AT_EXIT, NULL);
10009 FILE *fp = g_fopen(filepath, "ab");
10012 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
10020 gboolean compose_draft (gpointer data, guint action)
10022 Compose *compose = (Compose *)data;
10027 MsgFlags flag = {0, 0};
10028 static gboolean lock = FALSE;
10029 MsgInfo *newmsginfo;
10031 gboolean target_locked = FALSE;
10032 gboolean err = FALSE;
10034 if (lock) return FALSE;
10036 if (compose->sending)
10039 draft = account_get_special_folder(compose->account, F_DRAFT);
10040 cm_return_val_if_fail(draft != NULL, FALSE);
10042 if (!g_mutex_trylock(compose->mutex)) {
10043 /* we don't want to lock the mutex once it's available,
10044 * because as the only other part of compose.c locking
10045 * it is compose_close - which means once unlocked,
10046 * the compose struct will be freed */
10047 debug_print("couldn't lock mutex, probably sending\n");
10053 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10054 G_DIR_SEPARATOR, compose);
10055 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10056 FILE_OP_ERROR(tmp, "fopen");
10060 /* chmod for security */
10061 if (change_file_mode_rw(fp, tmp) < 0) {
10062 FILE_OP_ERROR(tmp, "chmod");
10063 g_warning("can't change file mode");
10066 /* Save draft infos */
10067 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10068 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10070 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10071 gchar *savefolderid;
10073 savefolderid = compose_get_save_to(compose);
10074 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10075 g_free(savefolderid);
10077 if (compose->return_receipt) {
10078 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10080 if (compose->privacy_system) {
10081 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10082 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10083 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10086 /* Message-ID of message replying to */
10087 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10088 gchar *folderid = NULL;
10090 if (compose->replyinfo->folder)
10091 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10092 if (folderid == NULL)
10093 folderid = g_strdup("NULL");
10095 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10098 /* Message-ID of message forwarding to */
10099 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10100 gchar *folderid = NULL;
10102 if (compose->fwdinfo->folder)
10103 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10104 if (folderid == NULL)
10105 folderid = g_strdup("NULL");
10107 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10111 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10112 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10114 sheaders = compose_get_manual_headers_info(compose);
10115 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10118 /* end of headers */
10119 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10126 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10130 if (fclose(fp) == EOF) {
10134 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10135 if (compose->targetinfo) {
10136 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10138 flag.perm_flags |= MSG_LOCKED;
10140 flag.tmp_flags = MSG_DRAFT;
10142 folder_item_scan(draft);
10143 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10144 MsgInfo *tmpinfo = NULL;
10145 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10146 if (compose->msgid) {
10147 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10150 msgnum = tmpinfo->msgnum;
10151 procmsg_msginfo_free(&tmpinfo);
10152 debug_print("got draft msgnum %d from scanning\n", msgnum);
10154 debug_print("didn't get draft msgnum after scanning\n");
10157 debug_print("got draft msgnum %d from adding\n", msgnum);
10163 if (action != COMPOSE_AUTO_SAVE) {
10164 if (action != COMPOSE_DRAFT_FOR_EXIT)
10165 alertpanel_error(_("Could not save draft."));
10168 gtkut_window_popup(compose->window);
10169 val = alertpanel_full(_("Could not save draft"),
10170 _("Could not save draft.\n"
10171 "Do you want to cancel exit or discard this email?"),
10172 _("_Cancel exit"), _("_Discard email"), NULL,
10173 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10174 if (val == G_ALERTALTERNATE) {
10176 g_mutex_unlock(compose->mutex); /* must be done before closing */
10177 compose_close(compose);
10181 g_mutex_unlock(compose->mutex); /* must be done before closing */
10190 if (compose->mode == COMPOSE_REEDIT) {
10191 compose_remove_reedit_target(compose, TRUE);
10194 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10197 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10199 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10201 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10202 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10203 procmsg_msginfo_set_flags(newmsginfo, 0,
10204 MSG_HAS_ATTACHMENT);
10206 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10207 compose_register_draft(newmsginfo);
10209 procmsg_msginfo_free(&newmsginfo);
10212 folder_item_scan(draft);
10214 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10216 g_mutex_unlock(compose->mutex); /* must be done before closing */
10217 compose_close(compose);
10223 path = folder_item_fetch_msg(draft, msgnum);
10224 if (path == NULL) {
10225 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10228 if (g_stat(path, &s) < 0) {
10229 FILE_OP_ERROR(path, "stat");
10235 procmsg_msginfo_free(&(compose->targetinfo));
10236 compose->targetinfo = procmsg_msginfo_new();
10237 compose->targetinfo->msgnum = msgnum;
10238 compose->targetinfo->size = (goffset)s.st_size;
10239 compose->targetinfo->mtime = s.st_mtime;
10240 compose->targetinfo->folder = draft;
10242 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10243 compose->mode = COMPOSE_REEDIT;
10245 if (action == COMPOSE_AUTO_SAVE) {
10246 compose->autosaved_draft = compose->targetinfo;
10248 compose->modified = FALSE;
10249 compose_set_title(compose);
10253 g_mutex_unlock(compose->mutex);
10257 void compose_clear_exit_drafts(void)
10259 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10260 DRAFTED_AT_EXIT, NULL);
10261 if (is_file_exist(filepath))
10262 claws_unlink(filepath);
10267 void compose_reopen_exit_drafts(void)
10269 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10270 DRAFTED_AT_EXIT, NULL);
10271 FILE *fp = g_fopen(filepath, "rb");
10275 while (fgets(buf, sizeof(buf), fp)) {
10276 gchar **parts = g_strsplit(buf, "\t", 2);
10277 const gchar *folder = parts[0];
10278 int msgnum = parts[1] ? atoi(parts[1]):-1;
10280 if (folder && *folder && msgnum > -1) {
10281 FolderItem *item = folder_find_item_from_identifier(folder);
10282 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10284 compose_reedit(info, FALSE);
10291 compose_clear_exit_drafts();
10294 static void compose_save_cb(GtkAction *action, gpointer data)
10296 Compose *compose = (Compose *)data;
10297 compose_draft(compose, COMPOSE_KEEP_EDITING);
10298 compose->rmode = COMPOSE_REEDIT;
10301 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10303 if (compose && file_list) {
10306 for ( tmp = file_list; tmp; tmp = tmp->next) {
10307 gchar *file = (gchar *) tmp->data;
10308 gchar *utf8_filename = conv_filename_to_utf8(file);
10309 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10310 compose_changed_cb(NULL, compose);
10315 g_free(utf8_filename);
10320 static void compose_attach_cb(GtkAction *action, gpointer data)
10322 Compose *compose = (Compose *)data;
10325 if (compose->redirect_filename != NULL)
10328 /* Set focus_window properly, in case we were called via popup menu,
10329 * which unsets it (via focus_out_event callback on compose window). */
10330 manage_window_focus_in(compose->window, NULL, NULL);
10332 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10335 compose_attach_from_list(compose, file_list, TRUE);
10336 g_list_free(file_list);
10340 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10342 Compose *compose = (Compose *)data;
10344 gint files_inserted = 0;
10346 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10351 for ( tmp = file_list; tmp; tmp = tmp->next) {
10352 gchar *file = (gchar *) tmp->data;
10353 gchar *filedup = g_strdup(file);
10354 gchar *shortfile = g_path_get_basename(filedup);
10355 ComposeInsertResult res;
10356 /* insert the file if the file is short or if the user confirmed that
10357 he/she wants to insert the large file */
10358 res = compose_insert_file(compose, file);
10359 if (res == COMPOSE_INSERT_READ_ERROR) {
10360 alertpanel_error(_("File '%s' could not be read."), shortfile);
10361 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10362 alertpanel_error(_("File '%s' contained invalid characters\n"
10363 "for the current encoding, insertion may be incorrect."),
10365 } else if (res == COMPOSE_INSERT_SUCCESS)
10372 g_list_free(file_list);
10376 if (files_inserted > 0 && compose->gtkaspell &&
10377 compose->gtkaspell->check_while_typing)
10378 gtkaspell_highlight_all(compose->gtkaspell);
10382 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10384 Compose *compose = (Compose *)data;
10386 compose_insert_sig(compose, FALSE);
10389 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10391 Compose *compose = (Compose *)data;
10393 compose_insert_sig(compose, TRUE);
10396 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10400 Compose *compose = (Compose *)data;
10402 gtkut_widget_get_uposition(widget, &x, &y);
10403 if (!compose->batch) {
10404 prefs_common.compose_x = x;
10405 prefs_common.compose_y = y;
10407 if (compose->sending || compose->updating)
10409 compose_close_cb(NULL, compose);
10413 void compose_close_toolbar(Compose *compose)
10415 compose_close_cb(NULL, compose);
10418 static void compose_close_cb(GtkAction *action, gpointer data)
10420 Compose *compose = (Compose *)data;
10424 if (compose->exteditor_tag != -1) {
10425 if (!compose_ext_editor_kill(compose))
10430 if (compose->modified) {
10431 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10432 if (!g_mutex_trylock(compose->mutex)) {
10433 /* we don't want to lock the mutex once it's available,
10434 * because as the only other part of compose.c locking
10435 * it is compose_close - which means once unlocked,
10436 * the compose struct will be freed */
10437 debug_print("couldn't lock mutex, probably sending\n");
10441 val = alertpanel(_("Discard message"),
10442 _("This message has been modified. Discard it?"),
10443 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10445 val = alertpanel(_("Save changes"),
10446 _("This message has been modified. Save the latest changes?"),
10447 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10450 g_mutex_unlock(compose->mutex);
10452 case G_ALERTDEFAULT:
10453 if (compose_can_autosave(compose) && !reedit)
10454 compose_remove_draft(compose);
10456 case G_ALERTALTERNATE:
10457 compose_draft(data, COMPOSE_QUIT_EDITING);
10464 compose_close(compose);
10467 static void compose_print_cb(GtkAction *action, gpointer data)
10469 Compose *compose = (Compose *) data;
10471 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10472 if (compose->targetinfo)
10473 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10476 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10478 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10479 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10480 Compose *compose = (Compose *) data;
10483 compose->out_encoding = (CharSet)value;
10486 static void compose_address_cb(GtkAction *action, gpointer data)
10488 Compose *compose = (Compose *)data;
10490 #ifndef USE_ALT_ADDRBOOK
10491 addressbook_open(compose);
10493 GError* error = NULL;
10494 addressbook_connect_signals(compose);
10495 addressbook_dbus_open(TRUE, &error);
10497 g_warning("%s", error->message);
10498 g_error_free(error);
10503 static void about_show_cb(GtkAction *action, gpointer data)
10508 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10510 Compose *compose = (Compose *)data;
10515 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10516 cm_return_if_fail(tmpl != NULL);
10518 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10520 val = alertpanel(_("Apply template"), msg,
10521 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10524 if (val == G_ALERTDEFAULT)
10525 compose_template_apply(compose, tmpl, TRUE);
10526 else if (val == G_ALERTALTERNATE)
10527 compose_template_apply(compose, tmpl, FALSE);
10530 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10532 Compose *compose = (Compose *)data;
10535 if (compose->exteditor_tag != -1) {
10536 debug_print("ignoring open external editor: external editor still open\n");
10540 compose_exec_ext_editor(compose);
10543 static void compose_undo_cb(GtkAction *action, gpointer data)
10545 Compose *compose = (Compose *)data;
10546 gboolean prev_autowrap = compose->autowrap;
10548 compose->autowrap = FALSE;
10549 undo_undo(compose->undostruct);
10550 compose->autowrap = prev_autowrap;
10553 static void compose_redo_cb(GtkAction *action, gpointer data)
10555 Compose *compose = (Compose *)data;
10556 gboolean prev_autowrap = compose->autowrap;
10558 compose->autowrap = FALSE;
10559 undo_redo(compose->undostruct);
10560 compose->autowrap = prev_autowrap;
10563 static void entry_cut_clipboard(GtkWidget *entry)
10565 if (GTK_IS_EDITABLE(entry))
10566 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10567 else if (GTK_IS_TEXT_VIEW(entry))
10568 gtk_text_buffer_cut_clipboard(
10569 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10570 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10574 static void entry_copy_clipboard(GtkWidget *entry)
10576 if (GTK_IS_EDITABLE(entry))
10577 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10578 else if (GTK_IS_TEXT_VIEW(entry))
10579 gtk_text_buffer_copy_clipboard(
10580 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10581 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10584 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10585 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10587 if (GTK_IS_TEXT_VIEW(entry)) {
10588 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10589 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10590 GtkTextIter start_iter, end_iter;
10592 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10594 if (contents == NULL)
10597 /* we shouldn't delete the selection when middle-click-pasting, or we
10598 * can't mid-click-paste our own selection */
10599 if (clip != GDK_SELECTION_PRIMARY) {
10600 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10601 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10604 if (insert_place == NULL) {
10605 /* if insert_place isn't specified, insert at the cursor.
10606 * used for Ctrl-V pasting */
10607 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10608 start = gtk_text_iter_get_offset(&start_iter);
10609 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10611 /* if insert_place is specified, paste here.
10612 * used for mid-click-pasting */
10613 start = gtk_text_iter_get_offset(insert_place);
10614 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10615 if (prefs_common.primary_paste_unselects)
10616 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10620 /* paste unwrapped: mark the paste so it's not wrapped later */
10621 end = start + strlen(contents);
10622 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10623 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10624 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10625 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10626 /* rewrap paragraph now (after a mid-click-paste) */
10627 mark_start = gtk_text_buffer_get_insert(buffer);
10628 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10629 gtk_text_iter_backward_char(&start_iter);
10630 compose_beautify_paragraph(compose, &start_iter, TRUE);
10632 } else if (GTK_IS_EDITABLE(entry))
10633 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10635 compose->modified = TRUE;
10638 static void entry_allsel(GtkWidget *entry)
10640 if (GTK_IS_EDITABLE(entry))
10641 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10642 else if (GTK_IS_TEXT_VIEW(entry)) {
10643 GtkTextIter startiter, enditer;
10644 GtkTextBuffer *textbuf;
10646 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10647 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10648 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10650 gtk_text_buffer_move_mark_by_name(textbuf,
10651 "selection_bound", &startiter);
10652 gtk_text_buffer_move_mark_by_name(textbuf,
10653 "insert", &enditer);
10657 static void compose_cut_cb(GtkAction *action, gpointer data)
10659 Compose *compose = (Compose *)data;
10660 if (compose->focused_editable
10661 #ifndef GENERIC_UMPC
10662 && gtk_widget_has_focus(compose->focused_editable)
10665 entry_cut_clipboard(compose->focused_editable);
10668 static void compose_copy_cb(GtkAction *action, gpointer data)
10670 Compose *compose = (Compose *)data;
10671 if (compose->focused_editable
10672 #ifndef GENERIC_UMPC
10673 && gtk_widget_has_focus(compose->focused_editable)
10676 entry_copy_clipboard(compose->focused_editable);
10679 static void compose_paste_cb(GtkAction *action, gpointer data)
10681 Compose *compose = (Compose *)data;
10682 gint prev_autowrap;
10683 GtkTextBuffer *buffer;
10685 if (compose->focused_editable &&
10686 #ifndef GENERIC_UMPC
10687 gtk_widget_has_focus(compose->focused_editable)
10690 entry_paste_clipboard(compose, compose->focused_editable,
10691 prefs_common.linewrap_pastes,
10692 GDK_SELECTION_CLIPBOARD, NULL);
10697 #ifndef GENERIC_UMPC
10698 gtk_widget_has_focus(compose->text) &&
10700 compose->gtkaspell &&
10701 compose->gtkaspell->check_while_typing)
10702 gtkaspell_highlight_all(compose->gtkaspell);
10706 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10708 Compose *compose = (Compose *)data;
10709 gint wrap_quote = prefs_common.linewrap_quote;
10710 if (compose->focused_editable
10711 #ifndef GENERIC_UMPC
10712 && gtk_widget_has_focus(compose->focused_editable)
10715 /* let text_insert() (called directly or at a later time
10716 * after the gtk_editable_paste_clipboard) know that
10717 * text is to be inserted as a quotation. implemented
10718 * by using a simple refcount... */
10719 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10720 G_OBJECT(compose->focused_editable),
10721 "paste_as_quotation"));
10722 g_object_set_data(G_OBJECT(compose->focused_editable),
10723 "paste_as_quotation",
10724 GINT_TO_POINTER(paste_as_quotation + 1));
10725 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10726 entry_paste_clipboard(compose, compose->focused_editable,
10727 prefs_common.linewrap_pastes,
10728 GDK_SELECTION_CLIPBOARD, NULL);
10729 prefs_common.linewrap_quote = wrap_quote;
10733 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10735 Compose *compose = (Compose *)data;
10736 gint prev_autowrap;
10737 GtkTextBuffer *buffer;
10739 if (compose->focused_editable
10740 #ifndef GENERIC_UMPC
10741 && gtk_widget_has_focus(compose->focused_editable)
10744 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10745 GDK_SELECTION_CLIPBOARD, NULL);
10750 #ifndef GENERIC_UMPC
10751 gtk_widget_has_focus(compose->text) &&
10753 compose->gtkaspell &&
10754 compose->gtkaspell->check_while_typing)
10755 gtkaspell_highlight_all(compose->gtkaspell);
10759 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10761 Compose *compose = (Compose *)data;
10762 gint prev_autowrap;
10763 GtkTextBuffer *buffer;
10765 if (compose->focused_editable
10766 #ifndef GENERIC_UMPC
10767 && gtk_widget_has_focus(compose->focused_editable)
10770 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10771 GDK_SELECTION_CLIPBOARD, NULL);
10776 #ifndef GENERIC_UMPC
10777 gtk_widget_has_focus(compose->text) &&
10779 compose->gtkaspell &&
10780 compose->gtkaspell->check_while_typing)
10781 gtkaspell_highlight_all(compose->gtkaspell);
10785 static void compose_allsel_cb(GtkAction *action, gpointer data)
10787 Compose *compose = (Compose *)data;
10788 if (compose->focused_editable
10789 #ifndef GENERIC_UMPC
10790 && gtk_widget_has_focus(compose->focused_editable)
10793 entry_allsel(compose->focused_editable);
10796 static void textview_move_beginning_of_line (GtkTextView *text)
10798 GtkTextBuffer *buffer;
10802 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10804 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10805 mark = gtk_text_buffer_get_insert(buffer);
10806 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10807 gtk_text_iter_set_line_offset(&ins, 0);
10808 gtk_text_buffer_place_cursor(buffer, &ins);
10811 static void textview_move_forward_character (GtkTextView *text)
10813 GtkTextBuffer *buffer;
10817 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10819 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10820 mark = gtk_text_buffer_get_insert(buffer);
10821 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10822 if (gtk_text_iter_forward_cursor_position(&ins))
10823 gtk_text_buffer_place_cursor(buffer, &ins);
10826 static void textview_move_backward_character (GtkTextView *text)
10828 GtkTextBuffer *buffer;
10832 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10834 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10835 mark = gtk_text_buffer_get_insert(buffer);
10836 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10837 if (gtk_text_iter_backward_cursor_position(&ins))
10838 gtk_text_buffer_place_cursor(buffer, &ins);
10841 static void textview_move_forward_word (GtkTextView *text)
10843 GtkTextBuffer *buffer;
10848 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10850 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10851 mark = gtk_text_buffer_get_insert(buffer);
10852 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10853 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10854 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10855 gtk_text_iter_backward_word_start(&ins);
10856 gtk_text_buffer_place_cursor(buffer, &ins);
10860 static void textview_move_backward_word (GtkTextView *text)
10862 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 if (gtk_text_iter_backward_word_starts(&ins, 1))
10872 gtk_text_buffer_place_cursor(buffer, &ins);
10875 static void textview_move_end_of_line (GtkTextView *text)
10877 GtkTextBuffer *buffer;
10881 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10883 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10884 mark = gtk_text_buffer_get_insert(buffer);
10885 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10886 if (gtk_text_iter_forward_to_line_end(&ins))
10887 gtk_text_buffer_place_cursor(buffer, &ins);
10890 static void textview_move_next_line (GtkTextView *text)
10892 GtkTextBuffer *buffer;
10897 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10899 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10900 mark = gtk_text_buffer_get_insert(buffer);
10901 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10902 offset = gtk_text_iter_get_line_offset(&ins);
10903 if (gtk_text_iter_forward_line(&ins)) {
10904 gtk_text_iter_set_line_offset(&ins, offset);
10905 gtk_text_buffer_place_cursor(buffer, &ins);
10909 static void textview_move_previous_line (GtkTextView *text)
10911 GtkTextBuffer *buffer;
10916 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10918 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10919 mark = gtk_text_buffer_get_insert(buffer);
10920 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10921 offset = gtk_text_iter_get_line_offset(&ins);
10922 if (gtk_text_iter_backward_line(&ins)) {
10923 gtk_text_iter_set_line_offset(&ins, offset);
10924 gtk_text_buffer_place_cursor(buffer, &ins);
10928 static void textview_delete_forward_character (GtkTextView *text)
10930 GtkTextBuffer *buffer;
10932 GtkTextIter ins, end_iter;
10934 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10936 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10937 mark = gtk_text_buffer_get_insert(buffer);
10938 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10940 if (gtk_text_iter_forward_char(&end_iter)) {
10941 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10945 static void textview_delete_backward_character (GtkTextView *text)
10947 GtkTextBuffer *buffer;
10949 GtkTextIter ins, end_iter;
10951 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10953 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10954 mark = gtk_text_buffer_get_insert(buffer);
10955 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10957 if (gtk_text_iter_backward_char(&end_iter)) {
10958 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10962 static void textview_delete_forward_word (GtkTextView *text)
10964 GtkTextBuffer *buffer;
10966 GtkTextIter ins, end_iter;
10968 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10970 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10971 mark = gtk_text_buffer_get_insert(buffer);
10972 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10974 if (gtk_text_iter_forward_word_end(&end_iter)) {
10975 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10979 static void textview_delete_backward_word (GtkTextView *text)
10981 GtkTextBuffer *buffer;
10983 GtkTextIter ins, end_iter;
10985 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10987 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10988 mark = gtk_text_buffer_get_insert(buffer);
10989 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10991 if (gtk_text_iter_backward_word_start(&end_iter)) {
10992 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10996 static void textview_delete_line (GtkTextView *text)
10998 GtkTextBuffer *buffer;
11000 GtkTextIter ins, start_iter, end_iter;
11002 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11004 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11005 mark = gtk_text_buffer_get_insert(buffer);
11006 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11009 gtk_text_iter_set_line_offset(&start_iter, 0);
11012 if (gtk_text_iter_ends_line(&end_iter)){
11013 if (!gtk_text_iter_forward_char(&end_iter))
11014 gtk_text_iter_backward_char(&start_iter);
11017 gtk_text_iter_forward_to_line_end(&end_iter);
11018 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11021 static void textview_delete_to_line_end (GtkTextView *text)
11023 GtkTextBuffer *buffer;
11025 GtkTextIter ins, end_iter;
11027 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11029 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11030 mark = gtk_text_buffer_get_insert(buffer);
11031 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11033 if (gtk_text_iter_ends_line(&end_iter))
11034 gtk_text_iter_forward_char(&end_iter);
11036 gtk_text_iter_forward_to_line_end(&end_iter);
11037 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11040 #define DO_ACTION(name, act) { \
11041 if(!strcmp(name, a_name)) { \
11045 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11047 const gchar *a_name = gtk_action_get_name(action);
11048 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11049 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11050 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11051 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11052 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11053 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11054 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11055 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11056 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11057 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11058 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11059 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11060 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11061 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11065 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11067 Compose *compose = (Compose *)data;
11068 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11069 ComposeCallAdvancedAction action = -1;
11071 action = compose_call_advanced_action_from_path(gaction);
11074 void (*do_action) (GtkTextView *text);
11075 } action_table[] = {
11076 {textview_move_beginning_of_line},
11077 {textview_move_forward_character},
11078 {textview_move_backward_character},
11079 {textview_move_forward_word},
11080 {textview_move_backward_word},
11081 {textview_move_end_of_line},
11082 {textview_move_next_line},
11083 {textview_move_previous_line},
11084 {textview_delete_forward_character},
11085 {textview_delete_backward_character},
11086 {textview_delete_forward_word},
11087 {textview_delete_backward_word},
11088 {textview_delete_line},
11089 {textview_delete_to_line_end}
11092 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11094 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11095 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11096 if (action_table[action].do_action)
11097 action_table[action].do_action(text);
11099 g_warning("Not implemented yet.");
11103 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11105 GtkAllocation allocation;
11109 if (GTK_IS_EDITABLE(widget)) {
11110 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11111 gtk_editable_set_position(GTK_EDITABLE(widget),
11114 if ((parent = gtk_widget_get_parent(widget))
11115 && (parent = gtk_widget_get_parent(parent))
11116 && (parent = gtk_widget_get_parent(parent))) {
11117 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11118 gtk_widget_get_allocation(widget, &allocation);
11119 gint y = allocation.y;
11120 gint height = allocation.height;
11121 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11122 (GTK_SCROLLED_WINDOW(parent));
11124 gfloat value = gtk_adjustment_get_value(shown);
11125 gfloat upper = gtk_adjustment_get_upper(shown);
11126 gfloat page_size = gtk_adjustment_get_page_size(shown);
11127 if (y < (int)value) {
11128 gtk_adjustment_set_value(shown, y - 1);
11130 if ((y + height) > ((int)value + (int)page_size)) {
11131 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11132 gtk_adjustment_set_value(shown,
11133 y + height - (int)page_size - 1);
11135 gtk_adjustment_set_value(shown,
11136 (int)upper - (int)page_size - 1);
11143 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11144 compose->focused_editable = widget;
11146 #ifdef GENERIC_UMPC
11147 if (GTK_IS_TEXT_VIEW(widget)
11148 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11149 g_object_ref(compose->notebook);
11150 g_object_ref(compose->edit_vbox);
11151 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11152 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11153 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11154 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11155 g_object_unref(compose->notebook);
11156 g_object_unref(compose->edit_vbox);
11157 g_signal_handlers_block_by_func(G_OBJECT(widget),
11158 G_CALLBACK(compose_grab_focus_cb),
11160 gtk_widget_grab_focus(widget);
11161 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11162 G_CALLBACK(compose_grab_focus_cb),
11164 } else if (!GTK_IS_TEXT_VIEW(widget)
11165 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11166 g_object_ref(compose->notebook);
11167 g_object_ref(compose->edit_vbox);
11168 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11169 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11170 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11171 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11172 g_object_unref(compose->notebook);
11173 g_object_unref(compose->edit_vbox);
11174 g_signal_handlers_block_by_func(G_OBJECT(widget),
11175 G_CALLBACK(compose_grab_focus_cb),
11177 gtk_widget_grab_focus(widget);
11178 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11179 G_CALLBACK(compose_grab_focus_cb),
11185 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11187 compose->modified = TRUE;
11188 // compose_beautify_paragraph(compose, NULL, TRUE);
11189 #ifndef GENERIC_UMPC
11190 compose_set_title(compose);
11194 static void compose_wrap_cb(GtkAction *action, gpointer data)
11196 Compose *compose = (Compose *)data;
11197 compose_beautify_paragraph(compose, NULL, TRUE);
11200 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11202 Compose *compose = (Compose *)data;
11203 compose_wrap_all_full(compose, TRUE);
11206 static void compose_find_cb(GtkAction *action, gpointer data)
11208 Compose *compose = (Compose *)data;
11210 message_search_compose(compose);
11213 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11216 Compose *compose = (Compose *)data;
11217 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11218 if (compose->autowrap)
11219 compose_wrap_all_full(compose, TRUE);
11220 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11223 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11226 Compose *compose = (Compose *)data;
11227 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11230 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11232 Compose *compose = (Compose *)data;
11234 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11237 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11239 Compose *compose = (Compose *)data;
11241 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11244 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11246 g_free(compose->privacy_system);
11247 g_free(compose->encdata);
11249 compose->privacy_system = g_strdup(account->default_privacy_system);
11250 compose_update_privacy_system_menu_item(compose, warn);
11253 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11255 Compose *compose = (Compose *)data;
11257 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11258 gtk_widget_show(compose->ruler_hbox);
11259 prefs_common.show_ruler = TRUE;
11261 gtk_widget_hide(compose->ruler_hbox);
11262 gtk_widget_queue_resize(compose->edit_vbox);
11263 prefs_common.show_ruler = FALSE;
11267 static void compose_attach_drag_received_cb (GtkWidget *widget,
11268 GdkDragContext *context,
11271 GtkSelectionData *data,
11274 gpointer user_data)
11276 Compose *compose = (Compose *)user_data;
11280 type = gtk_selection_data_get_data_type(data);
11281 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11282 && gtk_drag_get_source_widget(context) !=
11283 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11284 list = uri_list_extract_filenames(
11285 (const gchar *)gtk_selection_data_get_data(data));
11286 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11287 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11288 compose_attach_append
11289 (compose, (const gchar *)tmp->data,
11290 utf8_filename, NULL, NULL);
11291 g_free(utf8_filename);
11293 if (list) compose_changed_cb(NULL, compose);
11294 list_free_strings(list);
11296 } else if (gtk_drag_get_source_widget(context)
11297 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11298 /* comes from our summaryview */
11299 SummaryView * summaryview = NULL;
11300 GSList * list = NULL, *cur = NULL;
11302 if (mainwindow_get_mainwindow())
11303 summaryview = mainwindow_get_mainwindow()->summaryview;
11306 list = summary_get_selected_msg_list(summaryview);
11308 for (cur = list; cur; cur = cur->next) {
11309 MsgInfo *msginfo = (MsgInfo *)cur->data;
11310 gchar *file = NULL;
11312 file = procmsg_get_message_file_full(msginfo,
11315 compose_attach_append(compose, (const gchar *)file,
11316 (const gchar *)file, "message/rfc822", NULL);
11320 g_slist_free(list);
11324 static gboolean compose_drag_drop(GtkWidget *widget,
11325 GdkDragContext *drag_context,
11327 guint time, gpointer user_data)
11329 /* not handling this signal makes compose_insert_drag_received_cb
11334 static gboolean completion_set_focus_to_subject
11335 (GtkWidget *widget,
11336 GdkEventKey *event,
11339 cm_return_val_if_fail(compose != NULL, FALSE);
11341 /* make backtab move to subject field */
11342 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11343 gtk_widget_grab_focus(compose->subject_entry);
11349 static void compose_insert_drag_received_cb (GtkWidget *widget,
11350 GdkDragContext *drag_context,
11353 GtkSelectionData *data,
11356 gpointer user_data)
11358 Compose *compose = (Compose *)user_data;
11364 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11366 type = gtk_selection_data_get_data_type(data);
11367 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11368 AlertValue val = G_ALERTDEFAULT;
11369 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11371 list = uri_list_extract_filenames(ddata);
11372 num_files = g_list_length(list);
11373 if (list == NULL && strstr(ddata, "://")) {
11374 /* Assume a list of no files, and data has ://, is a remote link */
11375 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11376 gchar *tmpfile = get_tmp_file();
11377 str_write_to_file(tmpdata, tmpfile);
11379 compose_insert_file(compose, tmpfile);
11380 claws_unlink(tmpfile);
11382 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11383 compose_beautify_paragraph(compose, NULL, TRUE);
11386 switch (prefs_common.compose_dnd_mode) {
11387 case COMPOSE_DND_ASK:
11388 msg = g_strdup_printf(
11390 "Do you want to insert the contents of the file "
11391 "into the message body, or attach it to the email?",
11392 "Do you want to insert the contents of the %d files "
11393 "into the message body, or attach them to the email?",
11396 val = alertpanel_full(_("Insert or attach?"), msg,
11397 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11398 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11401 case COMPOSE_DND_INSERT:
11402 val = G_ALERTALTERNATE;
11404 case COMPOSE_DND_ATTACH:
11405 val = G_ALERTOTHER;
11408 /* unexpected case */
11409 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11412 if (val & G_ALERTDISABLE) {
11413 val &= ~G_ALERTDISABLE;
11414 /* remember what action to perform by default, only if we don't click Cancel */
11415 if (val == G_ALERTALTERNATE)
11416 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11417 else if (val == G_ALERTOTHER)
11418 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11421 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11422 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11423 list_free_strings(list);
11426 } else if (val == G_ALERTOTHER) {
11427 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11428 list_free_strings(list);
11433 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11434 compose_insert_file(compose, (const gchar *)tmp->data);
11436 list_free_strings(list);
11438 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11443 static void compose_header_drag_received_cb (GtkWidget *widget,
11444 GdkDragContext *drag_context,
11447 GtkSelectionData *data,
11450 gpointer user_data)
11452 GtkEditable *entry = (GtkEditable *)user_data;
11453 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11455 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11458 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11459 gchar *decoded=g_new(gchar, strlen(email));
11462 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11463 gtk_editable_delete_text(entry, 0, -1);
11464 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11465 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11469 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11472 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11474 Compose *compose = (Compose *)data;
11476 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11477 compose->return_receipt = TRUE;
11479 compose->return_receipt = FALSE;
11482 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11484 Compose *compose = (Compose *)data;
11486 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11487 compose->remove_references = TRUE;
11489 compose->remove_references = FALSE;
11492 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11493 ComposeHeaderEntry *headerentry)
11495 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11499 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11500 GdkEventKey *event,
11501 ComposeHeaderEntry *headerentry)
11503 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11504 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11505 !(event->state & GDK_MODIFIER_MASK) &&
11506 (event->keyval == GDK_KEY_BackSpace) &&
11507 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11508 gtk_container_remove
11509 (GTK_CONTAINER(headerentry->compose->header_table),
11510 headerentry->combo);
11511 gtk_container_remove
11512 (GTK_CONTAINER(headerentry->compose->header_table),
11513 headerentry->entry);
11514 headerentry->compose->header_list =
11515 g_slist_remove(headerentry->compose->header_list,
11517 g_free(headerentry);
11518 } else if (event->keyval == GDK_KEY_Tab) {
11519 if (headerentry->compose->header_last == headerentry) {
11520 /* Override default next focus, and give it to subject_entry
11521 * instead of notebook tabs
11523 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11524 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11531 static gboolean scroll_postpone(gpointer data)
11533 Compose *compose = (Compose *)data;
11535 if (compose->batch)
11538 GTK_EVENTS_FLUSH();
11539 compose_show_first_last_header(compose, FALSE);
11543 static void compose_headerentry_changed_cb(GtkWidget *entry,
11544 ComposeHeaderEntry *headerentry)
11546 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11547 compose_create_header_entry(headerentry->compose);
11548 g_signal_handlers_disconnect_matched
11549 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11550 0, 0, NULL, NULL, headerentry);
11552 if (!headerentry->compose->batch)
11553 g_timeout_add(0, scroll_postpone, headerentry->compose);
11557 static gboolean compose_defer_auto_save_draft(Compose *compose)
11559 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11560 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11564 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11566 GtkAdjustment *vadj;
11568 cm_return_if_fail(compose);
11573 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11574 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11575 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11576 gtk_widget_get_parent(compose->header_table)));
11577 gtk_adjustment_set_value(vadj, (show_first ?
11578 gtk_adjustment_get_lower(vadj) :
11579 (gtk_adjustment_get_upper(vadj) -
11580 gtk_adjustment_get_page_size(vadj))));
11581 gtk_adjustment_changed(vadj);
11584 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11585 const gchar *text, gint len, Compose *compose)
11587 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11588 (G_OBJECT(compose->text), "paste_as_quotation"));
11591 cm_return_if_fail(text != NULL);
11593 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11594 G_CALLBACK(text_inserted),
11596 if (paste_as_quotation) {
11598 const gchar *qmark;
11600 GtkTextIter start_iter;
11603 len = strlen(text);
11605 new_text = g_strndup(text, len);
11607 qmark = compose_quote_char_from_context(compose);
11609 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11610 gtk_text_buffer_place_cursor(buffer, iter);
11612 pos = gtk_text_iter_get_offset(iter);
11614 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11615 _("Quote format error at line %d."));
11616 quote_fmt_reset_vartable();
11618 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11619 GINT_TO_POINTER(paste_as_quotation - 1));
11621 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11622 gtk_text_buffer_place_cursor(buffer, iter);
11623 gtk_text_buffer_delete_mark(buffer, mark);
11625 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11626 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11627 compose_beautify_paragraph(compose, &start_iter, FALSE);
11628 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11629 gtk_text_buffer_delete_mark(buffer, mark);
11631 if (strcmp(text, "\n") || compose->automatic_break
11632 || gtk_text_iter_starts_line(iter)) {
11633 GtkTextIter before_ins;
11634 gtk_text_buffer_insert(buffer, iter, text, len);
11635 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11636 before_ins = *iter;
11637 gtk_text_iter_backward_chars(&before_ins, len);
11638 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11641 /* check if the preceding is just whitespace or quote */
11642 GtkTextIter start_line;
11643 gchar *tmp = NULL, *quote = NULL;
11644 gint quote_len = 0, is_normal = 0;
11645 start_line = *iter;
11646 gtk_text_iter_set_line_offset(&start_line, 0);
11647 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11650 if (*tmp == '\0') {
11653 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11661 gtk_text_buffer_insert(buffer, iter, text, len);
11663 gtk_text_buffer_insert_with_tags_by_name(buffer,
11664 iter, text, len, "no_join", NULL);
11669 if (!paste_as_quotation) {
11670 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11671 compose_beautify_paragraph(compose, iter, FALSE);
11672 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11673 gtk_text_buffer_delete_mark(buffer, mark);
11676 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11677 G_CALLBACK(text_inserted),
11679 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11681 if (compose_can_autosave(compose) &&
11682 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11683 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11684 compose->draft_timeout_tag = g_timeout_add
11685 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11689 static void compose_check_all(GtkAction *action, gpointer data)
11691 Compose *compose = (Compose *)data;
11692 if (!compose->gtkaspell)
11695 if (gtk_widget_has_focus(compose->subject_entry))
11696 claws_spell_entry_check_all(
11697 CLAWS_SPELL_ENTRY(compose->subject_entry));
11699 gtkaspell_check_all(compose->gtkaspell);
11702 static void compose_highlight_all(GtkAction *action, gpointer data)
11704 Compose *compose = (Compose *)data;
11705 if (compose->gtkaspell) {
11706 claws_spell_entry_recheck_all(
11707 CLAWS_SPELL_ENTRY(compose->subject_entry));
11708 gtkaspell_highlight_all(compose->gtkaspell);
11712 static void compose_check_backwards(GtkAction *action, gpointer data)
11714 Compose *compose = (Compose *)data;
11715 if (!compose->gtkaspell) {
11716 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11720 if (gtk_widget_has_focus(compose->subject_entry))
11721 claws_spell_entry_check_backwards(
11722 CLAWS_SPELL_ENTRY(compose->subject_entry));
11724 gtkaspell_check_backwards(compose->gtkaspell);
11727 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11729 Compose *compose = (Compose *)data;
11730 if (!compose->gtkaspell) {
11731 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11735 if (gtk_widget_has_focus(compose->subject_entry))
11736 claws_spell_entry_check_forwards_go(
11737 CLAWS_SPELL_ENTRY(compose->subject_entry));
11739 gtkaspell_check_forwards_go(compose->gtkaspell);
11744 *\brief Guess originating forward account from MsgInfo and several
11745 * "common preference" settings. Return NULL if no guess.
11747 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11749 PrefsAccount *account = NULL;
11751 cm_return_val_if_fail(msginfo, NULL);
11752 cm_return_val_if_fail(msginfo->folder, NULL);
11753 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11755 if (msginfo->folder->prefs->enable_default_account)
11756 account = account_find_from_id(msginfo->folder->prefs->default_account);
11758 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11760 Xstrdup_a(to, msginfo->to, return NULL);
11761 extract_address(to);
11762 account = account_find_from_address(to, FALSE);
11765 if (!account && prefs_common.forward_account_autosel) {
11766 gchar cc[BUFFSIZE];
11767 if (!procheader_get_header_from_msginfo
11768 (msginfo, cc,sizeof cc , "Cc:")) {
11769 gchar *buf = cc + strlen("Cc:");
11770 extract_address(buf);
11771 account = account_find_from_address(buf, FALSE);
11775 if (!account && prefs_common.forward_account_autosel) {
11776 gchar deliveredto[BUFFSIZE];
11777 if (!procheader_get_header_from_msginfo
11778 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11779 gchar *buf = deliveredto + strlen("Delivered-To:");
11780 extract_address(buf);
11781 account = account_find_from_address(buf, FALSE);
11786 account = msginfo->folder->folder->account;
11791 gboolean compose_close(Compose *compose)
11795 cm_return_val_if_fail(compose, FALSE);
11797 if (!g_mutex_trylock(compose->mutex)) {
11798 /* we have to wait for the (possibly deferred by auto-save)
11799 * drafting to be done, before destroying the compose under
11801 debug_print("waiting for drafting to finish...\n");
11802 compose_allow_user_actions(compose, FALSE);
11803 if (compose->close_timeout_tag == 0) {
11804 compose->close_timeout_tag =
11805 g_timeout_add (500, (GSourceFunc) compose_close,
11811 if (compose->draft_timeout_tag >= 0) {
11812 g_source_remove(compose->draft_timeout_tag);
11813 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11816 gtkut_widget_get_uposition(compose->window, &x, &y);
11817 if (!compose->batch) {
11818 prefs_common.compose_x = x;
11819 prefs_common.compose_y = y;
11821 g_mutex_unlock(compose->mutex);
11822 compose_destroy(compose);
11827 * Add entry field for each address in list.
11828 * \param compose E-Mail composition object.
11829 * \param listAddress List of (formatted) E-Mail addresses.
11831 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11834 node = listAddress;
11836 addr = ( gchar * ) node->data;
11837 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11838 node = g_list_next( node );
11842 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11843 guint action, gboolean opening_multiple)
11845 gchar *body = NULL;
11846 GSList *new_msglist = NULL;
11847 MsgInfo *tmp_msginfo = NULL;
11848 gboolean originally_enc = FALSE;
11849 gboolean originally_sig = FALSE;
11850 Compose *compose = NULL;
11851 gchar *s_system = NULL;
11853 cm_return_if_fail(msgview != NULL);
11855 cm_return_if_fail(msginfo_list != NULL);
11857 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11858 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11859 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11861 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11862 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11863 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11864 orig_msginfo, mimeinfo);
11865 if (tmp_msginfo != NULL) {
11866 new_msglist = g_slist_append(NULL, tmp_msginfo);
11868 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11869 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11870 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11872 tmp_msginfo->folder = orig_msginfo->folder;
11873 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11874 if (orig_msginfo->tags) {
11875 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11876 tmp_msginfo->folder->tags_dirty = TRUE;
11882 if (!opening_multiple)
11883 body = messageview_get_selection(msgview);
11886 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11887 procmsg_msginfo_free(&tmp_msginfo);
11888 g_slist_free(new_msglist);
11890 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11892 if (compose && originally_enc) {
11893 compose_force_encryption(compose, compose->account, FALSE, s_system);
11896 if (compose && originally_sig && compose->account->default_sign_reply) {
11897 compose_force_signing(compose, compose->account, s_system);
11901 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11904 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11907 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11908 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11909 GSList *cur = msginfo_list;
11910 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11911 "messages. Opening the windows "
11912 "could take some time. Do you "
11913 "want to continue?"),
11914 g_slist_length(msginfo_list));
11915 if (g_slist_length(msginfo_list) > 9
11916 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11917 != G_ALERTALTERNATE) {
11922 /* We'll open multiple compose windows */
11923 /* let the WM place the next windows */
11924 compose_force_window_origin = FALSE;
11925 for (; cur; cur = cur->next) {
11927 tmplist.data = cur->data;
11928 tmplist.next = NULL;
11929 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11931 compose_force_window_origin = TRUE;
11933 /* forwarding multiple mails as attachments is done via a
11934 * single compose window */
11935 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11939 void compose_check_for_email_account(Compose *compose)
11941 PrefsAccount *ac = NULL, *curr = NULL;
11947 if (compose->account && compose->account->protocol == A_NNTP) {
11948 ac = account_get_cur_account();
11949 if (ac->protocol == A_NNTP) {
11950 list = account_get_list();
11952 for( ; list != NULL ; list = g_list_next(list)) {
11953 curr = (PrefsAccount *) list->data;
11954 if (curr->protocol != A_NNTP) {
11960 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11965 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11966 const gchar *address)
11968 GSList *msginfo_list = NULL;
11969 gchar *body = messageview_get_selection(msgview);
11972 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11974 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11975 compose_check_for_email_account(compose);
11976 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11977 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11978 compose_reply_set_subject(compose, msginfo);
11981 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11984 void compose_set_position(Compose *compose, gint pos)
11986 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11988 gtkut_text_view_set_position(text, pos);
11991 gboolean compose_search_string(Compose *compose,
11992 const gchar *str, gboolean case_sens)
11994 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11996 return gtkut_text_view_search_string(text, str, case_sens);
11999 gboolean compose_search_string_backward(Compose *compose,
12000 const gchar *str, gboolean case_sens)
12002 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
12004 return gtkut_text_view_search_string_backward(text, str, case_sens);
12007 /* allocate a msginfo structure and populate its data from a compose data structure */
12008 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
12010 MsgInfo *newmsginfo;
12012 gchar buf[BUFFSIZE];
12014 cm_return_val_if_fail( compose != NULL, NULL );
12016 newmsginfo = procmsg_msginfo_new();
12019 get_rfc822_date(buf, sizeof(buf));
12020 newmsginfo->date = g_strdup(buf);
12023 if (compose->from_name) {
12024 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12025 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12029 if (compose->subject_entry)
12030 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12032 /* to, cc, reply-to, newsgroups */
12033 for (list = compose->header_list; list; list = list->next) {
12034 gchar *header = gtk_editable_get_chars(
12036 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12037 gchar *entry = gtk_editable_get_chars(
12038 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12040 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12041 if ( newmsginfo->to == NULL ) {
12042 newmsginfo->to = g_strdup(entry);
12043 } else if (entry && *entry) {
12044 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12045 g_free(newmsginfo->to);
12046 newmsginfo->to = tmp;
12049 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12050 if ( newmsginfo->cc == NULL ) {
12051 newmsginfo->cc = g_strdup(entry);
12052 } else if (entry && *entry) {
12053 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12054 g_free(newmsginfo->cc);
12055 newmsginfo->cc = tmp;
12058 if ( strcasecmp(header,
12059 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12060 if ( newmsginfo->newsgroups == NULL ) {
12061 newmsginfo->newsgroups = g_strdup(entry);
12062 } else if (entry && *entry) {
12063 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12064 g_free(newmsginfo->newsgroups);
12065 newmsginfo->newsgroups = tmp;
12073 /* other data is unset */
12079 /* update compose's dictionaries from folder dict settings */
12080 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12081 FolderItem *folder_item)
12083 cm_return_if_fail(compose != NULL);
12085 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12086 FolderItemPrefs *prefs = folder_item->prefs;
12088 if (prefs->enable_default_dictionary)
12089 gtkaspell_change_dict(compose->gtkaspell,
12090 prefs->default_dictionary, FALSE);
12091 if (folder_item->prefs->enable_default_alt_dictionary)
12092 gtkaspell_change_alt_dict(compose->gtkaspell,
12093 prefs->default_alt_dictionary);
12094 if (prefs->enable_default_dictionary
12095 || prefs->enable_default_alt_dictionary)
12096 compose_spell_menu_changed(compose);
12101 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12103 Compose *compose = (Compose *)data;
12105 cm_return_if_fail(compose != NULL);
12107 gtk_widget_grab_focus(compose->text);
12110 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12112 gtk_combo_box_popup(GTK_COMBO_BOX(data));