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:"))) {
5035 if (compose->batch) {
5036 gtk_widget_show_all(compose->window);
5038 aval = alertpanel(_("Send"),
5039 _("The only recipient is the default Cc address. Send anyway?"),
5040 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5041 if (aval != G_ALERTALTERNATE)
5045 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5046 gboolean found_other = FALSE;
5048 /* search header entries for to and newsgroup entries */
5049 for (list = compose->header_list; list; list = list->next) {
5052 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5053 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5056 if (strcmp(entry, compose->account->auto_bcc)
5057 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5067 if (compose->batch) {
5068 gtk_widget_show_all(compose->window);
5070 aval = alertpanel(_("Send"),
5071 _("The only recipient is the default BCC address. Send anyway?"),
5072 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5073 if (aval != G_ALERTALTERNATE)
5080 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5084 if (compose_check_for_valid_recipient(compose) == FALSE) {
5085 if (compose->batch) {
5086 gtk_widget_show_all(compose->window);
5088 alertpanel_error(_("Recipient is not specified."));
5092 if (compose_check_for_set_recipients(compose) == FALSE) {
5096 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5097 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5098 if (*str == '\0' && check_everything == TRUE &&
5099 compose->mode != COMPOSE_REDIRECT) {
5101 gchar *button_label;
5104 if (compose->sending)
5105 button_label = g_strconcat("+", _("_Send"), NULL);
5107 button_label = g_strconcat("+", _("_Queue"), NULL);
5108 message = g_strdup_printf(_("Subject is empty. %s"),
5109 compose->sending?_("Send it anyway?"):
5110 _("Queue it anyway?"));
5112 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5113 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5114 ALERT_QUESTION, G_ALERTDEFAULT);
5116 if (aval & G_ALERTDISABLE) {
5117 aval &= ~G_ALERTDISABLE;
5118 prefs_common.warn_empty_subj = FALSE;
5120 if (aval != G_ALERTALTERNATE)
5125 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5131 gint compose_send(Compose *compose)
5134 FolderItem *folder = NULL;
5136 gchar *msgpath = NULL;
5137 gboolean discard_window = FALSE;
5138 gchar *errstr = NULL;
5139 gchar *tmsgid = NULL;
5140 MainWindow *mainwin = mainwindow_get_mainwindow();
5141 gboolean queued_removed = FALSE;
5143 if (prefs_common.send_dialog_invisible
5144 || compose->batch == TRUE)
5145 discard_window = TRUE;
5147 compose_allow_user_actions (compose, FALSE);
5148 compose->sending = TRUE;
5150 if (compose_check_entries(compose, TRUE) == FALSE) {
5151 if (compose->batch) {
5152 gtk_widget_show_all(compose->window);
5158 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5161 if (compose->batch) {
5162 gtk_widget_show_all(compose->window);
5165 alertpanel_error(_("Could not queue message for sending:\n\n"
5166 "Charset conversion failed."));
5167 } else if (val == -5) {
5168 alertpanel_error(_("Could not queue message for sending:\n\n"
5169 "Couldn't get recipient encryption key."));
5170 } else if (val == -6) {
5172 } else if (val == -3) {
5173 if (privacy_peek_error())
5174 alertpanel_error(_("Could not queue message for sending:\n\n"
5175 "Signature failed: %s"), privacy_get_error());
5176 } else if (val == -2 && errno != 0) {
5177 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5179 alertpanel_error(_("Could not queue message for sending."));
5184 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5185 if (discard_window) {
5186 compose->sending = FALSE;
5187 compose_close(compose);
5188 /* No more compose access in the normal codepath
5189 * after this point! */
5194 alertpanel_error(_("The message was queued but could not be "
5195 "sent.\nUse \"Send queued messages\" from "
5196 "the main window to retry."));
5197 if (!discard_window) {
5204 if (msgpath == NULL) {
5205 msgpath = folder_item_fetch_msg(folder, msgnum);
5206 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5209 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5210 claws_unlink(msgpath);
5213 if (!discard_window) {
5215 if (!queued_removed)
5216 folder_item_remove_msg(folder, msgnum);
5217 folder_item_scan(folder);
5219 /* make sure we delete that */
5220 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5222 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5223 folder_item_remove_msg(folder, tmp->msgnum);
5224 procmsg_msginfo_free(&tmp);
5231 if (!queued_removed)
5232 folder_item_remove_msg(folder, msgnum);
5233 folder_item_scan(folder);
5235 /* make sure we delete that */
5236 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5238 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5239 folder_item_remove_msg(folder, tmp->msgnum);
5240 procmsg_msginfo_free(&tmp);
5243 if (!discard_window) {
5244 compose->sending = FALSE;
5245 compose_allow_user_actions (compose, TRUE);
5246 compose_close(compose);
5250 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5251 "the main window to retry."), errstr);
5254 alertpanel_error_log(_("The message was queued but could not be "
5255 "sent.\nUse \"Send queued messages\" from "
5256 "the main window to retry."));
5258 if (!discard_window) {
5267 toolbar_main_set_sensitive(mainwin);
5268 main_window_set_menu_sensitive(mainwin);
5274 compose_allow_user_actions (compose, TRUE);
5275 compose->sending = FALSE;
5276 compose->modified = TRUE;
5277 toolbar_main_set_sensitive(mainwin);
5278 main_window_set_menu_sensitive(mainwin);
5283 static gboolean compose_use_attach(Compose *compose)
5285 GtkTreeModel *model = gtk_tree_view_get_model
5286 (GTK_TREE_VIEW(compose->attach_clist));
5287 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5290 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5293 gchar buf[BUFFSIZE];
5295 gboolean first_to_address;
5296 gboolean first_cc_address;
5298 ComposeHeaderEntry *headerentry;
5299 const gchar *headerentryname;
5300 const gchar *cc_hdr;
5301 const gchar *to_hdr;
5302 gboolean err = FALSE;
5304 debug_print("Writing redirect header\n");
5306 cc_hdr = prefs_common_translated_header_name("Cc:");
5307 to_hdr = prefs_common_translated_header_name("To:");
5309 first_to_address = TRUE;
5310 for (list = compose->header_list; list; list = list->next) {
5311 headerentry = ((ComposeHeaderEntry *)list->data);
5312 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5314 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5315 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5316 Xstrdup_a(str, entstr, return -1);
5318 if (str[0] != '\0') {
5319 compose_convert_header
5320 (compose, buf, sizeof(buf), str,
5321 strlen("Resent-To") + 2, TRUE);
5323 if (first_to_address) {
5324 err |= (fprintf(fp, "Resent-To: ") < 0);
5325 first_to_address = FALSE;
5327 err |= (fprintf(fp, ",") < 0);
5329 err |= (fprintf(fp, "%s", buf) < 0);
5333 if (!first_to_address) {
5334 err |= (fprintf(fp, "\n") < 0);
5337 first_cc_address = TRUE;
5338 for (list = compose->header_list; list; list = list->next) {
5339 headerentry = ((ComposeHeaderEntry *)list->data);
5340 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5342 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5343 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5344 Xstrdup_a(str, strg, return -1);
5346 if (str[0] != '\0') {
5347 compose_convert_header
5348 (compose, buf, sizeof(buf), str,
5349 strlen("Resent-Cc") + 2, TRUE);
5351 if (first_cc_address) {
5352 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5353 first_cc_address = FALSE;
5355 err |= (fprintf(fp, ",") < 0);
5357 err |= (fprintf(fp, "%s", buf) < 0);
5361 if (!first_cc_address) {
5362 err |= (fprintf(fp, "\n") < 0);
5365 return (err ? -1:0);
5368 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5370 gchar buf[BUFFSIZE];
5372 const gchar *entstr;
5373 /* struct utsname utsbuf; */
5374 gboolean err = FALSE;
5376 cm_return_val_if_fail(fp != NULL, -1);
5377 cm_return_val_if_fail(compose->account != NULL, -1);
5378 cm_return_val_if_fail(compose->account->address != NULL, -1);
5381 if (prefs_common.hide_timezone)
5382 get_rfc822_date_hide_tz(buf, sizeof(buf));
5384 get_rfc822_date(buf, sizeof(buf));
5385 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5388 if (compose->account->name && *compose->account->name) {
5389 compose_convert_header
5390 (compose, buf, sizeof(buf), compose->account->name,
5391 strlen("From: "), TRUE);
5392 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5393 buf, compose->account->address) < 0);
5395 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5398 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5399 if (*entstr != '\0') {
5400 Xstrdup_a(str, entstr, return -1);
5403 compose_convert_header(compose, buf, sizeof(buf), str,
5404 strlen("Subject: "), FALSE);
5405 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5409 /* Resent-Message-ID */
5410 if (compose->account->gen_msgid) {
5411 gchar *addr = prefs_account_generate_msgid(compose->account);
5412 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", addr) < 0);
5414 g_free(compose->msgid);
5415 compose->msgid = addr;
5417 compose->msgid = NULL;
5420 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5423 /* separator between header and body */
5424 err |= (fputs("\n", fp) == EOF);
5426 return (err ? -1:0);
5429 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5433 gchar buf[BUFFSIZE];
5435 gboolean skip = FALSE;
5436 gboolean err = FALSE;
5437 gchar *not_included[]={
5438 "Return-Path:", "Delivered-To:", "Received:",
5439 "Subject:", "X-UIDL:", "AF:",
5440 "NF:", "PS:", "SRH:",
5441 "SFN:", "DSR:", "MID:",
5442 "CFG:", "PT:", "S:",
5443 "RQ:", "SSV:", "NSV:",
5444 "SSH:", "R:", "MAID:",
5445 "NAID:", "RMID:", "FMID:",
5446 "SCF:", "RRCPT:", "NG:",
5447 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5448 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5449 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5450 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5451 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5454 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5455 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5459 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5461 for (i = 0; not_included[i] != NULL; i++) {
5462 if (g_ascii_strncasecmp(buf, not_included[i],
5463 strlen(not_included[i])) == 0) {
5470 if (fputs(buf, fdest) == -1)
5473 if (!prefs_common.redirect_keep_from) {
5474 if (g_ascii_strncasecmp(buf, "From:",
5475 strlen("From:")) == 0) {
5476 err |= (fputs(" (by way of ", fdest) == EOF);
5477 if (compose->account->name
5478 && *compose->account->name) {
5479 compose_convert_header
5480 (compose, buf, sizeof(buf),
5481 compose->account->name,
5484 err |= (fprintf(fdest, "%s <%s>",
5486 compose->account->address) < 0);
5488 err |= (fprintf(fdest, "%s",
5489 compose->account->address) < 0);
5490 err |= (fputs(")", fdest) == EOF);
5494 if (fputs("\n", fdest) == -1)
5501 if (compose_redirect_write_headers(compose, fdest))
5504 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5505 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5518 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5520 GtkTextBuffer *buffer;
5521 GtkTextIter start, end;
5522 gchar *chars, *tmp_enc_file, *content;
5524 const gchar *out_codeset;
5525 EncodingType encoding = ENC_UNKNOWN;
5526 MimeInfo *mimemsg, *mimetext;
5528 const gchar *src_codeset = CS_INTERNAL;
5529 gchar *from_addr = NULL;
5530 gchar *from_name = NULL;
5533 if (action == COMPOSE_WRITE_FOR_SEND) {
5534 attach_parts = TRUE;
5536 /* We're sending the message, generate a Message-ID
5538 if (compose->msgid == NULL &&
5539 compose->account->gen_msgid) {
5540 compose->msgid = prefs_account_generate_msgid(compose->account);
5544 /* create message MimeInfo */
5545 mimemsg = procmime_mimeinfo_new();
5546 mimemsg->type = MIMETYPE_MESSAGE;
5547 mimemsg->subtype = g_strdup("rfc822");
5548 mimemsg->content = MIMECONTENT_MEM;
5549 mimemsg->tmp = TRUE; /* must free content later */
5550 mimemsg->data.mem = compose_get_header(compose);
5552 /* Create text part MimeInfo */
5553 /* get all composed text */
5554 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5555 gtk_text_buffer_get_start_iter(buffer, &start);
5556 gtk_text_buffer_get_end_iter(buffer, &end);
5557 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5559 out_codeset = conv_get_charset_str(compose->out_encoding);
5561 if (!out_codeset && is_ascii_str(chars)) {
5562 out_codeset = CS_US_ASCII;
5563 } else if (prefs_common.outgoing_fallback_to_ascii &&
5564 is_ascii_str(chars)) {
5565 out_codeset = CS_US_ASCII;
5566 encoding = ENC_7BIT;
5570 gchar *test_conv_global_out = NULL;
5571 gchar *test_conv_reply = NULL;
5573 /* automatic mode. be automatic. */
5574 codeconv_set_strict(TRUE);
5576 out_codeset = conv_get_outgoing_charset_str();
5578 debug_print("trying to convert to %s\n", out_codeset);
5579 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5582 if (!test_conv_global_out && compose->orig_charset
5583 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5584 out_codeset = compose->orig_charset;
5585 debug_print("failure; trying to convert to %s\n", out_codeset);
5586 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5589 if (!test_conv_global_out && !test_conv_reply) {
5591 out_codeset = CS_INTERNAL;
5592 debug_print("failure; finally using %s\n", out_codeset);
5594 g_free(test_conv_global_out);
5595 g_free(test_conv_reply);
5596 codeconv_set_strict(FALSE);
5599 if (encoding == ENC_UNKNOWN) {
5600 if (prefs_common.encoding_method == CTE_BASE64)
5601 encoding = ENC_BASE64;
5602 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5603 encoding = ENC_QUOTED_PRINTABLE;
5604 else if (prefs_common.encoding_method == CTE_8BIT)
5605 encoding = ENC_8BIT;
5607 encoding = procmime_get_encoding_for_charset(out_codeset);
5610 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5611 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5613 if (action == COMPOSE_WRITE_FOR_SEND) {
5614 codeconv_set_strict(TRUE);
5615 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5616 codeconv_set_strict(FALSE);
5621 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5622 "to the specified %s charset.\n"
5623 "Send it as %s?"), out_codeset, src_codeset);
5624 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5625 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5626 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5629 if (aval != G_ALERTALTERNATE) {
5634 out_codeset = src_codeset;
5640 out_codeset = src_codeset;
5645 if (prefs_common.rewrite_first_from && (encoding == ENC_8BIT || encoding == ENC_7BIT)) {
5646 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5647 strstr(buf, "\nFrom ") != NULL) {
5648 encoding = ENC_QUOTED_PRINTABLE;
5652 mimetext = procmime_mimeinfo_new();
5653 mimetext->content = MIMECONTENT_MEM;
5654 mimetext->tmp = TRUE; /* must free content later */
5655 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5656 * and free the data, which we need later. */
5657 mimetext->data.mem = g_strdup(buf);
5658 mimetext->type = MIMETYPE_TEXT;
5659 mimetext->subtype = g_strdup("plain");
5660 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5661 g_strdup(out_codeset));
5663 /* protect trailing spaces when signing message */
5664 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5665 privacy_system_can_sign(compose->privacy_system)) {
5666 encoding = ENC_QUOTED_PRINTABLE;
5670 debug_print("main text: %Id bytes encoded as %s in %d\n",
5672 debug_print("main text: %zd bytes encoded as %s in %d\n",
5674 strlen(buf), out_codeset, encoding);
5676 /* check for line length limit */
5677 if (action == COMPOSE_WRITE_FOR_SEND &&
5678 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5679 check_line_length(buf, 1000, &line) < 0) {
5682 msg = g_strdup_printf
5683 (_("Line %d exceeds the line length limit (998 bytes).\n"
5684 "The contents of the message might be broken on the way to the delivery.\n"
5686 "Send it anyway?"), line + 1);
5687 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5689 if (aval != G_ALERTALTERNATE) {
5695 if (encoding != ENC_UNKNOWN)
5696 procmime_encode_content(mimetext, encoding);
5698 /* append attachment parts */
5699 if (compose_use_attach(compose) && attach_parts) {
5700 MimeInfo *mimempart;
5701 gchar *boundary = NULL;
5702 mimempart = procmime_mimeinfo_new();
5703 mimempart->content = MIMECONTENT_EMPTY;
5704 mimempart->type = MIMETYPE_MULTIPART;
5705 mimempart->subtype = g_strdup("mixed");
5709 boundary = generate_mime_boundary(NULL);
5710 } while (strstr(buf, boundary) != NULL);
5712 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5715 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5717 g_node_append(mimempart->node, mimetext->node);
5718 g_node_append(mimemsg->node, mimempart->node);
5720 if (compose_add_attachments(compose, mimempart) < 0)
5723 g_node_append(mimemsg->node, mimetext->node);
5727 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5728 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5729 /* extract name and address */
5730 if (strstr(spec, " <") && strstr(spec, ">")) {
5731 from_addr = g_strdup(strrchr(spec, '<')+1);
5732 *(strrchr(from_addr, '>')) = '\0';
5733 from_name = g_strdup(spec);
5734 *(strrchr(from_name, '<')) = '\0';
5741 /* sign message if sending */
5742 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5743 privacy_system_can_sign(compose->privacy_system))
5744 if (!privacy_sign(compose->privacy_system, mimemsg,
5745 compose->account, from_addr)) {
5753 if (compose->use_encryption) {
5754 if (compose->encdata != NULL &&
5755 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5757 /* First, write an unencrypted copy and save it to outbox, if
5758 * user wants that. */
5759 if (compose->account->save_encrypted_as_clear_text) {
5760 debug_print("saving sent message unencrypted...\n");
5761 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5765 /* fp now points to a file with headers written,
5766 * let's make a copy. */
5768 content = file_read_stream_to_str(fp);
5770 str_write_to_file(content, tmp_enc_file);
5773 /* Now write the unencrypted body. */
5774 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5775 procmime_write_mimeinfo(mimemsg, tmpfp);
5778 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5780 outbox = folder_get_default_outbox();
5782 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5783 claws_unlink(tmp_enc_file);
5785 g_warning("Can't open file '%s'", tmp_enc_file);
5788 g_warning("couldn't get tempfile");
5791 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5792 debug_print("Couldn't encrypt mime structure: %s.\n",
5793 privacy_get_error());
5794 alertpanel_error(_("Couldn't encrypt the email: %s"),
5795 privacy_get_error());
5800 procmime_write_mimeinfo(mimemsg, fp);
5802 procmime_mimeinfo_free_all(&mimemsg);
5807 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5809 GtkTextBuffer *buffer;
5810 GtkTextIter start, end;
5815 if ((fp = g_fopen(file, "wb")) == NULL) {
5816 FILE_OP_ERROR(file, "fopen");
5820 /* chmod for security */
5821 if (change_file_mode_rw(fp, file) < 0) {
5822 FILE_OP_ERROR(file, "chmod");
5823 g_warning("can't change file mode");
5826 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5827 gtk_text_buffer_get_start_iter(buffer, &start);
5828 gtk_text_buffer_get_end_iter(buffer, &end);
5829 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5831 chars = conv_codeset_strdup
5832 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5841 len = strlen(chars);
5842 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5843 FILE_OP_ERROR(file, "fwrite");
5852 if (fclose(fp) == EOF) {
5853 FILE_OP_ERROR(file, "fclose");
5860 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5863 MsgInfo *msginfo = compose->targetinfo;
5865 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5866 if (!msginfo) return -1;
5868 if (!force && MSG_IS_LOCKED(msginfo->flags))
5871 item = msginfo->folder;
5872 cm_return_val_if_fail(item != NULL, -1);
5874 if (procmsg_msg_exist(msginfo) &&
5875 (folder_has_parent_of_type(item, F_QUEUE) ||
5876 folder_has_parent_of_type(item, F_DRAFT)
5877 || msginfo == compose->autosaved_draft)) {
5878 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5879 g_warning("can't remove the old message");
5882 debug_print("removed reedit target %d\n", msginfo->msgnum);
5889 static void compose_remove_draft(Compose *compose)
5892 MsgInfo *msginfo = compose->targetinfo;
5893 drafts = account_get_special_folder(compose->account, F_DRAFT);
5895 if (procmsg_msg_exist(msginfo)) {
5896 folder_item_remove_msg(drafts, msginfo->msgnum);
5901 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5902 gboolean remove_reedit_target)
5904 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5907 static gboolean compose_warn_encryption(Compose *compose)
5909 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5910 AlertValue val = G_ALERTALTERNATE;
5912 if (warning == NULL)
5915 val = alertpanel_full(_("Encryption warning"), warning,
5916 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5917 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5918 if (val & G_ALERTDISABLE) {
5919 val &= ~G_ALERTDISABLE;
5920 if (val == G_ALERTALTERNATE)
5921 privacy_inhibit_encrypt_warning(compose->privacy_system,
5925 if (val == G_ALERTALTERNATE) {
5932 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5933 gchar **msgpath, gboolean check_subject,
5934 gboolean remove_reedit_target)
5941 PrefsAccount *mailac = NULL, *newsac = NULL;
5942 gboolean err = FALSE;
5944 debug_print("queueing message...\n");
5945 cm_return_val_if_fail(compose->account != NULL, -1);
5947 if (compose_check_entries(compose, check_subject) == FALSE) {
5948 if (compose->batch) {
5949 gtk_widget_show_all(compose->window);
5954 if (!compose->to_list && !compose->newsgroup_list) {
5955 g_warning("can't get recipient list.");
5959 if (compose->to_list) {
5960 if (compose->account->protocol != A_NNTP)
5961 mailac = compose->account;
5962 else if (cur_account && cur_account->protocol != A_NNTP)
5963 mailac = cur_account;
5964 else if (!(mailac = compose_current_mail_account())) {
5965 alertpanel_error(_("No account for sending mails available!"));
5970 if (compose->newsgroup_list) {
5971 if (compose->account->protocol == A_NNTP)
5972 newsac = compose->account;
5974 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5979 /* write queue header */
5980 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5981 G_DIR_SEPARATOR, compose, (guint) rand());
5982 debug_print("queuing to %s\n", tmp);
5983 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5984 FILE_OP_ERROR(tmp, "fopen");
5989 if (change_file_mode_rw(fp, tmp) < 0) {
5990 FILE_OP_ERROR(tmp, "chmod");
5991 g_warning("can't change file mode");
5994 /* queueing variables */
5995 err |= (fprintf(fp, "AF:\n") < 0);
5996 err |= (fprintf(fp, "NF:0\n") < 0);
5997 err |= (fprintf(fp, "PS:10\n") < 0);
5998 err |= (fprintf(fp, "SRH:1\n") < 0);
5999 err |= (fprintf(fp, "SFN:\n") < 0);
6000 err |= (fprintf(fp, "DSR:\n") < 0);
6002 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
6004 err |= (fprintf(fp, "MID:\n") < 0);
6005 err |= (fprintf(fp, "CFG:\n") < 0);
6006 err |= (fprintf(fp, "PT:0\n") < 0);
6007 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6008 err |= (fprintf(fp, "RQ:\n") < 0);
6010 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6012 err |= (fprintf(fp, "SSV:\n") < 0);
6014 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6016 err |= (fprintf(fp, "NSV:\n") < 0);
6017 err |= (fprintf(fp, "SSH:\n") < 0);
6018 /* write recepient list */
6019 if (compose->to_list) {
6020 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6021 for (cur = compose->to_list->next; cur != NULL;
6023 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6024 err |= (fprintf(fp, "\n") < 0);
6026 /* write newsgroup list */
6027 if (compose->newsgroup_list) {
6028 err |= (fprintf(fp, "NG:") < 0);
6029 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6030 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6031 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6032 err |= (fprintf(fp, "\n") < 0);
6034 /* Sylpheed account IDs */
6036 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6038 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6041 if (compose->privacy_system != NULL) {
6042 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6043 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6044 if (compose->use_encryption) {
6045 if (!compose_warn_encryption(compose)) {
6051 if (mailac && mailac->encrypt_to_self) {
6052 GSList *tmp_list = g_slist_copy(compose->to_list);
6053 tmp_list = g_slist_append(tmp_list, compose->account->address);
6054 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6055 g_slist_free(tmp_list);
6057 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6059 if (compose->encdata != NULL) {
6060 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6061 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6062 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6063 compose->encdata) < 0);
6064 } /* else we finally dont want to encrypt */
6066 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6067 /* and if encdata was null, it means there's been a problem in
6070 g_warning("failed to write queue message");
6079 /* Save copy folder */
6080 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6081 gchar *savefolderid;
6083 savefolderid = compose_get_save_to(compose);
6084 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6085 g_free(savefolderid);
6087 /* Save copy folder */
6088 if (compose->return_receipt) {
6089 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6091 /* Message-ID of message replying to */
6092 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6093 gchar *folderid = NULL;
6095 if (compose->replyinfo->folder)
6096 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6097 if (folderid == NULL)
6098 folderid = g_strdup("NULL");
6100 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6103 /* Message-ID of message forwarding to */
6104 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6105 gchar *folderid = NULL;
6107 if (compose->fwdinfo->folder)
6108 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6109 if (folderid == NULL)
6110 folderid = g_strdup("NULL");
6112 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6116 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6117 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6119 /* end of headers */
6120 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6122 if (compose->redirect_filename != NULL) {
6123 if (compose_redirect_write_to_file(compose, fp) < 0) {
6131 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6135 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6139 g_warning("failed to write queue message");
6145 if (fclose(fp) == EOF) {
6146 FILE_OP_ERROR(tmp, "fclose");
6152 if (item && *item) {
6155 queue = account_get_special_folder(compose->account, F_QUEUE);
6158 g_warning("can't find queue folder");
6163 folder_item_scan(queue);
6164 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6165 g_warning("can't queue the message");
6171 if (msgpath == NULL) {
6177 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6178 compose_remove_reedit_target(compose, FALSE);
6181 if ((msgnum != NULL) && (item != NULL)) {
6189 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6192 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6195 gchar *type, *subtype;
6196 GtkTreeModel *model;
6199 model = gtk_tree_view_get_model(tree_view);
6201 if (!gtk_tree_model_get_iter_first(model, &iter))
6204 gtk_tree_model_get(model, &iter,
6208 if (!is_file_exist(ainfo->file)) {
6209 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6210 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6211 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6213 if (val == G_ALERTDEFAULT) {
6218 if (g_stat(ainfo->file, &statbuf) < 0)
6221 mimepart = procmime_mimeinfo_new();
6222 mimepart->content = MIMECONTENT_FILE;
6223 mimepart->data.filename = g_strdup(ainfo->file);
6224 mimepart->tmp = FALSE; /* or we destroy our attachment */
6225 mimepart->offset = 0;
6226 mimepart->length = statbuf.st_size;
6228 type = g_strdup(ainfo->content_type);
6230 if (!strchr(type, '/')) {
6232 type = g_strdup("application/octet-stream");
6235 subtype = strchr(type, '/') + 1;
6236 *(subtype - 1) = '\0';
6237 mimepart->type = procmime_get_media_type(type);
6238 mimepart->subtype = g_strdup(subtype);
6241 if (mimepart->type == MIMETYPE_MESSAGE &&
6242 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6243 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6244 } else if (mimepart->type == MIMETYPE_TEXT) {
6245 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6246 /* Text parts with no name come from multipart/alternative
6247 * forwards. Make sure the recipient won't look at the
6248 * original HTML part by mistake. */
6249 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6250 ainfo->name = g_strdup_printf(_("Original %s part"),
6254 g_hash_table_insert(mimepart->typeparameters,
6255 g_strdup("charset"), g_strdup(ainfo->charset));
6257 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6258 if (mimepart->type == MIMETYPE_APPLICATION &&
6259 !strcmp2(mimepart->subtype, "octet-stream"))
6260 g_hash_table_insert(mimepart->typeparameters,
6261 g_strdup("name"), g_strdup(ainfo->name));
6262 g_hash_table_insert(mimepart->dispositionparameters,
6263 g_strdup("filename"), g_strdup(ainfo->name));
6264 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6267 if (mimepart->type == MIMETYPE_MESSAGE
6268 || mimepart->type == MIMETYPE_MULTIPART)
6269 ainfo->encoding = ENC_BINARY;
6270 else if (compose->use_signing) {
6271 if (ainfo->encoding == ENC_7BIT)
6272 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6273 else if (ainfo->encoding == ENC_8BIT)
6274 ainfo->encoding = ENC_BASE64;
6279 procmime_encode_content(mimepart, ainfo->encoding);
6281 g_node_append(parent->node, mimepart->node);
6282 } while (gtk_tree_model_iter_next(model, &iter));
6287 static gchar *compose_quote_list_of_addresses(gchar *str)
6289 GSList *list = NULL, *item = NULL;
6290 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6292 list = address_list_append_with_comments(list, str);
6293 for (item = list; item != NULL; item = item->next) {
6294 gchar *spec = item->data;
6295 gchar *endofname = strstr(spec, " <");
6296 if (endofname != NULL) {
6299 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6300 qqname = escape_internal_quotes(qname, '"');
6302 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6303 gchar *addr = g_strdup(endofname);
6304 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6305 faddr = g_strconcat(name, addr, NULL);
6308 debug_print("new auto-quoted address: '%s'\n", faddr);
6312 result = g_strdup((faddr != NULL)? faddr: spec);
6314 result = g_strconcat(result,
6316 (faddr != NULL)? faddr: spec,
6319 if (faddr != NULL) {
6324 slist_free_strings_full(list);
6329 #define IS_IN_CUSTOM_HEADER(header) \
6330 (compose->account->add_customhdr && \
6331 custom_header_find(compose->account->customhdr_list, header) != NULL)
6333 static void compose_add_headerfield_from_headerlist(Compose *compose,
6335 const gchar *fieldname,
6336 const gchar *seperator)
6338 gchar *str, *fieldname_w_colon;
6339 gboolean add_field = FALSE;
6341 ComposeHeaderEntry *headerentry;
6342 const gchar *headerentryname;
6343 const gchar *trans_fieldname;
6346 if (IS_IN_CUSTOM_HEADER(fieldname))
6349 debug_print("Adding %s-fields\n", fieldname);
6351 fieldstr = g_string_sized_new(64);
6353 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6354 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6356 for (list = compose->header_list; list; list = list->next) {
6357 headerentry = ((ComposeHeaderEntry *)list->data);
6358 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6360 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6361 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6363 str = compose_quote_list_of_addresses(ustr);
6365 if (str != NULL && str[0] != '\0') {
6367 g_string_append(fieldstr, seperator);
6368 g_string_append(fieldstr, str);
6377 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6378 compose_convert_header
6379 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6380 strlen(fieldname) + 2, TRUE);
6381 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6385 g_free(fieldname_w_colon);
6386 g_string_free(fieldstr, TRUE);
6391 static gchar *compose_get_manual_headers_info(Compose *compose)
6393 GString *sh_header = g_string_new(" ");
6395 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6397 for (list = compose->header_list; list; list = list->next) {
6398 ComposeHeaderEntry *headerentry;
6401 gchar *headername_wcolon;
6402 const gchar *headername_trans;
6404 gboolean standard_header = FALSE;
6406 headerentry = ((ComposeHeaderEntry *)list->data);
6408 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6410 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6415 if (!strstr(tmp, ":")) {
6416 headername_wcolon = g_strconcat(tmp, ":", NULL);
6417 headername = g_strdup(tmp);
6419 headername_wcolon = g_strdup(tmp);
6420 headername = g_strdup(strtok(tmp, ":"));
6424 string = std_headers;
6425 while (*string != NULL) {
6426 headername_trans = prefs_common_translated_header_name(*string);
6427 if (!strcmp(headername_trans, headername_wcolon))
6428 standard_header = TRUE;
6431 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6432 g_string_append_printf(sh_header, "%s ", headername);
6434 g_free(headername_wcolon);
6436 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6437 return g_string_free(sh_header, FALSE);
6440 static gchar *compose_get_header(Compose *compose)
6442 gchar buf[BUFFSIZE];
6443 const gchar *entry_str;
6447 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6449 gchar *from_name = NULL, *from_address = NULL;
6452 cm_return_val_if_fail(compose->account != NULL, NULL);
6453 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6455 header = g_string_sized_new(64);
6458 if (prefs_common.hide_timezone)
6459 get_rfc822_date_hide_tz(buf, sizeof(buf));
6461 get_rfc822_date(buf, sizeof(buf));
6462 g_string_append_printf(header, "Date: %s\n", buf);
6466 if (compose->account->name && *compose->account->name) {
6468 QUOTE_IF_REQUIRED(buf, compose->account->name);
6469 tmp = g_strdup_printf("%s <%s>",
6470 buf, compose->account->address);
6472 tmp = g_strdup_printf("%s",
6473 compose->account->address);
6475 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6476 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6478 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6479 from_address = g_strdup(compose->account->address);
6481 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6482 /* extract name and address */
6483 if (strstr(spec, " <") && strstr(spec, ">")) {
6484 from_address = g_strdup(strrchr(spec, '<')+1);
6485 *(strrchr(from_address, '>')) = '\0';
6486 from_name = g_strdup(spec);
6487 *(strrchr(from_name, '<')) = '\0';
6490 from_address = g_strdup(spec);
6497 if (from_name && *from_name) {
6499 compose_convert_header
6500 (compose, buf, sizeof(buf), from_name,
6501 strlen("From: "), TRUE);
6502 QUOTE_IF_REQUIRED(name, buf);
6503 qname = escape_internal_quotes(name, '"');
6505 g_string_append_printf(header, "From: %s <%s>\n",
6506 qname, from_address);
6507 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6508 compose->return_receipt) {
6509 compose_convert_header(compose, buf, sizeof(buf), from_name,
6510 strlen("Disposition-Notification-To: "),
6512 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, from_address);
6517 g_string_append_printf(header, "From: %s\n", from_address);
6518 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6519 compose->return_receipt)
6520 g_string_append_printf(header, "Disposition-Notification-To: %s\n", from_address);
6524 g_free(from_address);
6527 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6530 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6533 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6537 * If this account is a NNTP account remove Bcc header from
6538 * message body since it otherwise will be publicly shown
6540 if (compose->account->protocol != A_NNTP)
6541 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6544 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6546 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6549 compose_convert_header(compose, buf, sizeof(buf), str,
6550 strlen("Subject: "), FALSE);
6551 g_string_append_printf(header, "Subject: %s\n", buf);
6557 if (compose->msgid != NULL && strlen(compose->msgid) > 0) {
6558 g_string_append_printf(header, "Message-ID: <%s>\n",
6562 if (compose->remove_references == FALSE) {
6564 if (compose->inreplyto && compose->to_list)
6565 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6568 if (compose->references)
6569 g_string_append_printf(header, "References: %s\n", compose->references);
6573 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6576 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6579 if (compose->account->organization &&
6580 strlen(compose->account->organization) &&
6581 !IS_IN_CUSTOM_HEADER("Organization")) {
6582 compose_convert_header(compose, buf, sizeof(buf),
6583 compose->account->organization,
6584 strlen("Organization: "), FALSE);
6585 g_string_append_printf(header, "Organization: %s\n", buf);
6588 /* Program version and system info */
6589 if (compose->account->gen_xmailer &&
6590 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6591 !compose->newsgroup_list) {
6592 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6594 gtk_major_version, gtk_minor_version, gtk_micro_version,
6597 if (compose->account->gen_xmailer &&
6598 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6599 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6601 gtk_major_version, gtk_minor_version, gtk_micro_version,
6605 /* custom headers */
6606 if (compose->account->add_customhdr) {
6609 for (cur = compose->account->customhdr_list; cur != NULL;
6611 CustomHeader *chdr = (CustomHeader *)cur->data;
6613 if (custom_header_is_allowed(chdr->name)
6614 && chdr->value != NULL
6615 && *(chdr->value) != '\0') {
6616 compose_convert_header
6617 (compose, buf, sizeof(buf),
6619 strlen(chdr->name) + 2, FALSE);
6620 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6625 /* Automatic Faces and X-Faces */
6626 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6627 g_string_append_printf(header, "X-Face: %s\n", buf);
6629 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6630 g_string_append_printf(header, "X-Face: %s\n", buf);
6632 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6633 g_string_append_printf(header, "Face: %s\n", buf);
6635 else if (get_default_face (buf, sizeof(buf)) == 0) {
6636 g_string_append_printf(header, "Face: %s\n", buf);
6640 switch (compose->priority) {
6641 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6642 "X-Priority: 1 (Highest)\n");
6644 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6645 "X-Priority: 2 (High)\n");
6647 case PRIORITY_NORMAL: break;
6648 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6649 "X-Priority: 4 (Low)\n");
6651 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6652 "X-Priority: 5 (Lowest)\n");
6654 default: debug_print("compose: priority unknown : %d\n",
6658 /* get special headers */
6659 for (list = compose->header_list; list; list = list->next) {
6660 ComposeHeaderEntry *headerentry;
6663 gchar *headername_wcolon;
6664 const gchar *headername_trans;
6667 gboolean standard_header = FALSE;
6669 headerentry = ((ComposeHeaderEntry *)list->data);
6671 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6673 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6678 if (!strstr(tmp, ":")) {
6679 headername_wcolon = g_strconcat(tmp, ":", NULL);
6680 headername = g_strdup(tmp);
6682 headername_wcolon = g_strdup(tmp);
6683 headername = g_strdup(strtok(tmp, ":"));
6687 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6688 Xstrdup_a(headervalue, entry_str, return NULL);
6689 subst_char(headervalue, '\r', ' ');
6690 subst_char(headervalue, '\n', ' ');
6691 string = std_headers;
6692 while (*string != NULL) {
6693 headername_trans = prefs_common_translated_header_name(*string);
6694 if (!strcmp(headername_trans, headername_wcolon))
6695 standard_header = TRUE;
6698 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6699 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6702 g_free(headername_wcolon);
6706 g_string_free(header, FALSE);
6711 #undef IS_IN_CUSTOM_HEADER
6713 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6714 gint header_len, gboolean addr_field)
6716 gchar *tmpstr = NULL;
6717 const gchar *out_codeset = NULL;
6719 cm_return_if_fail(src != NULL);
6720 cm_return_if_fail(dest != NULL);
6722 if (len < 1) return;
6724 tmpstr = g_strdup(src);
6726 subst_char(tmpstr, '\n', ' ');
6727 subst_char(tmpstr, '\r', ' ');
6730 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6731 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6732 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6737 codeconv_set_strict(TRUE);
6738 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6739 conv_get_charset_str(compose->out_encoding));
6740 codeconv_set_strict(FALSE);
6742 if (!dest || *dest == '\0') {
6743 gchar *test_conv_global_out = NULL;
6744 gchar *test_conv_reply = NULL;
6746 /* automatic mode. be automatic. */
6747 codeconv_set_strict(TRUE);
6749 out_codeset = conv_get_outgoing_charset_str();
6751 debug_print("trying to convert to %s\n", out_codeset);
6752 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6755 if (!test_conv_global_out && compose->orig_charset
6756 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6757 out_codeset = compose->orig_charset;
6758 debug_print("failure; trying to convert to %s\n", out_codeset);
6759 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6762 if (!test_conv_global_out && !test_conv_reply) {
6764 out_codeset = CS_INTERNAL;
6765 debug_print("finally using %s\n", out_codeset);
6767 g_free(test_conv_global_out);
6768 g_free(test_conv_reply);
6769 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6771 codeconv_set_strict(FALSE);
6776 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6780 cm_return_if_fail(user_data != NULL);
6782 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6783 g_strstrip(address);
6784 if (*address != '\0') {
6785 gchar *name = procheader_get_fromname(address);
6786 extract_address(address);
6787 #ifndef USE_ALT_ADDRBOOK
6788 addressbook_add_contact(name, address, NULL, NULL);
6790 debug_print("%s: %s\n", name, address);
6791 if (addressadd_selection(name, address, NULL, NULL)) {
6792 debug_print( "addressbook_add_contact - added\n" );
6799 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6801 GtkWidget *menuitem;
6804 cm_return_if_fail(menu != NULL);
6805 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6807 menuitem = gtk_separator_menu_item_new();
6808 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6809 gtk_widget_show(menuitem);
6811 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6812 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6814 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6815 g_strstrip(address);
6816 if (*address == '\0') {
6817 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6820 g_signal_connect(G_OBJECT(menuitem), "activate",
6821 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6822 gtk_widget_show(menuitem);
6825 void compose_add_extra_header(gchar *header, GtkListStore *model)
6828 if (strcmp(header, "")) {
6829 COMBOBOX_ADD(model, header, COMPOSE_TO);
6833 void compose_add_extra_header_entries(GtkListStore *model)
6837 gchar buf[BUFFSIZE];
6840 if (extra_headers == NULL) {
6841 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6842 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6843 debug_print("extra headers file not found\n");
6844 goto extra_headers_done;
6846 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6847 lastc = strlen(buf) - 1; /* remove trailing control chars */
6848 while (lastc >= 0 && buf[lastc] != ':')
6849 buf[lastc--] = '\0';
6850 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6851 buf[lastc] = '\0'; /* remove trailing : for comparison */
6852 if (custom_header_is_allowed(buf)) {
6854 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6857 g_message("disallowed extra header line: %s\n", buf);
6861 g_message("invalid extra header line: %s\n", buf);
6867 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6868 extra_headers = g_slist_reverse(extra_headers);
6870 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6873 static void compose_create_header_entry(Compose *compose)
6875 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6882 const gchar *header = NULL;
6883 ComposeHeaderEntry *headerentry;
6884 gboolean standard_header = FALSE;
6885 GtkListStore *model;
6888 headerentry = g_new0(ComposeHeaderEntry, 1);
6890 /* Combo box model */
6891 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6892 #if !GTK_CHECK_VERSION(2, 24, 0)
6893 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6895 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6897 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6899 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6901 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6902 COMPOSE_NEWSGROUPS);
6903 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6905 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6906 COMPOSE_FOLLOWUPTO);
6907 compose_add_extra_header_entries(model);
6910 #if GTK_CHECK_VERSION(2, 24, 0)
6911 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6912 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6913 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6914 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6915 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6917 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6918 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6919 G_CALLBACK(compose_grab_focus_cb), compose);
6920 gtk_widget_show(combo);
6922 /* Putting only the combobox child into focus chain of its parent causes
6923 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6924 * This eliminates need to pres Tab twice in order to really get from the
6925 * combobox to next widget. */
6927 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6928 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6931 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6932 compose->header_nextrow, compose->header_nextrow+1,
6933 GTK_SHRINK, GTK_FILL, 0, 0);
6934 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6935 const gchar *last_header_entry = gtk_entry_get_text(
6936 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6938 while (*string != NULL) {
6939 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6940 standard_header = TRUE;
6943 if (standard_header)
6944 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6946 if (!compose->header_last || !standard_header) {
6947 switch(compose->account->protocol) {
6949 header = prefs_common_translated_header_name("Newsgroups:");
6952 header = prefs_common_translated_header_name("To:");
6957 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6959 gtk_editable_set_editable(
6960 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6961 prefs_common.type_any_header);
6963 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6964 G_CALLBACK(compose_grab_focus_cb), compose);
6966 /* Entry field with cleanup button */
6967 button = gtk_button_new();
6968 gtk_button_set_image(GTK_BUTTON(button),
6969 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6970 gtk_widget_show(button);
6971 CLAWS_SET_TIP(button,
6972 _("Delete entry contents"));
6973 entry = gtk_entry_new();
6974 gtk_widget_show(entry);
6975 CLAWS_SET_TIP(entry,
6976 _("Use <tab> to autocomplete from addressbook"));
6977 hbox = gtk_hbox_new (FALSE, 0);
6978 gtk_widget_show(hbox);
6979 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6980 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6981 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6982 compose->header_nextrow, compose->header_nextrow+1,
6983 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6985 g_signal_connect(G_OBJECT(entry), "key-press-event",
6986 G_CALLBACK(compose_headerentry_key_press_event_cb),
6988 g_signal_connect(G_OBJECT(entry), "changed",
6989 G_CALLBACK(compose_headerentry_changed_cb),
6991 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6992 G_CALLBACK(compose_grab_focus_cb), compose);
6994 g_signal_connect(G_OBJECT(button), "clicked",
6995 G_CALLBACK(compose_headerentry_button_clicked_cb),
6999 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7000 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7001 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7002 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7003 G_CALLBACK(compose_header_drag_received_cb),
7005 g_signal_connect(G_OBJECT(entry), "drag-drop",
7006 G_CALLBACK(compose_drag_drop),
7008 g_signal_connect(G_OBJECT(entry), "populate-popup",
7009 G_CALLBACK(compose_entry_popup_extend),
7012 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7014 headerentry->compose = compose;
7015 headerentry->combo = combo;
7016 headerentry->entry = entry;
7017 headerentry->button = button;
7018 headerentry->hbox = hbox;
7019 headerentry->headernum = compose->header_nextrow;
7020 headerentry->type = PREF_NONE;
7022 compose->header_nextrow++;
7023 compose->header_last = headerentry;
7024 compose->header_list =
7025 g_slist_append(compose->header_list,
7029 static void compose_add_header_entry(Compose *compose, const gchar *header,
7030 gchar *text, ComposePrefType pref_type)
7032 ComposeHeaderEntry *last_header = compose->header_last;
7033 gchar *tmp = g_strdup(text), *email;
7034 gboolean replyto_hdr;
7036 replyto_hdr = (!strcasecmp(header,
7037 prefs_common_translated_header_name("Reply-To:")) ||
7039 prefs_common_translated_header_name("Followup-To:")) ||
7041 prefs_common_translated_header_name("In-Reply-To:")));
7043 extract_address(tmp);
7044 email = g_utf8_strdown(tmp, -1);
7046 if (replyto_hdr == FALSE &&
7047 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7049 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7050 header, text, (gint) pref_type);
7056 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7057 gtk_entry_set_text(GTK_ENTRY(
7058 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7060 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7061 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7062 last_header->type = pref_type;
7064 if (replyto_hdr == FALSE)
7065 g_hash_table_insert(compose->email_hashtable, email,
7066 GUINT_TO_POINTER(1));
7073 static void compose_destroy_headerentry(Compose *compose,
7074 ComposeHeaderEntry *headerentry)
7076 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7079 extract_address(text);
7080 email = g_utf8_strdown(text, -1);
7081 g_hash_table_remove(compose->email_hashtable, email);
7085 gtk_widget_destroy(headerentry->combo);
7086 gtk_widget_destroy(headerentry->entry);
7087 gtk_widget_destroy(headerentry->button);
7088 gtk_widget_destroy(headerentry->hbox);
7089 g_free(headerentry);
7092 static void compose_remove_header_entries(Compose *compose)
7095 for (list = compose->header_list; list; list = list->next)
7096 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7098 compose->header_last = NULL;
7099 g_slist_free(compose->header_list);
7100 compose->header_list = NULL;
7101 compose->header_nextrow = 1;
7102 compose_create_header_entry(compose);
7105 static GtkWidget *compose_create_header(Compose *compose)
7107 GtkWidget *from_optmenu_hbox;
7108 GtkWidget *header_table_main;
7109 GtkWidget *header_scrolledwin;
7110 GtkWidget *header_table;
7112 /* parent with account selection and from header */
7113 header_table_main = gtk_table_new(2, 2, FALSE);
7114 gtk_widget_show(header_table_main);
7115 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7117 from_optmenu_hbox = compose_account_option_menu_create(compose);
7118 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7119 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7121 /* child with header labels and entries */
7122 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7123 gtk_widget_show(header_scrolledwin);
7124 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7126 header_table = gtk_table_new(2, 2, FALSE);
7127 gtk_widget_show(header_table);
7128 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7129 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7130 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7131 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7132 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7134 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7135 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7137 compose->header_table = header_table;
7138 compose->header_list = NULL;
7139 compose->header_nextrow = 0;
7141 compose_create_header_entry(compose);
7143 compose->table = NULL;
7145 return header_table_main;
7148 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7150 Compose *compose = (Compose *)data;
7151 GdkEventButton event;
7154 event.time = gtk_get_current_event_time();
7156 return attach_button_pressed(compose->attach_clist, &event, compose);
7159 static GtkWidget *compose_create_attach(Compose *compose)
7161 GtkWidget *attach_scrwin;
7162 GtkWidget *attach_clist;
7164 GtkListStore *store;
7165 GtkCellRenderer *renderer;
7166 GtkTreeViewColumn *column;
7167 GtkTreeSelection *selection;
7169 /* attachment list */
7170 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7171 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7172 GTK_POLICY_AUTOMATIC,
7173 GTK_POLICY_AUTOMATIC);
7174 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7176 store = gtk_list_store_new(N_ATTACH_COLS,
7182 G_TYPE_AUTO_POINTER,
7184 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7185 (GTK_TREE_MODEL(store)));
7186 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7187 g_object_unref(store);
7189 renderer = gtk_cell_renderer_text_new();
7190 column = gtk_tree_view_column_new_with_attributes
7191 (_("Mime type"), renderer, "text",
7192 COL_MIMETYPE, NULL);
7193 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7195 renderer = gtk_cell_renderer_text_new();
7196 column = gtk_tree_view_column_new_with_attributes
7197 (_("Size"), renderer, "text",
7199 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7201 renderer = gtk_cell_renderer_text_new();
7202 column = gtk_tree_view_column_new_with_attributes
7203 (_("Name"), renderer, "text",
7205 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7207 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7208 prefs_common.use_stripes_everywhere);
7209 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7210 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7212 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7213 G_CALLBACK(attach_selected), compose);
7214 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7215 G_CALLBACK(attach_button_pressed), compose);
7216 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7217 G_CALLBACK(popup_attach_button_pressed), compose);
7218 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7219 G_CALLBACK(attach_key_pressed), compose);
7222 gtk_drag_dest_set(attach_clist,
7223 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7224 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7225 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7226 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7227 G_CALLBACK(compose_attach_drag_received_cb),
7229 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7230 G_CALLBACK(compose_drag_drop),
7233 compose->attach_scrwin = attach_scrwin;
7234 compose->attach_clist = attach_clist;
7236 return attach_scrwin;
7239 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7240 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7242 static GtkWidget *compose_create_others(Compose *compose)
7245 GtkWidget *savemsg_checkbtn;
7246 GtkWidget *savemsg_combo;
7247 GtkWidget *savemsg_select;
7250 gchar *folderidentifier;
7252 /* Table for settings */
7253 table = gtk_table_new(3, 1, FALSE);
7254 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7255 gtk_widget_show(table);
7256 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7259 /* Save Message to folder */
7260 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7261 gtk_widget_show(savemsg_checkbtn);
7262 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7263 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7264 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7266 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7267 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7269 #if !GTK_CHECK_VERSION(2, 24, 0)
7270 savemsg_combo = gtk_combo_box_entry_new_text();
7272 savemsg_combo = gtk_combo_box_text_new_with_entry();
7274 compose->savemsg_checkbtn = savemsg_checkbtn;
7275 compose->savemsg_combo = savemsg_combo;
7276 gtk_widget_show(savemsg_combo);
7278 if (prefs_common.compose_save_to_history)
7279 #if !GTK_CHECK_VERSION(2, 24, 0)
7280 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7281 prefs_common.compose_save_to_history);
7283 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7284 prefs_common.compose_save_to_history);
7286 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7287 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7288 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7289 G_CALLBACK(compose_grab_focus_cb), compose);
7290 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7291 folderidentifier = folder_item_get_identifier(account_get_special_folder
7292 (compose->account, F_OUTBOX));
7293 compose_set_save_to(compose, folderidentifier);
7294 g_free(folderidentifier);
7297 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7298 gtk_widget_show(savemsg_select);
7299 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7300 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7301 G_CALLBACK(compose_savemsg_select_cb),
7307 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7309 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7310 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7313 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7318 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7321 path = folder_item_get_identifier(dest);
7323 compose_set_save_to(compose, path);
7327 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7328 GdkAtom clip, GtkTextIter *insert_place);
7331 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7335 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7337 if (event->button == 3) {
7339 GtkTextIter sel_start, sel_end;
7340 gboolean stuff_selected;
7342 /* move the cursor to allow GtkAspell to check the word
7343 * under the mouse */
7344 if (event->x && event->y) {
7345 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7346 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7348 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7351 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7352 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7355 stuff_selected = gtk_text_buffer_get_selection_bounds(
7357 &sel_start, &sel_end);
7359 gtk_text_buffer_place_cursor (buffer, &iter);
7360 /* reselect stuff */
7362 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7363 gtk_text_buffer_select_range(buffer,
7364 &sel_start, &sel_end);
7366 return FALSE; /* pass the event so that the right-click goes through */
7369 if (event->button == 2) {
7374 /* get the middle-click position to paste at the correct place */
7375 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7376 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7378 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7381 entry_paste_clipboard(compose, text,
7382 prefs_common.linewrap_pastes,
7383 GDK_SELECTION_PRIMARY, &iter);
7391 static void compose_spell_menu_changed(void *data)
7393 Compose *compose = (Compose *)data;
7395 GtkWidget *menuitem;
7396 GtkWidget *parent_item;
7397 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7400 if (compose->gtkaspell == NULL)
7403 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7404 "/Menu/Spelling/Options");
7406 /* setting the submenu removes /Spelling/Options from the factory
7407 * so we need to save it */
7409 if (parent_item == NULL) {
7410 parent_item = compose->aspell_options_menu;
7411 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7413 compose->aspell_options_menu = parent_item;
7415 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7417 spell_menu = g_slist_reverse(spell_menu);
7418 for (items = spell_menu;
7419 items; items = items->next) {
7420 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7421 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7422 gtk_widget_show(GTK_WIDGET(menuitem));
7424 g_slist_free(spell_menu);
7426 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7427 gtk_widget_show(parent_item);
7430 static void compose_dict_changed(void *data)
7432 Compose *compose = (Compose *) data;
7434 if(!compose->gtkaspell)
7436 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7439 gtkaspell_highlight_all(compose->gtkaspell);
7440 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7444 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7446 Compose *compose = (Compose *)data;
7447 GdkEventButton event;
7450 event.time = gtk_get_current_event_time();
7454 return text_clicked(compose->text, &event, compose);
7457 static gboolean compose_force_window_origin = TRUE;
7458 static Compose *compose_create(PrefsAccount *account,
7467 GtkWidget *handlebox;
7469 GtkWidget *notebook;
7471 GtkWidget *attach_hbox;
7472 GtkWidget *attach_lab1;
7473 GtkWidget *attach_lab2;
7478 GtkWidget *subject_hbox;
7479 GtkWidget *subject_frame;
7480 GtkWidget *subject_entry;
7484 GtkWidget *edit_vbox;
7485 GtkWidget *ruler_hbox;
7487 GtkWidget *scrolledwin;
7489 GtkTextBuffer *buffer;
7490 GtkClipboard *clipboard;
7492 UndoMain *undostruct;
7494 GtkWidget *popupmenu;
7495 GtkWidget *tmpl_menu;
7496 GtkActionGroup *action_group = NULL;
7499 GtkAspell * gtkaspell = NULL;
7502 static GdkGeometry geometry;
7504 cm_return_val_if_fail(account != NULL, NULL);
7506 gtkut_convert_int_to_gdk_color(prefs_common.default_header_bgcolor,
7507 &default_header_bgcolor);
7508 gtkut_convert_int_to_gdk_color(prefs_common.default_header_color,
7509 &default_header_color);
7511 debug_print("Creating compose window...\n");
7512 compose = g_new0(Compose, 1);
7514 compose->batch = batch;
7515 compose->account = account;
7516 compose->folder = folder;
7518 compose->mutex = cm_mutex_new();
7519 compose->set_cursor_pos = -1;
7521 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7523 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7524 gtk_widget_set_size_request(window, prefs_common.compose_width,
7525 prefs_common.compose_height);
7527 if (!geometry.max_width) {
7528 geometry.max_width = gdk_screen_width();
7529 geometry.max_height = gdk_screen_height();
7532 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7533 &geometry, GDK_HINT_MAX_SIZE);
7534 if (!geometry.min_width) {
7535 geometry.min_width = 600;
7536 geometry.min_height = 440;
7538 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7539 &geometry, GDK_HINT_MIN_SIZE);
7541 #ifndef GENERIC_UMPC
7542 if (compose_force_window_origin)
7543 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7544 prefs_common.compose_y);
7546 g_signal_connect(G_OBJECT(window), "delete_event",
7547 G_CALLBACK(compose_delete_cb), compose);
7548 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7549 gtk_widget_realize(window);
7551 gtkut_widget_set_composer_icon(window);
7553 vbox = gtk_vbox_new(FALSE, 0);
7554 gtk_container_add(GTK_CONTAINER(window), vbox);
7556 compose->ui_manager = gtk_ui_manager_new();
7557 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7558 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7559 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7560 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7561 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7562 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7563 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7564 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7565 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7566 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7685 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)
7686 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)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7691 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7692 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)
7693 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)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7698 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)
7699 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7701 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7702 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)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7708 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)
7709 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7710 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7712 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7714 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7715 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)
7716 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)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7718 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7729 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)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7742 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7745 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7747 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7748 gtk_widget_show_all(menubar);
7750 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7751 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7753 if (prefs_common.toolbar_detachable) {
7754 handlebox = gtk_handle_box_new();
7756 handlebox = gtk_hbox_new(FALSE, 0);
7758 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7760 gtk_widget_realize(handlebox);
7761 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7764 vbox2 = gtk_vbox_new(FALSE, 2);
7765 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7766 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7769 notebook = gtk_notebook_new();
7770 gtk_widget_show(notebook);
7772 /* header labels and entries */
7773 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7774 compose_create_header(compose),
7775 gtk_label_new_with_mnemonic(_("Hea_der")));
7776 /* attachment list */
7777 attach_hbox = gtk_hbox_new(FALSE, 0);
7778 gtk_widget_show(attach_hbox);
7780 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7781 gtk_widget_show(attach_lab1);
7782 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7784 attach_lab2 = gtk_label_new("");
7785 gtk_widget_show(attach_lab2);
7786 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7788 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7789 compose_create_attach(compose),
7792 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7793 compose_create_others(compose),
7794 gtk_label_new_with_mnemonic(_("Othe_rs")));
7797 subject_hbox = gtk_hbox_new(FALSE, 0);
7798 gtk_widget_show(subject_hbox);
7800 subject_frame = gtk_frame_new(NULL);
7801 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7802 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7803 gtk_widget_show(subject_frame);
7805 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7806 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7807 gtk_widget_show(subject);
7809 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7810 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7811 gtk_widget_show(label);
7814 subject_entry = claws_spell_entry_new();
7816 subject_entry = gtk_entry_new();
7818 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7819 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7820 G_CALLBACK(compose_grab_focus_cb), compose);
7821 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7822 gtk_widget_show(subject_entry);
7823 compose->subject_entry = subject_entry;
7824 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7826 edit_vbox = gtk_vbox_new(FALSE, 0);
7828 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7831 ruler_hbox = gtk_hbox_new(FALSE, 0);
7832 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7834 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7835 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7836 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7840 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7841 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7842 GTK_POLICY_AUTOMATIC,
7843 GTK_POLICY_AUTOMATIC);
7844 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7846 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7848 text = gtk_text_view_new();
7849 if (prefs_common.show_compose_margin) {
7850 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7851 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7853 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7854 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7855 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7856 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7857 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7859 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7860 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7861 G_CALLBACK(compose_edit_size_alloc),
7863 g_signal_connect(G_OBJECT(buffer), "changed",
7864 G_CALLBACK(compose_changed_cb), compose);
7865 g_signal_connect(G_OBJECT(text), "grab_focus",
7866 G_CALLBACK(compose_grab_focus_cb), compose);
7867 g_signal_connect(G_OBJECT(buffer), "insert_text",
7868 G_CALLBACK(text_inserted), compose);
7869 g_signal_connect(G_OBJECT(text), "button_press_event",
7870 G_CALLBACK(text_clicked), compose);
7871 g_signal_connect(G_OBJECT(text), "popup-menu",
7872 G_CALLBACK(compose_popup_menu), compose);
7873 g_signal_connect(G_OBJECT(subject_entry), "changed",
7874 G_CALLBACK(compose_changed_cb), compose);
7875 g_signal_connect(G_OBJECT(subject_entry), "activate",
7876 G_CALLBACK(compose_subject_entry_activated), compose);
7879 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7880 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7881 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7882 g_signal_connect(G_OBJECT(text), "drag_data_received",
7883 G_CALLBACK(compose_insert_drag_received_cb),
7885 g_signal_connect(G_OBJECT(text), "drag-drop",
7886 G_CALLBACK(compose_drag_drop),
7888 g_signal_connect(G_OBJECT(text), "key-press-event",
7889 G_CALLBACK(completion_set_focus_to_subject),
7891 gtk_widget_show_all(vbox);
7893 /* pane between attach clist and text */
7894 paned = gtk_vpaned_new();
7895 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7896 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7897 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7898 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7899 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7900 G_CALLBACK(compose_notebook_size_alloc), paned);
7902 gtk_widget_show_all(paned);
7905 if (prefs_common.textfont) {
7906 PangoFontDescription *font_desc;
7908 font_desc = pango_font_description_from_string
7909 (prefs_common.textfont);
7911 gtk_widget_modify_font(text, font_desc);
7912 pango_font_description_free(font_desc);
7916 gtk_action_group_add_actions(action_group, compose_popup_entries,
7917 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7918 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7919 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7920 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7921 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7922 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7923 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7925 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7927 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7928 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7929 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7931 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7933 undostruct = undo_init(text);
7934 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7937 address_completion_start(window);
7939 compose->window = window;
7940 compose->vbox = vbox;
7941 compose->menubar = menubar;
7942 compose->handlebox = handlebox;
7944 compose->vbox2 = vbox2;
7946 compose->paned = paned;
7948 compose->attach_label = attach_lab2;
7950 compose->notebook = notebook;
7951 compose->edit_vbox = edit_vbox;
7952 compose->ruler_hbox = ruler_hbox;
7953 compose->ruler = ruler;
7954 compose->scrolledwin = scrolledwin;
7955 compose->text = text;
7957 compose->focused_editable = NULL;
7959 compose->popupmenu = popupmenu;
7961 compose->tmpl_menu = tmpl_menu;
7963 compose->mode = mode;
7964 compose->rmode = mode;
7966 compose->targetinfo = NULL;
7967 compose->replyinfo = NULL;
7968 compose->fwdinfo = NULL;
7970 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7971 g_str_equal, (GDestroyNotify) g_free, NULL);
7973 compose->replyto = NULL;
7975 compose->bcc = NULL;
7976 compose->followup_to = NULL;
7978 compose->ml_post = NULL;
7980 compose->inreplyto = NULL;
7981 compose->references = NULL;
7982 compose->msgid = NULL;
7983 compose->boundary = NULL;
7985 compose->autowrap = prefs_common.autowrap;
7986 compose->autoindent = prefs_common.auto_indent;
7987 compose->use_signing = FALSE;
7988 compose->use_encryption = FALSE;
7989 compose->privacy_system = NULL;
7990 compose->encdata = NULL;
7992 compose->modified = FALSE;
7994 compose->return_receipt = FALSE;
7996 compose->to_list = NULL;
7997 compose->newsgroup_list = NULL;
7999 compose->undostruct = undostruct;
8001 compose->sig_str = NULL;
8003 compose->exteditor_file = NULL;
8004 compose->exteditor_pid = -1;
8005 compose->exteditor_tag = -1;
8006 compose->exteditor_socket = NULL;
8007 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8009 compose->folder_update_callback_id =
8010 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8011 compose_update_folder_hook,
8012 (gpointer) compose);
8015 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8016 if (mode != COMPOSE_REDIRECT) {
8017 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8018 strcmp(prefs_common.dictionary, "")) {
8019 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8020 prefs_common.alt_dictionary,
8021 conv_get_locale_charset_str(),
8022 prefs_common.misspelled_col,
8023 prefs_common.check_while_typing,
8024 prefs_common.recheck_when_changing_dict,
8025 prefs_common.use_alternate,
8026 prefs_common.use_both_dicts,
8027 GTK_TEXT_VIEW(text),
8028 GTK_WINDOW(compose->window),
8029 compose_dict_changed,
8030 compose_spell_menu_changed,
8033 alertpanel_error(_("Spell checker could not "
8035 gtkaspell_checkers_strerror());
8036 gtkaspell_checkers_reset_error();
8038 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8042 compose->gtkaspell = gtkaspell;
8043 compose_spell_menu_changed(compose);
8044 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8047 compose_select_account(compose, account, TRUE);
8049 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8050 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8052 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8053 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8055 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8056 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8058 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8059 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8061 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8062 if (account->protocol != A_NNTP)
8063 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8064 prefs_common_translated_header_name("To:"));
8066 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8067 prefs_common_translated_header_name("Newsgroups:"));
8069 #ifndef USE_ALT_ADDRBOOK
8070 addressbook_set_target_compose(compose);
8072 if (mode != COMPOSE_REDIRECT)
8073 compose_set_template_menu(compose);
8075 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8078 compose_list = g_list_append(compose_list, compose);
8080 if (!prefs_common.show_ruler)
8081 gtk_widget_hide(ruler_hbox);
8083 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8086 compose->priority = PRIORITY_NORMAL;
8087 compose_update_priority_menu_item(compose);
8089 compose_set_out_encoding(compose);
8092 compose_update_actions_menu(compose);
8094 /* Privacy Systems menu */
8095 compose_update_privacy_systems_menu(compose);
8097 activate_privacy_system(compose, account, TRUE);
8098 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8100 gtk_widget_realize(window);
8102 gtk_widget_show(window);
8108 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8113 GtkWidget *optmenubox;
8114 GtkWidget *fromlabel;
8117 GtkWidget *from_name = NULL;
8119 gint num = 0, def_menu = 0;
8121 accounts = account_get_list();
8122 cm_return_val_if_fail(accounts != NULL, NULL);
8124 optmenubox = gtk_event_box_new();
8125 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8126 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8128 hbox = gtk_hbox_new(FALSE, 4);
8129 from_name = gtk_entry_new();
8131 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8132 G_CALLBACK(compose_grab_focus_cb), compose);
8133 g_signal_connect_after(G_OBJECT(from_name), "activate",
8134 G_CALLBACK(from_name_activate_cb), optmenu);
8136 for (; accounts != NULL; accounts = accounts->next, num++) {
8137 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8138 gchar *name, *from = NULL;
8140 if (ac == compose->account) def_menu = num;
8142 name = g_markup_printf_escaped("<i>%s</i>",
8145 if (ac == compose->account) {
8146 if (ac->name && *ac->name) {
8148 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8149 from = g_strdup_printf("%s <%s>",
8151 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8153 from = g_strdup_printf("%s",
8155 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8157 if (cur_account != compose->account) {
8158 gtk_widget_modify_base(
8159 GTK_WIDGET(from_name),
8160 GTK_STATE_NORMAL, &default_header_bgcolor);
8161 gtk_widget_modify_text(
8162 GTK_WIDGET(from_name),
8163 GTK_STATE_NORMAL, &default_header_color);
8166 COMBOBOX_ADD(menu, name, ac->account_id);
8171 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8173 g_signal_connect(G_OBJECT(optmenu), "changed",
8174 G_CALLBACK(account_activated),
8176 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8177 G_CALLBACK(compose_entry_popup_extend),
8180 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8181 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8183 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8184 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8185 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8187 /* Putting only the GtkEntry into focus chain of parent hbox causes
8188 * the account selector combobox next to it to be unreachable when
8189 * navigating widgets in GtkTable with up/down arrow keys.
8190 * Note: gtk_widget_set_can_focus() was not enough. */
8192 l = g_list_prepend(l, from_name);
8193 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8196 CLAWS_SET_TIP(optmenubox,
8197 _("Account to use for this email"));
8198 CLAWS_SET_TIP(from_name,
8199 _("Sender address to be used"));
8201 compose->account_combo = optmenu;
8202 compose->from_name = from_name;
8207 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8209 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8210 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8211 Compose *compose = (Compose *) data;
8213 compose->priority = value;
8217 static void compose_reply_change_mode(Compose *compose,
8220 gboolean was_modified = compose->modified;
8222 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8224 cm_return_if_fail(compose->replyinfo != NULL);
8226 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8228 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8230 if (action == COMPOSE_REPLY_TO_ALL)
8232 if (action == COMPOSE_REPLY_TO_SENDER)
8234 if (action == COMPOSE_REPLY_TO_LIST)
8237 compose_remove_header_entries(compose);
8238 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8239 if (compose->account->set_autocc && compose->account->auto_cc)
8240 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8242 if (compose->account->set_autobcc && compose->account->auto_bcc)
8243 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8245 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8246 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8247 compose_show_first_last_header(compose, TRUE);
8248 compose->modified = was_modified;
8249 compose_set_title(compose);
8252 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8254 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8255 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8256 Compose *compose = (Compose *) data;
8259 compose_reply_change_mode(compose, value);
8262 static void compose_update_priority_menu_item(Compose * compose)
8264 GtkWidget *menuitem = NULL;
8265 switch (compose->priority) {
8266 case PRIORITY_HIGHEST:
8267 menuitem = gtk_ui_manager_get_widget
8268 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8271 menuitem = gtk_ui_manager_get_widget
8272 (compose->ui_manager, "/Menu/Options/Priority/High");
8274 case PRIORITY_NORMAL:
8275 menuitem = gtk_ui_manager_get_widget
8276 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8279 menuitem = gtk_ui_manager_get_widget
8280 (compose->ui_manager, "/Menu/Options/Priority/Low");
8282 case PRIORITY_LOWEST:
8283 menuitem = gtk_ui_manager_get_widget
8284 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8287 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8290 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8292 Compose *compose = (Compose *) data;
8294 gboolean can_sign = FALSE, can_encrypt = FALSE;
8296 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8298 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8301 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8302 g_free(compose->privacy_system);
8303 compose->privacy_system = NULL;
8304 g_free(compose->encdata);
8305 compose->encdata = NULL;
8306 if (systemid != NULL) {
8307 compose->privacy_system = g_strdup(systemid);
8309 can_sign = privacy_system_can_sign(systemid);
8310 can_encrypt = privacy_system_can_encrypt(systemid);
8313 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8315 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8316 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8319 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8321 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8322 GtkWidget *menuitem = NULL;
8323 GList *children, *amenu;
8324 gboolean can_sign = FALSE, can_encrypt = FALSE;
8325 gboolean found = FALSE;
8327 if (compose->privacy_system != NULL) {
8329 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8330 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8331 cm_return_if_fail(menuitem != NULL);
8333 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8336 while (amenu != NULL) {
8337 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8338 if (systemid != NULL) {
8339 if (strcmp(systemid, compose->privacy_system) == 0 &&
8340 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8341 menuitem = GTK_WIDGET(amenu->data);
8343 can_sign = privacy_system_can_sign(systemid);
8344 can_encrypt = privacy_system_can_encrypt(systemid);
8348 } else if (strlen(compose->privacy_system) == 0 &&
8349 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8350 menuitem = GTK_WIDGET(amenu->data);
8353 can_encrypt = FALSE;
8358 amenu = amenu->next;
8360 g_list_free(children);
8361 if (menuitem != NULL)
8362 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8364 if (warn && !found && strlen(compose->privacy_system)) {
8365 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8366 "will not be able to sign or encrypt this message."),
8367 compose->privacy_system);
8371 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8372 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8375 static void compose_set_out_encoding(Compose *compose)
8377 CharSet out_encoding;
8378 const gchar *branch = NULL;
8379 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8381 switch(out_encoding) {
8382 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8383 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8384 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8385 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8386 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8387 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8388 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8389 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8390 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8391 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8392 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8393 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8394 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8395 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8396 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8397 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8398 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8399 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8400 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8401 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8402 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8403 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8404 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8405 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8406 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8407 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8408 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8409 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8410 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8411 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8412 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8413 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8414 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8415 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8417 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8420 static void compose_set_template_menu(Compose *compose)
8422 GSList *tmpl_list, *cur;
8426 tmpl_list = template_get_config();
8428 menu = gtk_menu_new();
8430 gtk_menu_set_accel_group (GTK_MENU (menu),
8431 gtk_ui_manager_get_accel_group(compose->ui_manager));
8432 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8433 Template *tmpl = (Template *)cur->data;
8434 gchar *accel_path = NULL;
8435 item = gtk_menu_item_new_with_label(tmpl->name);
8436 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8437 g_signal_connect(G_OBJECT(item), "activate",
8438 G_CALLBACK(compose_template_activate_cb),
8440 g_object_set_data(G_OBJECT(item), "template", tmpl);
8441 gtk_widget_show(item);
8442 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8443 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8447 gtk_widget_show(menu);
8448 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8451 void compose_update_actions_menu(Compose *compose)
8453 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8456 static void compose_update_privacy_systems_menu(Compose *compose)
8458 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8459 GSList *systems, *cur;
8461 GtkWidget *system_none;
8463 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8464 GtkWidget *privacy_menu = gtk_menu_new();
8466 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8467 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8469 g_signal_connect(G_OBJECT(system_none), "activate",
8470 G_CALLBACK(compose_set_privacy_system_cb), compose);
8472 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8473 gtk_widget_show(system_none);
8475 systems = privacy_get_system_ids();
8476 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8477 gchar *systemid = cur->data;
8479 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8480 widget = gtk_radio_menu_item_new_with_label(group,
8481 privacy_system_get_name(systemid));
8482 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8483 g_strdup(systemid), g_free);
8484 g_signal_connect(G_OBJECT(widget), "activate",
8485 G_CALLBACK(compose_set_privacy_system_cb), compose);
8487 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8488 gtk_widget_show(widget);
8491 g_slist_free(systems);
8492 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8493 gtk_widget_show_all(privacy_menu);
8494 gtk_widget_show_all(privacy_menuitem);
8497 void compose_reflect_prefs_all(void)
8502 for (cur = compose_list; cur != NULL; cur = cur->next) {
8503 compose = (Compose *)cur->data;
8504 compose_set_template_menu(compose);
8508 void compose_reflect_prefs_pixmap_theme(void)
8513 for (cur = compose_list; cur != NULL; cur = cur->next) {
8514 compose = (Compose *)cur->data;
8515 toolbar_update(TOOLBAR_COMPOSE, compose);
8519 static const gchar *compose_quote_char_from_context(Compose *compose)
8521 const gchar *qmark = NULL;
8523 cm_return_val_if_fail(compose != NULL, NULL);
8525 switch (compose->mode) {
8526 /* use forward-specific quote char */
8527 case COMPOSE_FORWARD:
8528 case COMPOSE_FORWARD_AS_ATTACH:
8529 case COMPOSE_FORWARD_INLINE:
8530 if (compose->folder && compose->folder->prefs &&
8531 compose->folder->prefs->forward_with_format)
8532 qmark = compose->folder->prefs->forward_quotemark;
8533 else if (compose->account->forward_with_format)
8534 qmark = compose->account->forward_quotemark;
8536 qmark = prefs_common.fw_quotemark;
8539 /* use reply-specific quote char in all other modes */
8541 if (compose->folder && compose->folder->prefs &&
8542 compose->folder->prefs->reply_with_format)
8543 qmark = compose->folder->prefs->reply_quotemark;
8544 else if (compose->account->reply_with_format)
8545 qmark = compose->account->reply_quotemark;
8547 qmark = prefs_common.quotemark;
8551 if (qmark == NULL || *qmark == '\0')
8557 static void compose_template_apply(Compose *compose, Template *tmpl,
8561 GtkTextBuffer *buffer;
8565 gchar *parsed_str = NULL;
8566 gint cursor_pos = 0;
8567 const gchar *err_msg = _("The body of the template has an error at line %d.");
8570 /* process the body */
8572 text = GTK_TEXT_VIEW(compose->text);
8573 buffer = gtk_text_view_get_buffer(text);
8576 qmark = compose_quote_char_from_context(compose);
8578 if (compose->replyinfo != NULL) {
8581 gtk_text_buffer_set_text(buffer, "", -1);
8582 mark = gtk_text_buffer_get_insert(buffer);
8583 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8585 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8586 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8588 } else if (compose->fwdinfo != NULL) {
8591 gtk_text_buffer_set_text(buffer, "", -1);
8592 mark = gtk_text_buffer_get_insert(buffer);
8593 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8595 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8596 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8599 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8601 GtkTextIter start, end;
8604 gtk_text_buffer_get_start_iter(buffer, &start);
8605 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8606 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8608 /* clear the buffer now */
8610 gtk_text_buffer_set_text(buffer, "", -1);
8612 parsed_str = compose_quote_fmt(compose, dummyinfo,
8613 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8614 procmsg_msginfo_free( &dummyinfo );
8620 gtk_text_buffer_set_text(buffer, "", -1);
8621 mark = gtk_text_buffer_get_insert(buffer);
8622 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8625 if (replace && parsed_str && compose->account->auto_sig)
8626 compose_insert_sig(compose, FALSE);
8628 if (replace && parsed_str) {
8629 gtk_text_buffer_get_start_iter(buffer, &iter);
8630 gtk_text_buffer_place_cursor(buffer, &iter);
8634 cursor_pos = quote_fmt_get_cursor_pos();
8635 compose->set_cursor_pos = cursor_pos;
8636 if (cursor_pos == -1)
8638 gtk_text_buffer_get_start_iter(buffer, &iter);
8639 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8640 gtk_text_buffer_place_cursor(buffer, &iter);
8643 /* process the other fields */
8645 compose_template_apply_fields(compose, tmpl);
8646 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8647 quote_fmt_reset_vartable();
8648 compose_changed_cb(NULL, compose);
8651 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8652 gtkaspell_highlight_all(compose->gtkaspell);
8656 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8658 MsgInfo* dummyinfo = NULL;
8659 MsgInfo *msginfo = NULL;
8662 if (compose->replyinfo != NULL)
8663 msginfo = compose->replyinfo;
8664 else if (compose->fwdinfo != NULL)
8665 msginfo = compose->fwdinfo;
8667 dummyinfo = compose_msginfo_new_from_compose(compose);
8668 msginfo = dummyinfo;
8671 if (tmpl->from && *tmpl->from != '\0') {
8673 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8674 compose->gtkaspell);
8676 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8678 quote_fmt_scan_string(tmpl->from);
8681 buf = quote_fmt_get_buffer();
8683 alertpanel_error(_("Template From format error."));
8685 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8689 if (tmpl->to && *tmpl->to != '\0') {
8691 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8692 compose->gtkaspell);
8694 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8696 quote_fmt_scan_string(tmpl->to);
8699 buf = quote_fmt_get_buffer();
8701 alertpanel_error(_("Template To format error."));
8703 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8707 if (tmpl->cc && *tmpl->cc != '\0') {
8709 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8710 compose->gtkaspell);
8712 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8714 quote_fmt_scan_string(tmpl->cc);
8717 buf = quote_fmt_get_buffer();
8719 alertpanel_error(_("Template Cc format error."));
8721 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8725 if (tmpl->bcc && *tmpl->bcc != '\0') {
8727 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8728 compose->gtkaspell);
8730 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8732 quote_fmt_scan_string(tmpl->bcc);
8735 buf = quote_fmt_get_buffer();
8737 alertpanel_error(_("Template Bcc format error."));
8739 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8743 if (tmpl->replyto && *tmpl->replyto != '\0') {
8745 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8746 compose->gtkaspell);
8748 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8750 quote_fmt_scan_string(tmpl->replyto);
8753 buf = quote_fmt_get_buffer();
8755 alertpanel_error(_("Template Reply-To format error."));
8757 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8761 /* process the subject */
8762 if (tmpl->subject && *tmpl->subject != '\0') {
8764 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8765 compose->gtkaspell);
8767 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8769 quote_fmt_scan_string(tmpl->subject);
8772 buf = quote_fmt_get_buffer();
8774 alertpanel_error(_("Template subject format error."));
8776 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8780 procmsg_msginfo_free( &dummyinfo );
8783 static void compose_destroy(Compose *compose)
8785 GtkAllocation allocation;
8786 GtkTextBuffer *buffer;
8787 GtkClipboard *clipboard;
8789 compose_list = g_list_remove(compose_list, compose);
8791 if (compose->updating) {
8792 debug_print("danger, not destroying anything now\n");
8793 compose->deferred_destroy = TRUE;
8797 /* NOTE: address_completion_end() does nothing with the window
8798 * however this may change. */
8799 address_completion_end(compose->window);
8801 slist_free_strings_full(compose->to_list);
8802 slist_free_strings_full(compose->newsgroup_list);
8803 slist_free_strings_full(compose->header_list);
8805 slist_free_strings_full(extra_headers);
8806 extra_headers = NULL;
8808 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8810 g_hash_table_destroy(compose->email_hashtable);
8812 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8813 compose->folder_update_callback_id);
8815 procmsg_msginfo_free(&(compose->targetinfo));
8816 procmsg_msginfo_free(&(compose->replyinfo));
8817 procmsg_msginfo_free(&(compose->fwdinfo));
8819 g_free(compose->replyto);
8820 g_free(compose->cc);
8821 g_free(compose->bcc);
8822 g_free(compose->newsgroups);
8823 g_free(compose->followup_to);
8825 g_free(compose->ml_post);
8827 g_free(compose->inreplyto);
8828 g_free(compose->references);
8829 g_free(compose->msgid);
8830 g_free(compose->boundary);
8832 g_free(compose->redirect_filename);
8833 if (compose->undostruct)
8834 undo_destroy(compose->undostruct);
8836 g_free(compose->sig_str);
8838 g_free(compose->exteditor_file);
8840 g_free(compose->orig_charset);
8842 g_free(compose->privacy_system);
8843 g_free(compose->encdata);
8845 #ifndef USE_ALT_ADDRBOOK
8846 if (addressbook_get_target_compose() == compose)
8847 addressbook_set_target_compose(NULL);
8850 if (compose->gtkaspell) {
8851 gtkaspell_delete(compose->gtkaspell);
8852 compose->gtkaspell = NULL;
8856 if (!compose->batch) {
8857 gtk_widget_get_allocation(compose->window, &allocation);
8858 prefs_common.compose_width = allocation.width;
8859 prefs_common.compose_height = allocation.height;
8862 if (!gtk_widget_get_parent(compose->paned))
8863 gtk_widget_destroy(compose->paned);
8864 gtk_widget_destroy(compose->popupmenu);
8866 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8867 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8868 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8870 gtk_widget_destroy(compose->window);
8871 toolbar_destroy(compose->toolbar);
8872 g_free(compose->toolbar);
8873 cm_mutex_free(compose->mutex);
8877 static void compose_attach_info_free(AttachInfo *ainfo)
8879 g_free(ainfo->file);
8880 g_free(ainfo->content_type);
8881 g_free(ainfo->name);
8882 g_free(ainfo->charset);
8886 static void compose_attach_update_label(Compose *compose)
8891 GtkTreeModel *model;
8896 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8897 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8898 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8902 while(gtk_tree_model_iter_next(model, &iter))
8905 text = g_strdup_printf("(%d)", i);
8906 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8910 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8912 Compose *compose = (Compose *)data;
8913 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8914 GtkTreeSelection *selection;
8916 GtkTreeModel *model;
8918 selection = gtk_tree_view_get_selection(tree_view);
8919 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8924 for (cur = sel; cur != NULL; cur = cur->next) {
8925 GtkTreePath *path = cur->data;
8926 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8929 gtk_tree_path_free(path);
8932 for (cur = sel; cur != NULL; cur = cur->next) {
8933 GtkTreeRowReference *ref = cur->data;
8934 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8937 if (gtk_tree_model_get_iter(model, &iter, path))
8938 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8940 gtk_tree_path_free(path);
8941 gtk_tree_row_reference_free(ref);
8945 compose_attach_update_label(compose);
8948 static struct _AttachProperty
8951 GtkWidget *mimetype_entry;
8952 GtkWidget *encoding_optmenu;
8953 GtkWidget *path_entry;
8954 GtkWidget *filename_entry;
8956 GtkWidget *cancel_btn;
8959 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8961 gtk_tree_path_free((GtkTreePath *)ptr);
8964 static void compose_attach_property(GtkAction *action, gpointer data)
8966 Compose *compose = (Compose *)data;
8967 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8969 GtkComboBox *optmenu;
8970 GtkTreeSelection *selection;
8972 GtkTreeModel *model;
8975 static gboolean cancelled;
8977 /* only if one selected */
8978 selection = gtk_tree_view_get_selection(tree_view);
8979 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8982 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8986 path = (GtkTreePath *) sel->data;
8987 gtk_tree_model_get_iter(model, &iter, path);
8988 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8991 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8997 if (!attach_prop.window)
8998 compose_attach_property_create(&cancelled);
8999 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9000 gtk_widget_grab_focus(attach_prop.ok_btn);
9001 gtk_widget_show(attach_prop.window);
9002 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9003 GTK_WINDOW(compose->window));
9005 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9006 if (ainfo->encoding == ENC_UNKNOWN)
9007 combobox_select_by_data(optmenu, ENC_BASE64);
9009 combobox_select_by_data(optmenu, ainfo->encoding);
9011 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9012 ainfo->content_type ? ainfo->content_type : "");
9013 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9014 ainfo->file ? ainfo->file : "");
9015 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9016 ainfo->name ? ainfo->name : "");
9019 const gchar *entry_text;
9021 gchar *cnttype = NULL;
9028 gtk_widget_hide(attach_prop.window);
9029 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9034 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9035 if (*entry_text != '\0') {
9038 text = g_strstrip(g_strdup(entry_text));
9039 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9040 cnttype = g_strdup(text);
9043 alertpanel_error(_("Invalid MIME type."));
9049 ainfo->encoding = combobox_get_active_data(optmenu);
9051 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9052 if (*entry_text != '\0') {
9053 if (is_file_exist(entry_text) &&
9054 (size = get_file_size(entry_text)) > 0)
9055 file = g_strdup(entry_text);
9058 (_("File doesn't exist or is empty."));
9064 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9065 if (*entry_text != '\0') {
9066 g_free(ainfo->name);
9067 ainfo->name = g_strdup(entry_text);
9071 g_free(ainfo->content_type);
9072 ainfo->content_type = cnttype;
9075 g_free(ainfo->file);
9079 ainfo->size = (goffset)size;
9081 /* update tree store */
9082 text = to_human_readable(ainfo->size);
9083 gtk_tree_model_get_iter(model, &iter, path);
9084 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9085 COL_MIMETYPE, ainfo->content_type,
9087 COL_NAME, ainfo->name,
9088 COL_CHARSET, ainfo->charset,
9094 gtk_tree_path_free(path);
9097 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9099 label = gtk_label_new(str); \
9100 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9101 GTK_FILL, 0, 0, 0); \
9102 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9104 entry = gtk_entry_new(); \
9105 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9106 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9109 static void compose_attach_property_create(gboolean *cancelled)
9115 GtkWidget *mimetype_entry;
9118 GtkListStore *optmenu_menu;
9119 GtkWidget *path_entry;
9120 GtkWidget *filename_entry;
9123 GtkWidget *cancel_btn;
9124 GList *mime_type_list, *strlist;
9127 debug_print("Creating attach_property window...\n");
9129 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9130 gtk_widget_set_size_request(window, 480, -1);
9131 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9132 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9133 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9134 g_signal_connect(G_OBJECT(window), "delete_event",
9135 G_CALLBACK(attach_property_delete_event),
9137 g_signal_connect(G_OBJECT(window), "key_press_event",
9138 G_CALLBACK(attach_property_key_pressed),
9141 vbox = gtk_vbox_new(FALSE, 8);
9142 gtk_container_add(GTK_CONTAINER(window), vbox);
9144 table = gtk_table_new(4, 2, FALSE);
9145 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9146 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9147 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9149 label = gtk_label_new(_("MIME type"));
9150 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9152 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9153 #if !GTK_CHECK_VERSION(2, 24, 0)
9154 mimetype_entry = gtk_combo_box_entry_new_text();
9156 mimetype_entry = gtk_combo_box_text_new_with_entry();
9158 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9159 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9161 /* stuff with list */
9162 mime_type_list = procmime_get_mime_type_list();
9164 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9165 MimeType *type = (MimeType *) mime_type_list->data;
9168 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9170 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9173 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9174 (GCompareFunc)strcmp2);
9177 for (mime_type_list = strlist; mime_type_list != NULL;
9178 mime_type_list = mime_type_list->next) {
9179 #if !GTK_CHECK_VERSION(2, 24, 0)
9180 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9182 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9184 g_free(mime_type_list->data);
9186 g_list_free(strlist);
9187 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9188 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9190 label = gtk_label_new(_("Encoding"));
9191 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9193 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9195 hbox = gtk_hbox_new(FALSE, 0);
9196 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9197 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9199 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9200 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9202 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9203 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9204 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9205 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9206 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9208 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9210 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9211 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9213 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9214 &ok_btn, GTK_STOCK_OK,
9216 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9217 gtk_widget_grab_default(ok_btn);
9219 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9220 G_CALLBACK(attach_property_ok),
9222 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9223 G_CALLBACK(attach_property_cancel),
9226 gtk_widget_show_all(vbox);
9228 attach_prop.window = window;
9229 attach_prop.mimetype_entry = mimetype_entry;
9230 attach_prop.encoding_optmenu = optmenu;
9231 attach_prop.path_entry = path_entry;
9232 attach_prop.filename_entry = filename_entry;
9233 attach_prop.ok_btn = ok_btn;
9234 attach_prop.cancel_btn = cancel_btn;
9237 #undef SET_LABEL_AND_ENTRY
9239 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9245 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9251 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9252 gboolean *cancelled)
9260 static gboolean attach_property_key_pressed(GtkWidget *widget,
9262 gboolean *cancelled)
9264 if (event && event->keyval == GDK_KEY_Escape) {
9268 if (event && event->keyval == GDK_KEY_Return) {
9276 static void compose_exec_ext_editor(Compose *compose)
9281 GdkNativeWindow socket_wid = 0;
9285 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9286 G_DIR_SEPARATOR, compose);
9288 if (compose_get_ext_editor_uses_socket()) {
9289 /* Only allow one socket */
9290 if (compose->exteditor_socket != NULL) {
9291 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9292 /* Move the focus off of the socket */
9293 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9298 /* Create the receiving GtkSocket */
9299 socket = gtk_socket_new ();
9300 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9301 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9303 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9304 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9305 /* Realize the socket so that we can use its ID */
9306 gtk_widget_realize(socket);
9307 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9308 compose->exteditor_socket = socket;
9311 if (pipe(pipe_fds) < 0) {
9317 if ((pid = fork()) < 0) {
9324 /* close the write side of the pipe */
9327 compose->exteditor_file = g_strdup(tmp);
9328 compose->exteditor_pid = pid;
9330 compose_set_ext_editor_sensitive(compose, FALSE);
9333 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9335 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9337 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9341 } else { /* process-monitoring process */
9347 /* close the read side of the pipe */
9350 if (compose_write_body_to_file(compose, tmp) < 0) {
9351 fd_write_all(pipe_fds[1], "2\n", 2);
9355 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9357 fd_write_all(pipe_fds[1], "1\n", 2);
9361 /* wait until editor is terminated */
9362 waitpid(pid_ed, NULL, 0);
9364 fd_write_all(pipe_fds[1], "0\n", 2);
9371 #endif /* G_OS_UNIX */
9375 static gboolean compose_get_ext_editor_cmd_valid()
9377 gboolean has_s = FALSE;
9378 gboolean has_w = FALSE;
9379 const gchar *p = prefs_common_get_ext_editor_cmd();
9382 while ((p = strchr(p, '%'))) {
9388 } else if (*p == 'w') {
9399 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9406 cm_return_val_if_fail(file != NULL, -1);
9408 if ((pid = fork()) < 0) {
9413 if (pid != 0) return pid;
9415 /* grandchild process */
9417 if (setpgid(0, getppid()))
9420 if (compose_get_ext_editor_cmd_valid()) {
9421 if (compose_get_ext_editor_uses_socket()) {
9422 p = g_strdup(prefs_common_get_ext_editor_cmd());
9423 s = strstr(p, "%w");
9425 if (strstr(p, "%s") < s)
9426 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9428 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9431 g_snprintf(buf, sizeof(buf),
9432 prefs_common_get_ext_editor_cmd(), file);
9435 if (prefs_common_get_ext_editor_cmd())
9436 g_warning("External editor command-line is invalid: '%s'",
9437 prefs_common_get_ext_editor_cmd());
9438 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9441 cmdline = strsplit_with_quote(buf, " ", 1024);
9442 execvp(cmdline[0], cmdline);
9445 g_strfreev(cmdline);
9450 static gboolean compose_ext_editor_kill(Compose *compose)
9452 pid_t pgid = compose->exteditor_pid * -1;
9455 ret = kill(pgid, 0);
9457 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9461 msg = g_strdup_printf
9462 (_("The external editor is still working.\n"
9463 "Force terminating the process?\n"
9464 "process group id: %d"), -pgid);
9465 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9466 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9470 if (val == G_ALERTALTERNATE) {
9471 g_source_remove(compose->exteditor_tag);
9472 g_io_channel_shutdown(compose->exteditor_ch,
9474 g_io_channel_unref(compose->exteditor_ch);
9476 if (kill(pgid, SIGTERM) < 0) perror("kill");
9477 waitpid(compose->exteditor_pid, NULL, 0);
9479 g_warning("Terminated process group id: %d. "
9480 "Temporary file: %s", -pgid, compose->exteditor_file);
9482 compose_set_ext_editor_sensitive(compose, TRUE);
9484 g_free(compose->exteditor_file);
9485 compose->exteditor_file = NULL;
9486 compose->exteditor_pid = -1;
9487 compose->exteditor_ch = NULL;
9488 compose->exteditor_tag = -1;
9496 static gboolean compose_can_autosave(Compose *compose)
9498 if (compose->privacy_system && compose->use_encryption)
9499 return prefs_common.autosave && prefs_common.autosave_encrypted;
9501 return prefs_common.autosave;
9504 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9508 Compose *compose = (Compose *)data;
9511 debug_print("Compose: input from monitoring process\n");
9513 if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
9518 g_io_channel_shutdown(source, FALSE, NULL);
9519 g_io_channel_unref(source);
9521 waitpid(compose->exteditor_pid, NULL, 0);
9523 if (buf[0] == '0') { /* success */
9524 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9525 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9526 GtkTextIter start, end;
9529 gtk_text_buffer_set_text(buffer, "", -1);
9530 compose_insert_file(compose, compose->exteditor_file);
9531 compose_changed_cb(NULL, compose);
9533 /* Check if we should save the draft or not */
9534 if (compose_can_autosave(compose))
9535 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9537 if (claws_unlink(compose->exteditor_file) < 0)
9538 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9540 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9541 gtk_text_buffer_get_start_iter(buffer, &start);
9542 gtk_text_buffer_get_end_iter(buffer, &end);
9543 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9544 if (chars && strlen(chars) > 0)
9545 compose->modified = TRUE;
9547 } else if (buf[0] == '1') { /* failed */
9548 g_warning("Couldn't exec external editor");
9549 if (claws_unlink(compose->exteditor_file) < 0)
9550 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9551 } else if (buf[0] == '2') {
9552 g_warning("Couldn't write to file");
9553 } else if (buf[0] == '3') {
9554 g_warning("Pipe read failed");
9557 compose_set_ext_editor_sensitive(compose, TRUE);
9559 g_free(compose->exteditor_file);
9560 compose->exteditor_file = NULL;
9561 compose->exteditor_pid = -1;
9562 compose->exteditor_ch = NULL;
9563 compose->exteditor_tag = -1;
9564 if (compose->exteditor_socket) {
9565 gtk_widget_destroy(compose->exteditor_socket);
9566 compose->exteditor_socket = NULL;
9573 static char *ext_editor_menu_entries[] = {
9574 "Menu/Message/Send",
9575 "Menu/Message/SendLater",
9576 "Menu/Message/InsertFile",
9577 "Menu/Message/InsertSig",
9578 "Menu/Message/ReplaceSig",
9579 "Menu/Message/Save",
9580 "Menu/Message/Print",
9585 "Menu/Tools/ShowRuler",
9586 "Menu/Tools/Actions",
9591 static void compose_set_ext_editor_sensitive(Compose *compose,
9596 for (i = 0; ext_editor_menu_entries[i]; ++i) {
9597 cm_menu_set_sensitive_full(compose->ui_manager,
9598 ext_editor_menu_entries[i], sensitive);
9601 if (compose_get_ext_editor_uses_socket()) {
9603 if (compose->exteditor_socket)
9604 gtk_widget_hide(compose->exteditor_socket);
9605 gtk_widget_show(compose->scrolledwin);
9606 if (prefs_common.show_ruler)
9607 gtk_widget_show(compose->ruler_hbox);
9608 /* Fix the focus, as it doesn't go anywhere when the
9609 * socket is hidden or destroyed */
9610 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9612 g_assert (compose->exteditor_socket != NULL);
9613 /* Fix the focus, as it doesn't go anywhere when the
9614 * edit box is hidden */
9615 if (gtk_widget_is_focus(compose->text))
9616 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9617 gtk_widget_hide(compose->scrolledwin);
9618 gtk_widget_hide(compose->ruler_hbox);
9619 gtk_widget_show(compose->exteditor_socket);
9622 gtk_widget_set_sensitive(compose->text, sensitive);
9624 if (compose->toolbar->send_btn)
9625 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9626 if (compose->toolbar->sendl_btn)
9627 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9628 if (compose->toolbar->draft_btn)
9629 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9630 if (compose->toolbar->insert_btn)
9631 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9632 if (compose->toolbar->sig_btn)
9633 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9634 if (compose->toolbar->exteditor_btn)
9635 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9636 if (compose->toolbar->linewrap_current_btn)
9637 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9638 if (compose->toolbar->linewrap_all_btn)
9639 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9642 static gboolean compose_get_ext_editor_uses_socket()
9644 return (prefs_common_get_ext_editor_cmd() &&
9645 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9648 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9650 compose->exteditor_socket = NULL;
9651 /* returning FALSE allows destruction of the socket */
9654 #endif /* G_OS_UNIX */
9657 * compose_undo_state_changed:
9659 * Change the sensivity of the menuentries undo and redo
9661 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9662 gint redo_state, gpointer data)
9664 Compose *compose = (Compose *)data;
9666 switch (undo_state) {
9667 case UNDO_STATE_TRUE:
9668 if (!undostruct->undo_state) {
9669 undostruct->undo_state = TRUE;
9670 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9673 case UNDO_STATE_FALSE:
9674 if (undostruct->undo_state) {
9675 undostruct->undo_state = FALSE;
9676 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9679 case UNDO_STATE_UNCHANGED:
9681 case UNDO_STATE_REFRESH:
9682 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9685 g_warning("Undo state not recognized");
9689 switch (redo_state) {
9690 case UNDO_STATE_TRUE:
9691 if (!undostruct->redo_state) {
9692 undostruct->redo_state = TRUE;
9693 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9696 case UNDO_STATE_FALSE:
9697 if (undostruct->redo_state) {
9698 undostruct->redo_state = FALSE;
9699 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9702 case UNDO_STATE_UNCHANGED:
9704 case UNDO_STATE_REFRESH:
9705 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9708 g_warning("Redo state not recognized");
9713 /* callback functions */
9715 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9716 GtkAllocation *allocation,
9719 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9722 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9723 * includes "non-client" (windows-izm) in calculation, so this calculation
9724 * may not be accurate.
9726 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9727 GtkAllocation *allocation,
9728 GtkSHRuler *shruler)
9730 if (prefs_common.show_ruler) {
9731 gint char_width = 0, char_height = 0;
9732 gint line_width_in_chars;
9734 gtkut_get_font_size(GTK_WIDGET(widget),
9735 &char_width, &char_height);
9736 line_width_in_chars =
9737 (allocation->width - allocation->x) / char_width;
9739 /* got the maximum */
9740 gtk_shruler_set_range(GTK_SHRULER(shruler),
9741 0.0, line_width_in_chars, 0);
9750 ComposePrefType type;
9751 gboolean entry_marked;
9754 static void account_activated(GtkComboBox *optmenu, gpointer data)
9756 Compose *compose = (Compose *)data;
9759 gchar *folderidentifier;
9760 gint account_id = 0;
9763 GSList *list, *saved_list = NULL;
9764 HeaderEntryState *state;
9766 /* Get ID of active account in the combo box */
9767 menu = gtk_combo_box_get_model(optmenu);
9768 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9769 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9771 ac = account_find_from_id(account_id);
9772 cm_return_if_fail(ac != NULL);
9774 if (ac != compose->account) {
9775 compose_select_account(compose, ac, FALSE);
9777 for (list = compose->header_list; list; list = list->next) {
9778 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9780 if (hentry->type == PREF_ACCOUNT || !list->next) {
9781 compose_destroy_headerentry(compose, hentry);
9784 state = g_malloc0(sizeof(HeaderEntryState));
9785 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9786 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9787 state->entry = gtk_editable_get_chars(
9788 GTK_EDITABLE(hentry->entry), 0, -1);
9789 state->type = hentry->type;
9791 saved_list = g_slist_append(saved_list, state);
9792 compose_destroy_headerentry(compose, hentry);
9795 compose->header_last = NULL;
9796 g_slist_free(compose->header_list);
9797 compose->header_list = NULL;
9798 compose->header_nextrow = 1;
9799 compose_create_header_entry(compose);
9801 if (ac->set_autocc && ac->auto_cc)
9802 compose_entry_append(compose, ac->auto_cc,
9803 COMPOSE_CC, PREF_ACCOUNT);
9804 if (ac->set_autobcc && ac->auto_bcc)
9805 compose_entry_append(compose, ac->auto_bcc,
9806 COMPOSE_BCC, PREF_ACCOUNT);
9807 if (ac->set_autoreplyto && ac->auto_replyto)
9808 compose_entry_append(compose, ac->auto_replyto,
9809 COMPOSE_REPLYTO, PREF_ACCOUNT);
9811 for (list = saved_list; list; list = list->next) {
9812 state = (HeaderEntryState *) list->data;
9814 compose_add_header_entry(compose, state->header,
9815 state->entry, state->type);
9817 g_free(state->header);
9818 g_free(state->entry);
9821 g_slist_free(saved_list);
9823 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9824 (ac->protocol == A_NNTP) ?
9825 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9828 /* Set message save folder */
9829 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9830 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9832 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9833 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9835 compose_set_save_to(compose, NULL);
9836 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9837 folderidentifier = folder_item_get_identifier(account_get_special_folder
9838 (compose->account, F_OUTBOX));
9839 compose_set_save_to(compose, folderidentifier);
9840 g_free(folderidentifier);
9844 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9845 GtkTreeViewColumn *column, Compose *compose)
9847 compose_attach_property(NULL, compose);
9850 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9853 Compose *compose = (Compose *)data;
9854 GtkTreeSelection *attach_selection;
9855 gint attach_nr_selected;
9858 if (!event) return FALSE;
9860 if (event->button == 3) {
9861 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9862 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9864 /* If no rows, or just one row is selected, right-click should
9865 * open menu relevant to the row being right-clicked on. We
9866 * achieve that by selecting the clicked row first. If more
9867 * than one row is selected, we shouldn't modify the selection,
9868 * as user may want to remove selected rows (attachments). */
9869 if (attach_nr_selected < 2) {
9870 gtk_tree_selection_unselect_all(attach_selection);
9871 attach_nr_selected = 0;
9872 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9873 event->x, event->y, &path, NULL, NULL, NULL);
9875 gtk_tree_selection_select_path(attach_selection, path);
9876 gtk_tree_path_free(path);
9877 attach_nr_selected++;
9881 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9882 /* Properties menu item makes no sense with more than one row
9883 * selected, the properties dialog can only edit one attachment. */
9884 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9886 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9887 NULL, NULL, event->button, event->time);
9894 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9897 Compose *compose = (Compose *)data;
9899 if (!event) return FALSE;
9901 switch (event->keyval) {
9902 case GDK_KEY_Delete:
9903 compose_attach_remove_selected(NULL, compose);
9909 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9911 toolbar_comp_set_sensitive(compose, allow);
9912 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9913 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9915 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9917 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9921 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9925 static void compose_send_cb(GtkAction *action, gpointer data)
9927 Compose *compose = (Compose *)data;
9930 if (compose->exteditor_tag != -1) {
9931 debug_print("ignoring send: external editor still open\n");
9935 if (prefs_common.work_offline &&
9936 !inc_offline_should_override(TRUE,
9937 _("Claws Mail needs network access in order "
9938 "to send this email.")))
9941 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9942 g_source_remove(compose->draft_timeout_tag);
9943 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9946 compose_send(compose);
9949 static void compose_send_later_cb(GtkAction *action, gpointer data)
9951 Compose *compose = (Compose *)data;
9955 compose_allow_user_actions(compose, FALSE);
9956 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9957 compose_allow_user_actions(compose, TRUE);
9961 compose_close(compose);
9962 } else if (val == -1) {
9963 alertpanel_error(_("Could not queue message."));
9964 } else if (val == -2) {
9965 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno));
9966 } else if (val == -3) {
9967 if (privacy_peek_error())
9968 alertpanel_error(_("Could not queue message for sending:\n\n"
9969 "Signature failed: %s"), privacy_get_error());
9970 } else if (val == -4) {
9971 alertpanel_error(_("Could not queue message for sending:\n\n"
9972 "Charset conversion failed."));
9973 } else if (val == -5) {
9974 alertpanel_error(_("Could not queue message for sending:\n\n"
9975 "Couldn't get recipient encryption key."));
9976 } else if (val == -6) {
9979 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9982 #define DRAFTED_AT_EXIT "drafted_at_exit"
9983 static void compose_register_draft(MsgInfo *info)
9985 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9986 DRAFTED_AT_EXIT, NULL);
9987 FILE *fp = g_fopen(filepath, "ab");
9990 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9998 gboolean compose_draft (gpointer data, guint action)
10000 Compose *compose = (Compose *)data;
10005 MsgFlags flag = {0, 0};
10006 static gboolean lock = FALSE;
10007 MsgInfo *newmsginfo;
10009 gboolean target_locked = FALSE;
10010 gboolean err = FALSE;
10012 if (lock) return FALSE;
10014 if (compose->sending)
10017 draft = account_get_special_folder(compose->account, F_DRAFT);
10018 cm_return_val_if_fail(draft != NULL, FALSE);
10020 if (!g_mutex_trylock(compose->mutex)) {
10021 /* we don't want to lock the mutex once it's available,
10022 * because as the only other part of compose.c locking
10023 * it is compose_close - which means once unlocked,
10024 * the compose struct will be freed */
10025 debug_print("couldn't lock mutex, probably sending\n");
10031 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10032 G_DIR_SEPARATOR, compose);
10033 if ((fp = g_fopen(tmp, "wb")) == NULL) {
10034 FILE_OP_ERROR(tmp, "fopen");
10038 /* chmod for security */
10039 if (change_file_mode_rw(fp, tmp) < 0) {
10040 FILE_OP_ERROR(tmp, "chmod");
10041 g_warning("can't change file mode");
10044 /* Save draft infos */
10045 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
10046 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
10048 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
10049 gchar *savefolderid;
10051 savefolderid = compose_get_save_to(compose);
10052 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
10053 g_free(savefolderid);
10055 if (compose->return_receipt) {
10056 err |= (fprintf(fp, "RRCPT:1\n") < 0);
10058 if (compose->privacy_system) {
10059 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
10060 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
10061 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
10064 /* Message-ID of message replying to */
10065 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
10066 gchar *folderid = NULL;
10068 if (compose->replyinfo->folder)
10069 folderid = folder_item_get_identifier(compose->replyinfo->folder);
10070 if (folderid == NULL)
10071 folderid = g_strdup("NULL");
10073 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
10076 /* Message-ID of message forwarding to */
10077 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
10078 gchar *folderid = NULL;
10080 if (compose->fwdinfo->folder)
10081 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
10082 if (folderid == NULL)
10083 folderid = g_strdup("NULL");
10085 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
10089 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
10090 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
10092 sheaders = compose_get_manual_headers_info(compose);
10093 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
10096 /* end of headers */
10097 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
10104 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
10108 if (fclose(fp) == EOF) {
10112 flag.perm_flags = MSG_NEW|MSG_UNREAD;
10113 if (compose->targetinfo) {
10114 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
10116 flag.perm_flags |= MSG_LOCKED;
10118 flag.tmp_flags = MSG_DRAFT;
10120 folder_item_scan(draft);
10121 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
10122 MsgInfo *tmpinfo = NULL;
10123 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
10124 if (compose->msgid) {
10125 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
10128 msgnum = tmpinfo->msgnum;
10129 procmsg_msginfo_free(&tmpinfo);
10130 debug_print("got draft msgnum %d from scanning\n", msgnum);
10132 debug_print("didn't get draft msgnum after scanning\n");
10135 debug_print("got draft msgnum %d from adding\n", msgnum);
10141 if (action != COMPOSE_AUTO_SAVE) {
10142 if (action != COMPOSE_DRAFT_FOR_EXIT)
10143 alertpanel_error(_("Could not save draft."));
10146 gtkut_window_popup(compose->window);
10147 val = alertpanel_full(_("Could not save draft"),
10148 _("Could not save draft.\n"
10149 "Do you want to cancel exit or discard this email?"),
10150 _("_Cancel exit"), _("_Discard email"), NULL,
10151 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
10152 if (val == G_ALERTALTERNATE) {
10154 g_mutex_unlock(compose->mutex); /* must be done before closing */
10155 compose_close(compose);
10159 g_mutex_unlock(compose->mutex); /* must be done before closing */
10168 if (compose->mode == COMPOSE_REEDIT) {
10169 compose_remove_reedit_target(compose, TRUE);
10172 newmsginfo = folder_item_get_msginfo(draft, msgnum);
10175 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
10177 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
10179 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
10180 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
10181 procmsg_msginfo_set_flags(newmsginfo, 0,
10182 MSG_HAS_ATTACHMENT);
10184 if (action == COMPOSE_DRAFT_FOR_EXIT) {
10185 compose_register_draft(newmsginfo);
10187 procmsg_msginfo_free(&newmsginfo);
10190 folder_item_scan(draft);
10192 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
10194 g_mutex_unlock(compose->mutex); /* must be done before closing */
10195 compose_close(compose);
10201 path = folder_item_fetch_msg(draft, msgnum);
10202 if (path == NULL) {
10203 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
10206 if (g_stat(path, &s) < 0) {
10207 FILE_OP_ERROR(path, "stat");
10213 procmsg_msginfo_free(&(compose->targetinfo));
10214 compose->targetinfo = procmsg_msginfo_new();
10215 compose->targetinfo->msgnum = msgnum;
10216 compose->targetinfo->size = (goffset)s.st_size;
10217 compose->targetinfo->mtime = s.st_mtime;
10218 compose->targetinfo->folder = draft;
10220 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10221 compose->mode = COMPOSE_REEDIT;
10223 if (action == COMPOSE_AUTO_SAVE) {
10224 compose->autosaved_draft = compose->targetinfo;
10226 compose->modified = FALSE;
10227 compose_set_title(compose);
10231 g_mutex_unlock(compose->mutex);
10235 void compose_clear_exit_drafts(void)
10237 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10238 DRAFTED_AT_EXIT, NULL);
10239 if (is_file_exist(filepath))
10240 claws_unlink(filepath);
10245 void compose_reopen_exit_drafts(void)
10247 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10248 DRAFTED_AT_EXIT, NULL);
10249 FILE *fp = g_fopen(filepath, "rb");
10253 while (fgets(buf, sizeof(buf), fp)) {
10254 gchar **parts = g_strsplit(buf, "\t", 2);
10255 const gchar *folder = parts[0];
10256 int msgnum = parts[1] ? atoi(parts[1]):-1;
10258 if (folder && *folder && msgnum > -1) {
10259 FolderItem *item = folder_find_item_from_identifier(folder);
10260 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10262 compose_reedit(info, FALSE);
10269 compose_clear_exit_drafts();
10272 static void compose_save_cb(GtkAction *action, gpointer data)
10274 Compose *compose = (Compose *)data;
10275 compose_draft(compose, COMPOSE_KEEP_EDITING);
10276 compose->rmode = COMPOSE_REEDIT;
10279 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10281 if (compose && file_list) {
10284 for ( tmp = file_list; tmp; tmp = tmp->next) {
10285 gchar *file = (gchar *) tmp->data;
10286 gchar *utf8_filename = conv_filename_to_utf8(file);
10287 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10288 compose_changed_cb(NULL, compose);
10293 g_free(utf8_filename);
10298 static void compose_attach_cb(GtkAction *action, gpointer data)
10300 Compose *compose = (Compose *)data;
10303 if (compose->redirect_filename != NULL)
10306 /* Set focus_window properly, in case we were called via popup menu,
10307 * which unsets it (via focus_out_event callback on compose window). */
10308 manage_window_focus_in(compose->window, NULL, NULL);
10310 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10313 compose_attach_from_list(compose, file_list, TRUE);
10314 g_list_free(file_list);
10318 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10320 Compose *compose = (Compose *)data;
10322 gint files_inserted = 0;
10324 file_list = filesel_select_multiple_files_open(_("Select file"), NULL);
10329 for ( tmp = file_list; tmp; tmp = tmp->next) {
10330 gchar *file = (gchar *) tmp->data;
10331 gchar *filedup = g_strdup(file);
10332 gchar *shortfile = g_path_get_basename(filedup);
10333 ComposeInsertResult res;
10334 /* insert the file if the file is short or if the user confirmed that
10335 he/she wants to insert the large file */
10336 res = compose_insert_file(compose, file);
10337 if (res == COMPOSE_INSERT_READ_ERROR) {
10338 alertpanel_error(_("File '%s' could not be read."), shortfile);
10339 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10340 alertpanel_error(_("File '%s' contained invalid characters\n"
10341 "for the current encoding, insertion may be incorrect."),
10343 } else if (res == COMPOSE_INSERT_SUCCESS)
10350 g_list_free(file_list);
10354 if (files_inserted > 0 && compose->gtkaspell &&
10355 compose->gtkaspell->check_while_typing)
10356 gtkaspell_highlight_all(compose->gtkaspell);
10360 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10362 Compose *compose = (Compose *)data;
10364 compose_insert_sig(compose, FALSE);
10367 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10369 Compose *compose = (Compose *)data;
10371 compose_insert_sig(compose, TRUE);
10374 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10378 Compose *compose = (Compose *)data;
10380 gtkut_widget_get_uposition(widget, &x, &y);
10381 if (!compose->batch) {
10382 prefs_common.compose_x = x;
10383 prefs_common.compose_y = y;
10385 if (compose->sending || compose->updating)
10387 compose_close_cb(NULL, compose);
10391 void compose_close_toolbar(Compose *compose)
10393 compose_close_cb(NULL, compose);
10396 static void compose_close_cb(GtkAction *action, gpointer data)
10398 Compose *compose = (Compose *)data;
10402 if (compose->exteditor_tag != -1) {
10403 if (!compose_ext_editor_kill(compose))
10408 if (compose->modified) {
10409 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10410 if (!g_mutex_trylock(compose->mutex)) {
10411 /* we don't want to lock the mutex once it's available,
10412 * because as the only other part of compose.c locking
10413 * it is compose_close - which means once unlocked,
10414 * the compose struct will be freed */
10415 debug_print("couldn't lock mutex, probably sending\n");
10419 val = alertpanel(_("Discard message"),
10420 _("This message has been modified. Discard it?"),
10421 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10423 val = alertpanel(_("Save changes"),
10424 _("This message has been modified. Save the latest changes?"),
10425 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10428 g_mutex_unlock(compose->mutex);
10430 case G_ALERTDEFAULT:
10431 if (compose_can_autosave(compose) && !reedit)
10432 compose_remove_draft(compose);
10434 case G_ALERTALTERNATE:
10435 compose_draft(data, COMPOSE_QUIT_EDITING);
10442 compose_close(compose);
10445 static void compose_print_cb(GtkAction *action, gpointer data)
10447 Compose *compose = (Compose *) data;
10449 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10450 if (compose->targetinfo)
10451 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10454 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10456 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10457 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10458 Compose *compose = (Compose *) data;
10461 compose->out_encoding = (CharSet)value;
10464 static void compose_address_cb(GtkAction *action, gpointer data)
10466 Compose *compose = (Compose *)data;
10468 #ifndef USE_ALT_ADDRBOOK
10469 addressbook_open(compose);
10471 GError* error = NULL;
10472 addressbook_connect_signals(compose);
10473 addressbook_dbus_open(TRUE, &error);
10475 g_warning("%s", error->message);
10476 g_error_free(error);
10481 static void about_show_cb(GtkAction *action, gpointer data)
10486 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10488 Compose *compose = (Compose *)data;
10493 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10494 cm_return_if_fail(tmpl != NULL);
10496 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10498 val = alertpanel(_("Apply template"), msg,
10499 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10502 if (val == G_ALERTDEFAULT)
10503 compose_template_apply(compose, tmpl, TRUE);
10504 else if (val == G_ALERTALTERNATE)
10505 compose_template_apply(compose, tmpl, FALSE);
10508 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10510 Compose *compose = (Compose *)data;
10513 if (compose->exteditor_tag != -1) {
10514 debug_print("ignoring open external editor: external editor still open\n");
10518 compose_exec_ext_editor(compose);
10521 static void compose_undo_cb(GtkAction *action, gpointer data)
10523 Compose *compose = (Compose *)data;
10524 gboolean prev_autowrap = compose->autowrap;
10526 compose->autowrap = FALSE;
10527 undo_undo(compose->undostruct);
10528 compose->autowrap = prev_autowrap;
10531 static void compose_redo_cb(GtkAction *action, gpointer data)
10533 Compose *compose = (Compose *)data;
10534 gboolean prev_autowrap = compose->autowrap;
10536 compose->autowrap = FALSE;
10537 undo_redo(compose->undostruct);
10538 compose->autowrap = prev_autowrap;
10541 static void entry_cut_clipboard(GtkWidget *entry)
10543 if (GTK_IS_EDITABLE(entry))
10544 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10545 else if (GTK_IS_TEXT_VIEW(entry))
10546 gtk_text_buffer_cut_clipboard(
10547 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10548 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10552 static void entry_copy_clipboard(GtkWidget *entry)
10554 if (GTK_IS_EDITABLE(entry))
10555 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10556 else if (GTK_IS_TEXT_VIEW(entry))
10557 gtk_text_buffer_copy_clipboard(
10558 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10559 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10562 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10563 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10565 if (GTK_IS_TEXT_VIEW(entry)) {
10566 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10567 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10568 GtkTextIter start_iter, end_iter;
10570 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10572 if (contents == NULL)
10575 /* we shouldn't delete the selection when middle-click-pasting, or we
10576 * can't mid-click-paste our own selection */
10577 if (clip != GDK_SELECTION_PRIMARY) {
10578 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10579 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10582 if (insert_place == NULL) {
10583 /* if insert_place isn't specified, insert at the cursor.
10584 * used for Ctrl-V pasting */
10585 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10586 start = gtk_text_iter_get_offset(&start_iter);
10587 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10589 /* if insert_place is specified, paste here.
10590 * used for mid-click-pasting */
10591 start = gtk_text_iter_get_offset(insert_place);
10592 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10593 if (prefs_common.primary_paste_unselects)
10594 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10598 /* paste unwrapped: mark the paste so it's not wrapped later */
10599 end = start + strlen(contents);
10600 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10601 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10602 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10603 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10604 /* rewrap paragraph now (after a mid-click-paste) */
10605 mark_start = gtk_text_buffer_get_insert(buffer);
10606 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10607 gtk_text_iter_backward_char(&start_iter);
10608 compose_beautify_paragraph(compose, &start_iter, TRUE);
10610 } else if (GTK_IS_EDITABLE(entry))
10611 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10613 compose->modified = TRUE;
10616 static void entry_allsel(GtkWidget *entry)
10618 if (GTK_IS_EDITABLE(entry))
10619 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10620 else if (GTK_IS_TEXT_VIEW(entry)) {
10621 GtkTextIter startiter, enditer;
10622 GtkTextBuffer *textbuf;
10624 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10625 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10626 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10628 gtk_text_buffer_move_mark_by_name(textbuf,
10629 "selection_bound", &startiter);
10630 gtk_text_buffer_move_mark_by_name(textbuf,
10631 "insert", &enditer);
10635 static void compose_cut_cb(GtkAction *action, gpointer data)
10637 Compose *compose = (Compose *)data;
10638 if (compose->focused_editable
10639 #ifndef GENERIC_UMPC
10640 && gtk_widget_has_focus(compose->focused_editable)
10643 entry_cut_clipboard(compose->focused_editable);
10646 static void compose_copy_cb(GtkAction *action, gpointer data)
10648 Compose *compose = (Compose *)data;
10649 if (compose->focused_editable
10650 #ifndef GENERIC_UMPC
10651 && gtk_widget_has_focus(compose->focused_editable)
10654 entry_copy_clipboard(compose->focused_editable);
10657 static void compose_paste_cb(GtkAction *action, gpointer data)
10659 Compose *compose = (Compose *)data;
10660 gint prev_autowrap;
10661 GtkTextBuffer *buffer;
10663 if (compose->focused_editable &&
10664 #ifndef GENERIC_UMPC
10665 gtk_widget_has_focus(compose->focused_editable)
10668 entry_paste_clipboard(compose, compose->focused_editable,
10669 prefs_common.linewrap_pastes,
10670 GDK_SELECTION_CLIPBOARD, NULL);
10675 #ifndef GENERIC_UMPC
10676 gtk_widget_has_focus(compose->text) &&
10678 compose->gtkaspell &&
10679 compose->gtkaspell->check_while_typing)
10680 gtkaspell_highlight_all(compose->gtkaspell);
10684 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10686 Compose *compose = (Compose *)data;
10687 gint wrap_quote = prefs_common.linewrap_quote;
10688 if (compose->focused_editable
10689 #ifndef GENERIC_UMPC
10690 && gtk_widget_has_focus(compose->focused_editable)
10693 /* let text_insert() (called directly or at a later time
10694 * after the gtk_editable_paste_clipboard) know that
10695 * text is to be inserted as a quotation. implemented
10696 * by using a simple refcount... */
10697 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10698 G_OBJECT(compose->focused_editable),
10699 "paste_as_quotation"));
10700 g_object_set_data(G_OBJECT(compose->focused_editable),
10701 "paste_as_quotation",
10702 GINT_TO_POINTER(paste_as_quotation + 1));
10703 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10704 entry_paste_clipboard(compose, compose->focused_editable,
10705 prefs_common.linewrap_pastes,
10706 GDK_SELECTION_CLIPBOARD, NULL);
10707 prefs_common.linewrap_quote = wrap_quote;
10711 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10713 Compose *compose = (Compose *)data;
10714 gint prev_autowrap;
10715 GtkTextBuffer *buffer;
10717 if (compose->focused_editable
10718 #ifndef GENERIC_UMPC
10719 && gtk_widget_has_focus(compose->focused_editable)
10722 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10723 GDK_SELECTION_CLIPBOARD, NULL);
10728 #ifndef GENERIC_UMPC
10729 gtk_widget_has_focus(compose->text) &&
10731 compose->gtkaspell &&
10732 compose->gtkaspell->check_while_typing)
10733 gtkaspell_highlight_all(compose->gtkaspell);
10737 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10739 Compose *compose = (Compose *)data;
10740 gint prev_autowrap;
10741 GtkTextBuffer *buffer;
10743 if (compose->focused_editable
10744 #ifndef GENERIC_UMPC
10745 && gtk_widget_has_focus(compose->focused_editable)
10748 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10749 GDK_SELECTION_CLIPBOARD, NULL);
10754 #ifndef GENERIC_UMPC
10755 gtk_widget_has_focus(compose->text) &&
10757 compose->gtkaspell &&
10758 compose->gtkaspell->check_while_typing)
10759 gtkaspell_highlight_all(compose->gtkaspell);
10763 static void compose_allsel_cb(GtkAction *action, gpointer data)
10765 Compose *compose = (Compose *)data;
10766 if (compose->focused_editable
10767 #ifndef GENERIC_UMPC
10768 && gtk_widget_has_focus(compose->focused_editable)
10771 entry_allsel(compose->focused_editable);
10774 static void textview_move_beginning_of_line (GtkTextView *text)
10776 GtkTextBuffer *buffer;
10780 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10782 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10783 mark = gtk_text_buffer_get_insert(buffer);
10784 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10785 gtk_text_iter_set_line_offset(&ins, 0);
10786 gtk_text_buffer_place_cursor(buffer, &ins);
10789 static void textview_move_forward_character (GtkTextView *text)
10791 GtkTextBuffer *buffer;
10795 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10797 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10798 mark = gtk_text_buffer_get_insert(buffer);
10799 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10800 if (gtk_text_iter_forward_cursor_position(&ins))
10801 gtk_text_buffer_place_cursor(buffer, &ins);
10804 static void textview_move_backward_character (GtkTextView *text)
10806 GtkTextBuffer *buffer;
10810 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10812 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10813 mark = gtk_text_buffer_get_insert(buffer);
10814 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10815 if (gtk_text_iter_backward_cursor_position(&ins))
10816 gtk_text_buffer_place_cursor(buffer, &ins);
10819 static void textview_move_forward_word (GtkTextView *text)
10821 GtkTextBuffer *buffer;
10826 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10828 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10829 mark = gtk_text_buffer_get_insert(buffer);
10830 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10831 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10832 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10833 gtk_text_iter_backward_word_start(&ins);
10834 gtk_text_buffer_place_cursor(buffer, &ins);
10838 static void textview_move_backward_word (GtkTextView *text)
10840 GtkTextBuffer *buffer;
10844 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10846 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10847 mark = gtk_text_buffer_get_insert(buffer);
10848 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10849 if (gtk_text_iter_backward_word_starts(&ins, 1))
10850 gtk_text_buffer_place_cursor(buffer, &ins);
10853 static void textview_move_end_of_line (GtkTextView *text)
10855 GtkTextBuffer *buffer;
10859 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10861 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10862 mark = gtk_text_buffer_get_insert(buffer);
10863 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10864 if (gtk_text_iter_forward_to_line_end(&ins))
10865 gtk_text_buffer_place_cursor(buffer, &ins);
10868 static void textview_move_next_line (GtkTextView *text)
10870 GtkTextBuffer *buffer;
10875 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10877 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10878 mark = gtk_text_buffer_get_insert(buffer);
10879 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10880 offset = gtk_text_iter_get_line_offset(&ins);
10881 if (gtk_text_iter_forward_line(&ins)) {
10882 gtk_text_iter_set_line_offset(&ins, offset);
10883 gtk_text_buffer_place_cursor(buffer, &ins);
10887 static void textview_move_previous_line (GtkTextView *text)
10889 GtkTextBuffer *buffer;
10894 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10896 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10897 mark = gtk_text_buffer_get_insert(buffer);
10898 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10899 offset = gtk_text_iter_get_line_offset(&ins);
10900 if (gtk_text_iter_backward_line(&ins)) {
10901 gtk_text_iter_set_line_offset(&ins, offset);
10902 gtk_text_buffer_place_cursor(buffer, &ins);
10906 static void textview_delete_forward_character (GtkTextView *text)
10908 GtkTextBuffer *buffer;
10910 GtkTextIter ins, end_iter;
10912 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10914 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10915 mark = gtk_text_buffer_get_insert(buffer);
10916 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10918 if (gtk_text_iter_forward_char(&end_iter)) {
10919 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10923 static void textview_delete_backward_character (GtkTextView *text)
10925 GtkTextBuffer *buffer;
10927 GtkTextIter ins, end_iter;
10929 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10931 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10932 mark = gtk_text_buffer_get_insert(buffer);
10933 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10935 if (gtk_text_iter_backward_char(&end_iter)) {
10936 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10940 static void textview_delete_forward_word (GtkTextView *text)
10942 GtkTextBuffer *buffer;
10944 GtkTextIter ins, end_iter;
10946 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10948 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10949 mark = gtk_text_buffer_get_insert(buffer);
10950 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10952 if (gtk_text_iter_forward_word_end(&end_iter)) {
10953 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10957 static void textview_delete_backward_word (GtkTextView *text)
10959 GtkTextBuffer *buffer;
10961 GtkTextIter ins, end_iter;
10963 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10965 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10966 mark = gtk_text_buffer_get_insert(buffer);
10967 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10969 if (gtk_text_iter_backward_word_start(&end_iter)) {
10970 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10974 static void textview_delete_line (GtkTextView *text)
10976 GtkTextBuffer *buffer;
10978 GtkTextIter ins, start_iter, end_iter;
10980 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10982 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10983 mark = gtk_text_buffer_get_insert(buffer);
10984 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10987 gtk_text_iter_set_line_offset(&start_iter, 0);
10990 if (gtk_text_iter_ends_line(&end_iter)){
10991 if (!gtk_text_iter_forward_char(&end_iter))
10992 gtk_text_iter_backward_char(&start_iter);
10995 gtk_text_iter_forward_to_line_end(&end_iter);
10996 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10999 static void textview_delete_to_line_end (GtkTextView *text)
11001 GtkTextBuffer *buffer;
11003 GtkTextIter ins, end_iter;
11005 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11007 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11008 mark = gtk_text_buffer_get_insert(buffer);
11009 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11011 if (gtk_text_iter_ends_line(&end_iter))
11012 gtk_text_iter_forward_char(&end_iter);
11014 gtk_text_iter_forward_to_line_end(&end_iter);
11015 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11018 #define DO_ACTION(name, act) { \
11019 if(!strcmp(name, a_name)) { \
11023 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11025 const gchar *a_name = gtk_action_get_name(action);
11026 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11027 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11028 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11029 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11030 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11031 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11032 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11033 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11034 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11035 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11036 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11037 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11038 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11039 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11043 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11045 Compose *compose = (Compose *)data;
11046 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11047 ComposeCallAdvancedAction action = -1;
11049 action = compose_call_advanced_action_from_path(gaction);
11052 void (*do_action) (GtkTextView *text);
11053 } action_table[] = {
11054 {textview_move_beginning_of_line},
11055 {textview_move_forward_character},
11056 {textview_move_backward_character},
11057 {textview_move_forward_word},
11058 {textview_move_backward_word},
11059 {textview_move_end_of_line},
11060 {textview_move_next_line},
11061 {textview_move_previous_line},
11062 {textview_delete_forward_character},
11063 {textview_delete_backward_character},
11064 {textview_delete_forward_word},
11065 {textview_delete_backward_word},
11066 {textview_delete_line},
11067 {textview_delete_to_line_end}
11070 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11072 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11073 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11074 if (action_table[action].do_action)
11075 action_table[action].do_action(text);
11077 g_warning("Not implemented yet.");
11081 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11083 GtkAllocation allocation;
11087 if (GTK_IS_EDITABLE(widget)) {
11088 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11089 gtk_editable_set_position(GTK_EDITABLE(widget),
11092 if ((parent = gtk_widget_get_parent(widget))
11093 && (parent = gtk_widget_get_parent(parent))
11094 && (parent = gtk_widget_get_parent(parent))) {
11095 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11096 gtk_widget_get_allocation(widget, &allocation);
11097 gint y = allocation.y;
11098 gint height = allocation.height;
11099 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11100 (GTK_SCROLLED_WINDOW(parent));
11102 gfloat value = gtk_adjustment_get_value(shown);
11103 gfloat upper = gtk_adjustment_get_upper(shown);
11104 gfloat page_size = gtk_adjustment_get_page_size(shown);
11105 if (y < (int)value) {
11106 gtk_adjustment_set_value(shown, y - 1);
11108 if ((y + height) > ((int)value + (int)page_size)) {
11109 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11110 gtk_adjustment_set_value(shown,
11111 y + height - (int)page_size - 1);
11113 gtk_adjustment_set_value(shown,
11114 (int)upper - (int)page_size - 1);
11121 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11122 compose->focused_editable = widget;
11124 #ifdef GENERIC_UMPC
11125 if (GTK_IS_TEXT_VIEW(widget)
11126 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11127 g_object_ref(compose->notebook);
11128 g_object_ref(compose->edit_vbox);
11129 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11130 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11131 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11132 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11133 g_object_unref(compose->notebook);
11134 g_object_unref(compose->edit_vbox);
11135 g_signal_handlers_block_by_func(G_OBJECT(widget),
11136 G_CALLBACK(compose_grab_focus_cb),
11138 gtk_widget_grab_focus(widget);
11139 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11140 G_CALLBACK(compose_grab_focus_cb),
11142 } else if (!GTK_IS_TEXT_VIEW(widget)
11143 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11144 g_object_ref(compose->notebook);
11145 g_object_ref(compose->edit_vbox);
11146 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11147 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11148 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11149 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11150 g_object_unref(compose->notebook);
11151 g_object_unref(compose->edit_vbox);
11152 g_signal_handlers_block_by_func(G_OBJECT(widget),
11153 G_CALLBACK(compose_grab_focus_cb),
11155 gtk_widget_grab_focus(widget);
11156 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11157 G_CALLBACK(compose_grab_focus_cb),
11163 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11165 compose->modified = TRUE;
11166 // compose_beautify_paragraph(compose, NULL, TRUE);
11167 #ifndef GENERIC_UMPC
11168 compose_set_title(compose);
11172 static void compose_wrap_cb(GtkAction *action, gpointer data)
11174 Compose *compose = (Compose *)data;
11175 compose_beautify_paragraph(compose, NULL, TRUE);
11178 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11180 Compose *compose = (Compose *)data;
11181 compose_wrap_all_full(compose, TRUE);
11184 static void compose_find_cb(GtkAction *action, gpointer data)
11186 Compose *compose = (Compose *)data;
11188 message_search_compose(compose);
11191 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11194 Compose *compose = (Compose *)data;
11195 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11196 if (compose->autowrap)
11197 compose_wrap_all_full(compose, TRUE);
11198 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11201 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11204 Compose *compose = (Compose *)data;
11205 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11208 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11210 Compose *compose = (Compose *)data;
11212 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11215 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11217 Compose *compose = (Compose *)data;
11219 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11222 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11224 g_free(compose->privacy_system);
11225 g_free(compose->encdata);
11227 compose->privacy_system = g_strdup(account->default_privacy_system);
11228 compose_update_privacy_system_menu_item(compose, warn);
11231 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11233 Compose *compose = (Compose *)data;
11235 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11236 gtk_widget_show(compose->ruler_hbox);
11237 prefs_common.show_ruler = TRUE;
11239 gtk_widget_hide(compose->ruler_hbox);
11240 gtk_widget_queue_resize(compose->edit_vbox);
11241 prefs_common.show_ruler = FALSE;
11245 static void compose_attach_drag_received_cb (GtkWidget *widget,
11246 GdkDragContext *context,
11249 GtkSelectionData *data,
11252 gpointer user_data)
11254 Compose *compose = (Compose *)user_data;
11258 type = gtk_selection_data_get_data_type(data);
11259 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11260 && gtk_drag_get_source_widget(context) !=
11261 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11262 list = uri_list_extract_filenames(
11263 (const gchar *)gtk_selection_data_get_data(data));
11264 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11265 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11266 compose_attach_append
11267 (compose, (const gchar *)tmp->data,
11268 utf8_filename, NULL, NULL);
11269 g_free(utf8_filename);
11271 if (list) compose_changed_cb(NULL, compose);
11272 list_free_strings(list);
11274 } else if (gtk_drag_get_source_widget(context)
11275 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11276 /* comes from our summaryview */
11277 SummaryView * summaryview = NULL;
11278 GSList * list = NULL, *cur = NULL;
11280 if (mainwindow_get_mainwindow())
11281 summaryview = mainwindow_get_mainwindow()->summaryview;
11284 list = summary_get_selected_msg_list(summaryview);
11286 for (cur = list; cur; cur = cur->next) {
11287 MsgInfo *msginfo = (MsgInfo *)cur->data;
11288 gchar *file = NULL;
11290 file = procmsg_get_message_file_full(msginfo,
11293 compose_attach_append(compose, (const gchar *)file,
11294 (const gchar *)file, "message/rfc822", NULL);
11298 g_slist_free(list);
11302 static gboolean compose_drag_drop(GtkWidget *widget,
11303 GdkDragContext *drag_context,
11305 guint time, gpointer user_data)
11307 /* not handling this signal makes compose_insert_drag_received_cb
11312 static gboolean completion_set_focus_to_subject
11313 (GtkWidget *widget,
11314 GdkEventKey *event,
11317 cm_return_val_if_fail(compose != NULL, FALSE);
11319 /* make backtab move to subject field */
11320 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11321 gtk_widget_grab_focus(compose->subject_entry);
11327 static void compose_insert_drag_received_cb (GtkWidget *widget,
11328 GdkDragContext *drag_context,
11331 GtkSelectionData *data,
11334 gpointer user_data)
11336 Compose *compose = (Compose *)user_data;
11342 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11344 type = gtk_selection_data_get_data_type(data);
11345 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11346 AlertValue val = G_ALERTDEFAULT;
11347 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11349 list = uri_list_extract_filenames(ddata);
11350 num_files = g_list_length(list);
11351 if (list == NULL && strstr(ddata, "://")) {
11352 /* Assume a list of no files, and data has ://, is a remote link */
11353 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11354 gchar *tmpfile = get_tmp_file();
11355 str_write_to_file(tmpdata, tmpfile);
11357 compose_insert_file(compose, tmpfile);
11358 claws_unlink(tmpfile);
11360 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11361 compose_beautify_paragraph(compose, NULL, TRUE);
11364 switch (prefs_common.compose_dnd_mode) {
11365 case COMPOSE_DND_ASK:
11366 msg = g_strdup_printf(
11368 "Do you want to insert the contents of the file "
11369 "into the message body, or attach it to the email?",
11370 "Do you want to insert the contents of the %d files "
11371 "into the message body, or attach them to the email?",
11374 val = alertpanel_full(_("Insert or attach?"), msg,
11375 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11376 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11379 case COMPOSE_DND_INSERT:
11380 val = G_ALERTALTERNATE;
11382 case COMPOSE_DND_ATTACH:
11383 val = G_ALERTOTHER;
11386 /* unexpected case */
11387 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11390 if (val & G_ALERTDISABLE) {
11391 val &= ~G_ALERTDISABLE;
11392 /* remember what action to perform by default, only if we don't click Cancel */
11393 if (val == G_ALERTALTERNATE)
11394 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11395 else if (val == G_ALERTOTHER)
11396 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11399 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11400 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11401 list_free_strings(list);
11404 } else if (val == G_ALERTOTHER) {
11405 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11406 list_free_strings(list);
11411 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11412 compose_insert_file(compose, (const gchar *)tmp->data);
11414 list_free_strings(list);
11416 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11421 static void compose_header_drag_received_cb (GtkWidget *widget,
11422 GdkDragContext *drag_context,
11425 GtkSelectionData *data,
11428 gpointer user_data)
11430 GtkEditable *entry = (GtkEditable *)user_data;
11431 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11433 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11436 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11437 gchar *decoded=g_new(gchar, strlen(email));
11440 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11441 gtk_editable_delete_text(entry, 0, -1);
11442 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11443 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11447 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11450 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11452 Compose *compose = (Compose *)data;
11454 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11455 compose->return_receipt = TRUE;
11457 compose->return_receipt = FALSE;
11460 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11462 Compose *compose = (Compose *)data;
11464 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11465 compose->remove_references = TRUE;
11467 compose->remove_references = FALSE;
11470 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11471 ComposeHeaderEntry *headerentry)
11473 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11477 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11478 GdkEventKey *event,
11479 ComposeHeaderEntry *headerentry)
11481 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11482 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11483 !(event->state & GDK_MODIFIER_MASK) &&
11484 (event->keyval == GDK_KEY_BackSpace) &&
11485 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11486 gtk_container_remove
11487 (GTK_CONTAINER(headerentry->compose->header_table),
11488 headerentry->combo);
11489 gtk_container_remove
11490 (GTK_CONTAINER(headerentry->compose->header_table),
11491 headerentry->entry);
11492 headerentry->compose->header_list =
11493 g_slist_remove(headerentry->compose->header_list,
11495 g_free(headerentry);
11496 } else if (event->keyval == GDK_KEY_Tab) {
11497 if (headerentry->compose->header_last == headerentry) {
11498 /* Override default next focus, and give it to subject_entry
11499 * instead of notebook tabs
11501 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11502 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11509 static gboolean scroll_postpone(gpointer data)
11511 Compose *compose = (Compose *)data;
11513 if (compose->batch)
11516 GTK_EVENTS_FLUSH();
11517 compose_show_first_last_header(compose, FALSE);
11521 static void compose_headerentry_changed_cb(GtkWidget *entry,
11522 ComposeHeaderEntry *headerentry)
11524 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11525 compose_create_header_entry(headerentry->compose);
11526 g_signal_handlers_disconnect_matched
11527 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11528 0, 0, NULL, NULL, headerentry);
11530 if (!headerentry->compose->batch)
11531 g_timeout_add(0, scroll_postpone, headerentry->compose);
11535 static gboolean compose_defer_auto_save_draft(Compose *compose)
11537 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11538 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11542 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11544 GtkAdjustment *vadj;
11546 cm_return_if_fail(compose);
11551 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11552 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11553 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11554 gtk_widget_get_parent(compose->header_table)));
11555 gtk_adjustment_set_value(vadj, (show_first ?
11556 gtk_adjustment_get_lower(vadj) :
11557 (gtk_adjustment_get_upper(vadj) -
11558 gtk_adjustment_get_page_size(vadj))));
11559 gtk_adjustment_changed(vadj);
11562 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11563 const gchar *text, gint len, Compose *compose)
11565 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11566 (G_OBJECT(compose->text), "paste_as_quotation"));
11569 cm_return_if_fail(text != NULL);
11571 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11572 G_CALLBACK(text_inserted),
11574 if (paste_as_quotation) {
11576 const gchar *qmark;
11578 GtkTextIter start_iter;
11581 len = strlen(text);
11583 new_text = g_strndup(text, len);
11585 qmark = compose_quote_char_from_context(compose);
11587 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11588 gtk_text_buffer_place_cursor(buffer, iter);
11590 pos = gtk_text_iter_get_offset(iter);
11592 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11593 _("Quote format error at line %d."));
11594 quote_fmt_reset_vartable();
11596 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11597 GINT_TO_POINTER(paste_as_quotation - 1));
11599 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11600 gtk_text_buffer_place_cursor(buffer, iter);
11601 gtk_text_buffer_delete_mark(buffer, mark);
11603 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11604 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11605 compose_beautify_paragraph(compose, &start_iter, FALSE);
11606 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11607 gtk_text_buffer_delete_mark(buffer, mark);
11609 if (strcmp(text, "\n") || compose->automatic_break
11610 || gtk_text_iter_starts_line(iter)) {
11611 GtkTextIter before_ins;
11612 gtk_text_buffer_insert(buffer, iter, text, len);
11613 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11614 before_ins = *iter;
11615 gtk_text_iter_backward_chars(&before_ins, len);
11616 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11619 /* check if the preceding is just whitespace or quote */
11620 GtkTextIter start_line;
11621 gchar *tmp = NULL, *quote = NULL;
11622 gint quote_len = 0, is_normal = 0;
11623 start_line = *iter;
11624 gtk_text_iter_set_line_offset(&start_line, 0);
11625 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11628 if (*tmp == '\0') {
11631 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11639 gtk_text_buffer_insert(buffer, iter, text, len);
11641 gtk_text_buffer_insert_with_tags_by_name(buffer,
11642 iter, text, len, "no_join", NULL);
11647 if (!paste_as_quotation) {
11648 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11649 compose_beautify_paragraph(compose, iter, FALSE);
11650 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11651 gtk_text_buffer_delete_mark(buffer, mark);
11654 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11655 G_CALLBACK(text_inserted),
11657 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11659 if (compose_can_autosave(compose) &&
11660 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11661 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11662 compose->draft_timeout_tag = g_timeout_add
11663 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11667 static void compose_check_all(GtkAction *action, gpointer data)
11669 Compose *compose = (Compose *)data;
11670 if (!compose->gtkaspell)
11673 if (gtk_widget_has_focus(compose->subject_entry))
11674 claws_spell_entry_check_all(
11675 CLAWS_SPELL_ENTRY(compose->subject_entry));
11677 gtkaspell_check_all(compose->gtkaspell);
11680 static void compose_highlight_all(GtkAction *action, gpointer data)
11682 Compose *compose = (Compose *)data;
11683 if (compose->gtkaspell) {
11684 claws_spell_entry_recheck_all(
11685 CLAWS_SPELL_ENTRY(compose->subject_entry));
11686 gtkaspell_highlight_all(compose->gtkaspell);
11690 static void compose_check_backwards(GtkAction *action, gpointer data)
11692 Compose *compose = (Compose *)data;
11693 if (!compose->gtkaspell) {
11694 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11698 if (gtk_widget_has_focus(compose->subject_entry))
11699 claws_spell_entry_check_backwards(
11700 CLAWS_SPELL_ENTRY(compose->subject_entry));
11702 gtkaspell_check_backwards(compose->gtkaspell);
11705 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11707 Compose *compose = (Compose *)data;
11708 if (!compose->gtkaspell) {
11709 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11713 if (gtk_widget_has_focus(compose->subject_entry))
11714 claws_spell_entry_check_forwards_go(
11715 CLAWS_SPELL_ENTRY(compose->subject_entry));
11717 gtkaspell_check_forwards_go(compose->gtkaspell);
11722 *\brief Guess originating forward account from MsgInfo and several
11723 * "common preference" settings. Return NULL if no guess.
11725 static PrefsAccount *compose_find_account(MsgInfo *msginfo)
11727 PrefsAccount *account = NULL;
11729 cm_return_val_if_fail(msginfo, NULL);
11730 cm_return_val_if_fail(msginfo->folder, NULL);
11731 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11733 if (msginfo->folder->prefs->enable_default_account)
11734 account = account_find_from_id(msginfo->folder->prefs->default_account);
11736 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11738 Xstrdup_a(to, msginfo->to, return NULL);
11739 extract_address(to);
11740 account = account_find_from_address(to, FALSE);
11743 if (!account && prefs_common.forward_account_autosel) {
11744 gchar cc[BUFFSIZE];
11745 if (!procheader_get_header_from_msginfo
11746 (msginfo, cc,sizeof cc , "Cc:")) {
11747 gchar *buf = cc + strlen("Cc:");
11748 extract_address(buf);
11749 account = account_find_from_address(buf, FALSE);
11753 if (!account && prefs_common.forward_account_autosel) {
11754 gchar deliveredto[BUFFSIZE];
11755 if (!procheader_get_header_from_msginfo
11756 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11757 gchar *buf = deliveredto + strlen("Delivered-To:");
11758 extract_address(buf);
11759 account = account_find_from_address(buf, FALSE);
11764 account = msginfo->folder->folder->account;
11769 gboolean compose_close(Compose *compose)
11773 cm_return_val_if_fail(compose, FALSE);
11775 if (!g_mutex_trylock(compose->mutex)) {
11776 /* we have to wait for the (possibly deferred by auto-save)
11777 * drafting to be done, before destroying the compose under
11779 debug_print("waiting for drafting to finish...\n");
11780 compose_allow_user_actions(compose, FALSE);
11781 if (compose->close_timeout_tag == 0) {
11782 compose->close_timeout_tag =
11783 g_timeout_add (500, (GSourceFunc) compose_close,
11789 if (compose->draft_timeout_tag >= 0) {
11790 g_source_remove(compose->draft_timeout_tag);
11791 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11794 gtkut_widget_get_uposition(compose->window, &x, &y);
11795 if (!compose->batch) {
11796 prefs_common.compose_x = x;
11797 prefs_common.compose_y = y;
11799 g_mutex_unlock(compose->mutex);
11800 compose_destroy(compose);
11805 * Add entry field for each address in list.
11806 * \param compose E-Mail composition object.
11807 * \param listAddress List of (formatted) E-Mail addresses.
11809 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11812 node = listAddress;
11814 addr = ( gchar * ) node->data;
11815 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11816 node = g_list_next( node );
11820 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11821 guint action, gboolean opening_multiple)
11823 gchar *body = NULL;
11824 GSList *new_msglist = NULL;
11825 MsgInfo *tmp_msginfo = NULL;
11826 gboolean originally_enc = FALSE;
11827 gboolean originally_sig = FALSE;
11828 Compose *compose = NULL;
11829 gchar *s_system = NULL;
11831 cm_return_if_fail(msgview != NULL);
11833 cm_return_if_fail(msginfo_list != NULL);
11835 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11836 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11837 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11839 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11840 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11841 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11842 orig_msginfo, mimeinfo);
11843 if (tmp_msginfo != NULL) {
11844 new_msglist = g_slist_append(NULL, tmp_msginfo);
11846 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11847 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11848 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11850 tmp_msginfo->folder = orig_msginfo->folder;
11851 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11852 if (orig_msginfo->tags) {
11853 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11854 tmp_msginfo->folder->tags_dirty = TRUE;
11860 if (!opening_multiple)
11861 body = messageview_get_selection(msgview);
11864 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11865 procmsg_msginfo_free(&tmp_msginfo);
11866 g_slist_free(new_msglist);
11868 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11870 if (compose && originally_enc) {
11871 compose_force_encryption(compose, compose->account, FALSE, s_system);
11874 if (compose && originally_sig && compose->account->default_sign_reply) {
11875 compose_force_signing(compose, compose->account, s_system);
11879 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11882 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11885 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11886 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11887 GSList *cur = msginfo_list;
11888 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11889 "messages. Opening the windows "
11890 "could take some time. Do you "
11891 "want to continue?"),
11892 g_slist_length(msginfo_list));
11893 if (g_slist_length(msginfo_list) > 9
11894 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11895 != G_ALERTALTERNATE) {
11900 /* We'll open multiple compose windows */
11901 /* let the WM place the next windows */
11902 compose_force_window_origin = FALSE;
11903 for (; cur; cur = cur->next) {
11905 tmplist.data = cur->data;
11906 tmplist.next = NULL;
11907 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11909 compose_force_window_origin = TRUE;
11911 /* forwarding multiple mails as attachments is done via a
11912 * single compose window */
11913 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11917 void compose_check_for_email_account(Compose *compose)
11919 PrefsAccount *ac = NULL, *curr = NULL;
11925 if (compose->account && compose->account->protocol == A_NNTP) {
11926 ac = account_get_cur_account();
11927 if (ac->protocol == A_NNTP) {
11928 list = account_get_list();
11930 for( ; list != NULL ; list = g_list_next(list)) {
11931 curr = (PrefsAccount *) list->data;
11932 if (curr->protocol != A_NNTP) {
11938 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11943 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11944 const gchar *address)
11946 GSList *msginfo_list = NULL;
11947 gchar *body = messageview_get_selection(msgview);
11950 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11952 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11953 compose_check_for_email_account(compose);
11954 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11955 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11956 compose_reply_set_subject(compose, msginfo);
11959 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11962 void compose_set_position(Compose *compose, gint pos)
11964 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11966 gtkut_text_view_set_position(text, pos);
11969 gboolean compose_search_string(Compose *compose,
11970 const gchar *str, gboolean case_sens)
11972 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11974 return gtkut_text_view_search_string(text, str, case_sens);
11977 gboolean compose_search_string_backward(Compose *compose,
11978 const gchar *str, gboolean case_sens)
11980 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11982 return gtkut_text_view_search_string_backward(text, str, case_sens);
11985 /* allocate a msginfo structure and populate its data from a compose data structure */
11986 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11988 MsgInfo *newmsginfo;
11990 gchar buf[BUFFSIZE];
11992 cm_return_val_if_fail( compose != NULL, NULL );
11994 newmsginfo = procmsg_msginfo_new();
11997 get_rfc822_date(buf, sizeof(buf));
11998 newmsginfo->date = g_strdup(buf);
12001 if (compose->from_name) {
12002 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
12003 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
12007 if (compose->subject_entry)
12008 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12010 /* to, cc, reply-to, newsgroups */
12011 for (list = compose->header_list; list; list = list->next) {
12012 gchar *header = gtk_editable_get_chars(
12014 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12015 gchar *entry = gtk_editable_get_chars(
12016 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12018 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12019 if ( newmsginfo->to == NULL ) {
12020 newmsginfo->to = g_strdup(entry);
12021 } else if (entry && *entry) {
12022 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12023 g_free(newmsginfo->to);
12024 newmsginfo->to = tmp;
12027 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12028 if ( newmsginfo->cc == NULL ) {
12029 newmsginfo->cc = g_strdup(entry);
12030 } else if (entry && *entry) {
12031 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12032 g_free(newmsginfo->cc);
12033 newmsginfo->cc = tmp;
12036 if ( strcasecmp(header,
12037 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12038 if ( newmsginfo->newsgroups == NULL ) {
12039 newmsginfo->newsgroups = g_strdup(entry);
12040 } else if (entry && *entry) {
12041 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12042 g_free(newmsginfo->newsgroups);
12043 newmsginfo->newsgroups = tmp;
12051 /* other data is unset */
12057 /* update compose's dictionaries from folder dict settings */
12058 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12059 FolderItem *folder_item)
12061 cm_return_if_fail(compose != NULL);
12063 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12064 FolderItemPrefs *prefs = folder_item->prefs;
12066 if (prefs->enable_default_dictionary)
12067 gtkaspell_change_dict(compose->gtkaspell,
12068 prefs->default_dictionary, FALSE);
12069 if (folder_item->prefs->enable_default_alt_dictionary)
12070 gtkaspell_change_alt_dict(compose->gtkaspell,
12071 prefs->default_alt_dictionary);
12072 if (prefs->enable_default_dictionary
12073 || prefs->enable_default_alt_dictionary)
12074 compose_spell_menu_changed(compose);
12079 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12081 Compose *compose = (Compose *)data;
12083 cm_return_if_fail(compose != NULL);
12085 gtk_widget_grab_focus(compose->text);
12088 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12090 gtk_combo_box_popup(GTK_COMBO_BOX(data));