2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2015 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
87 #include "quoted-printable.h"
91 #include "gtkshruler.h"
93 #include "alertpanel.h"
94 #include "manage_window.h"
96 #include "folder_item_prefs.h"
97 #include "addr_compl.h"
98 #include "quote_fmt.h"
100 #include "foldersel.h"
103 #include "message_search.h"
104 #include "combobox.h"
108 #include "autofaces.h"
109 #include "spell_entry.h"
122 #define N_ATTACH_COLS (N_COL_COLUMNS)
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
140 } ComposeCallAdvancedAction;
144 PRIORITY_HIGHEST = 1,
153 COMPOSE_INSERT_SUCCESS,
154 COMPOSE_INSERT_READ_ERROR,
155 COMPOSE_INSERT_INVALID_CHARACTER,
156 COMPOSE_INSERT_NO_FILE
157 } ComposeInsertResult;
161 COMPOSE_WRITE_FOR_SEND,
162 COMPOSE_WRITE_FOR_STORE
167 COMPOSE_QUOTE_FORCED,
174 SUBJECT_FIELD_PRESENT,
179 #define B64_LINE_SIZE 57
180 #define B64_BUFFSIZE 77
182 #define MAX_REFERENCES_LEN 999
184 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
185 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
187 static GList *compose_list = NULL;
188 static GSList *extra_headers = NULL;
190 static Compose *compose_generic_new (PrefsAccount *account,
194 GList *listAddress );
196 static Compose *compose_create (PrefsAccount *account,
201 static void compose_entry_mark_default_to (Compose *compose,
202 const gchar *address);
203 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
204 ComposeQuoteMode quote_mode,
208 static Compose *compose_forward_multiple (PrefsAccount *account,
209 GSList *msginfo_list);
210 static Compose *compose_reply (MsgInfo *msginfo,
211 ComposeQuoteMode quote_mode,
216 static Compose *compose_reply_mode (ComposeMode mode,
217 GSList *msginfo_list,
219 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
220 static void compose_update_privacy_systems_menu(Compose *compose);
222 static GtkWidget *compose_account_option_menu_create
224 static void compose_set_out_encoding (Compose *compose);
225 static void compose_set_template_menu (Compose *compose);
226 static void compose_destroy (Compose *compose);
228 static MailField compose_entries_set (Compose *compose,
230 ComposeEntryType to_type);
231 static gint compose_parse_header (Compose *compose,
233 static gint compose_parse_manual_headers (Compose *compose,
235 HeaderEntry *entries);
236 static gchar *compose_parse_references (const gchar *ref,
239 static gchar *compose_quote_fmt (Compose *compose,
245 gboolean need_unescape,
246 const gchar *err_msg);
248 static void compose_reply_set_entry (Compose *compose,
254 followup_and_reply_to);
255 static void compose_reedit_set_entry (Compose *compose,
258 static void compose_insert_sig (Compose *compose,
260 static ComposeInsertResult compose_insert_file (Compose *compose,
263 static gboolean compose_attach_append (Compose *compose,
266 const gchar *content_type,
267 const gchar *charset);
268 static void compose_attach_parts (Compose *compose,
271 static gboolean compose_beautify_paragraph (Compose *compose,
272 GtkTextIter *par_iter,
274 static void compose_wrap_all (Compose *compose);
275 static void compose_wrap_all_full (Compose *compose,
278 static void compose_set_title (Compose *compose);
279 static void compose_select_account (Compose *compose,
280 PrefsAccount *account,
283 static PrefsAccount *compose_current_mail_account(void);
284 /* static gint compose_send (Compose *compose); */
285 static gboolean compose_check_for_valid_recipient
287 static gboolean compose_check_entries (Compose *compose,
288 gboolean check_everything);
289 static gint compose_write_to_file (Compose *compose,
292 gboolean attach_parts);
293 static gint compose_write_body_to_file (Compose *compose,
295 static gint compose_remove_reedit_target (Compose *compose,
297 static void compose_remove_draft (Compose *compose);
298 static gint compose_queue_sub (Compose *compose,
302 gboolean check_subject,
303 gboolean remove_reedit_target);
304 static int compose_add_attachments (Compose *compose,
306 static gchar *compose_get_header (Compose *compose);
307 static gchar *compose_get_manual_headers_info (Compose *compose);
309 static void compose_convert_header (Compose *compose,
314 gboolean addr_field);
316 static void compose_attach_info_free (AttachInfo *ainfo);
317 static void compose_attach_remove_selected (GtkAction *action,
320 static void compose_template_apply (Compose *compose,
323 static void compose_attach_property (GtkAction *action,
325 static void compose_attach_property_create (gboolean *cancelled);
326 static void attach_property_ok (GtkWidget *widget,
327 gboolean *cancelled);
328 static void attach_property_cancel (GtkWidget *widget,
329 gboolean *cancelled);
330 static gint attach_property_delete_event (GtkWidget *widget,
332 gboolean *cancelled);
333 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 gboolean *cancelled);
337 static void compose_exec_ext_editor (Compose *compose);
339 static gint compose_exec_ext_editor_real (const gchar *file,
340 GdkNativeWindow socket_wid);
341 static gboolean compose_ext_editor_kill (Compose *compose);
342 static gboolean compose_input_cb (GIOChannel *source,
343 GIOCondition condition,
345 static void compose_set_ext_editor_sensitive (Compose *compose,
347 static gboolean compose_get_ext_editor_cmd_valid();
348 static gboolean compose_get_ext_editor_uses_socket();
349 static gboolean compose_ext_editor_plug_removed_cb
352 #endif /* G_OS_UNIX */
354 static void compose_undo_state_changed (UndoMain *undostruct,
359 static void compose_create_header_entry (Compose *compose);
360 static void compose_add_header_entry (Compose *compose, const gchar *header,
361 gchar *text, ComposePrefType pref_type);
362 static void compose_remove_header_entries(Compose *compose);
364 static void compose_update_priority_menu_item(Compose * compose);
366 static void compose_spell_menu_changed (void *data);
367 static void compose_dict_changed (void *data);
369 static void compose_add_field_list ( Compose *compose,
370 GList *listAddress );
372 /* callback functions */
374 static void compose_notebook_size_alloc (GtkNotebook *notebook,
375 GtkAllocation *allocation,
377 static gboolean compose_edit_size_alloc (GtkEditable *widget,
378 GtkAllocation *allocation,
379 GtkSHRuler *shruler);
380 static void account_activated (GtkComboBox *optmenu,
382 static void attach_selected (GtkTreeView *tree_view,
383 GtkTreePath *tree_path,
384 GtkTreeViewColumn *column,
386 static gboolean attach_button_pressed (GtkWidget *widget,
387 GdkEventButton *event,
389 static gboolean attach_key_pressed (GtkWidget *widget,
392 static void compose_send_cb (GtkAction *action, gpointer data);
393 static void compose_send_later_cb (GtkAction *action, gpointer data);
395 static void compose_save_cb (GtkAction *action,
398 static void compose_attach_cb (GtkAction *action,
400 static void compose_insert_file_cb (GtkAction *action,
402 static void compose_insert_sig_cb (GtkAction *action,
404 static void compose_replace_sig_cb (GtkAction *action,
407 static void compose_close_cb (GtkAction *action,
409 static void compose_print_cb (GtkAction *action,
412 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
414 static void compose_address_cb (GtkAction *action,
416 static void about_show_cb (GtkAction *action,
418 static void compose_template_activate_cb(GtkWidget *widget,
421 static void compose_ext_editor_cb (GtkAction *action,
424 static gint compose_delete_cb (GtkWidget *widget,
428 static void compose_undo_cb (GtkAction *action,
430 static void compose_redo_cb (GtkAction *action,
432 static void compose_cut_cb (GtkAction *action,
434 static void compose_copy_cb (GtkAction *action,
436 static void compose_paste_cb (GtkAction *action,
438 static void compose_paste_as_quote_cb (GtkAction *action,
440 static void compose_paste_no_wrap_cb (GtkAction *action,
442 static void compose_paste_wrap_cb (GtkAction *action,
444 static void compose_allsel_cb (GtkAction *action,
447 static void compose_advanced_action_cb (GtkAction *action,
450 static void compose_grab_focus_cb (GtkWidget *widget,
453 static void compose_changed_cb (GtkTextBuffer *textbuf,
456 static void compose_wrap_cb (GtkAction *action,
458 static void compose_wrap_all_cb (GtkAction *action,
460 static void compose_find_cb (GtkAction *action,
462 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
464 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
467 static void compose_toggle_ruler_cb (GtkToggleAction *action,
469 static void compose_toggle_sign_cb (GtkToggleAction *action,
471 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
473 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
474 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
475 static void activate_privacy_system (Compose *compose,
476 PrefsAccount *account,
478 static void compose_use_signing(Compose *compose, gboolean use_signing);
479 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
480 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
482 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
484 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
485 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
486 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
488 static void compose_attach_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_insert_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
504 static void compose_header_drag_received_cb (GtkWidget *widget,
505 GdkDragContext *drag_context,
508 GtkSelectionData *data,
513 static gboolean compose_drag_drop (GtkWidget *widget,
514 GdkDragContext *drag_context,
516 guint time, gpointer user_data);
517 static gboolean completion_set_focus_to_subject
522 static void text_inserted (GtkTextBuffer *buffer,
527 static Compose *compose_generic_reply(MsgInfo *msginfo,
528 ComposeQuoteMode quote_mode,
532 gboolean followup_and_reply_to,
535 static void compose_headerentry_changed_cb (GtkWidget *entry,
536 ComposeHeaderEntry *headerentry);
537 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
539 ComposeHeaderEntry *headerentry);
540 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
541 ComposeHeaderEntry *headerentry);
543 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
545 static void compose_allow_user_actions (Compose *compose, gboolean allow);
547 static void compose_nothing_cb (GtkAction *action, gpointer data)
553 static void compose_check_all (GtkAction *action, gpointer data);
554 static void compose_highlight_all (GtkAction *action, gpointer data);
555 static void compose_check_backwards (GtkAction *action, gpointer data);
556 static void compose_check_forwards_go (GtkAction *action, gpointer data);
559 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
561 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
564 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
565 FolderItem *folder_item);
567 static void compose_attach_update_label(Compose *compose);
568 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
569 gboolean respect_default_to);
570 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
571 static void from_name_activate_cb(GtkWidget *widget, gpointer data);
573 static GtkActionEntry compose_popup_entries[] =
575 {"Compose", NULL, "Compose" },
576 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
577 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
578 {"Compose/---", NULL, "---", NULL, NULL, NULL },
579 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
582 static GtkActionEntry compose_entries[] =
584 {"Menu", NULL, "Menu" },
586 {"Message", NULL, N_("_Message") },
587 {"Edit", NULL, N_("_Edit") },
589 {"Spelling", NULL, N_("_Spelling") },
591 {"Options", NULL, N_("_Options") },
592 {"Tools", NULL, N_("_Tools") },
593 {"Help", NULL, N_("_Help") },
595 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
596 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
597 {"Message/---", NULL, "---" },
599 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
600 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
601 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
602 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
603 /* {"Message/---", NULL, "---" }, */
604 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
605 /* {"Message/---", NULL, "---" }, */
606 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
607 /* {"Message/---", NULL, "---" }, */
608 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
611 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
612 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
613 {"Edit/---", NULL, "---" },
615 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
616 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
617 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
619 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
620 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
621 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
622 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
624 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
626 {"Edit/Advanced", NULL, N_("A_dvanced") },
627 {"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*/
628 {"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*/
629 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
630 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
631 {"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*/
632 {"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*/
633 {"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*/
634 {"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*/
635 {"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*/
636 {"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*/
637 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
638 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
639 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
640 {"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*/
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
645 /* {"Edit/---", NULL, "---" }, */
646 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
647 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
648 /* {"Edit/---", NULL, "---" }, */
649 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
652 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
653 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
654 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
655 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
657 {"Spelling/---", NULL, "---" },
658 {"Spelling/Options", NULL, N_("_Options") },
663 {"Options/ReplyMode", NULL, N_("Reply _mode") },
664 {"Options/---", NULL, "---" },
665 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
666 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
668 /* {"Options/---", NULL, "---" }, */
670 {"Options/Priority", NULL, N_("_Priority") },
672 {"Options/Encoding", NULL, N_("Character _encoding") },
673 {"Options/Encoding/---", NULL, "---" },
674 #define ENC_ACTION(cs_char,c_char,string) \
675 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
677 {"Options/Encoding/Western", NULL, N_("Western European") },
678 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
679 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
680 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
681 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
682 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
683 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
684 {"Options/Encoding/Korean", NULL, N_("Korean") },
685 {"Options/Encoding/Thai", NULL, N_("Thai") },
688 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
690 {"Tools/Template", NULL, N_("_Template") },
691 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
692 {"Tools/Actions", NULL, N_("Actio_ns") },
693 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
696 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
699 static GtkToggleActionEntry compose_toggle_entries[] =
701 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
702 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
703 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
704 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
705 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
706 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
707 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
710 static GtkRadioActionEntry compose_radio_rm_entries[] =
712 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
713 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
714 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
715 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
718 static GtkRadioActionEntry compose_radio_prio_entries[] =
720 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
721 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
722 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
723 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
724 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
727 static GtkRadioActionEntry compose_radio_enc_entries[] =
729 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Cyrillic/"CS_MACCYR, C_MACCYR, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
761 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
764 static GtkTargetEntry compose_mime_types[] =
766 {"text/uri-list", 0, 0},
767 {"UTF8_STRING", 0, 0},
771 static gboolean compose_put_existing_to_front(MsgInfo *info)
773 const GList *compose_list = compose_get_compose_list();
774 const GList *elem = NULL;
777 for (elem = compose_list; elem != NULL && elem->data != NULL;
779 Compose *c = (Compose*)elem->data;
781 if (!c->targetinfo || !c->targetinfo->msgid ||
785 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
786 gtkut_window_popup(c->window);
794 static GdkColor quote_color1 =
795 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
796 static GdkColor quote_color2 =
797 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor quote_color3 =
799 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
801 static GdkColor quote_bgcolor1 =
802 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
803 static GdkColor quote_bgcolor2 =
804 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
805 static GdkColor quote_bgcolor3 =
806 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
808 static GdkColor signature_color = {
815 static GdkColor uri_color = {
822 static void compose_create_tags(GtkTextView *text, Compose *compose)
824 GtkTextBuffer *buffer;
825 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
826 #if !GTK_CHECK_VERSION(2, 24, 0)
833 buffer = gtk_text_view_get_buffer(text);
835 if (prefs_common.enable_color) {
836 /* grab the quote colors, converting from an int to a GdkColor */
837 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
839 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
841 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
843 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
845 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
847 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
849 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
851 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
854 signature_color = quote_color1 = quote_color2 = quote_color3 =
855 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
858 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
859 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
860 "foreground-gdk", "e_color1,
861 "paragraph-background-gdk", "e_bgcolor1,
863 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
864 "foreground-gdk", "e_color2,
865 "paragraph-background-gdk", "e_bgcolor2,
867 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
868 "foreground-gdk", "e_color3,
869 "paragraph-background-gdk", "e_bgcolor3,
872 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
873 "foreground-gdk", "e_color1,
875 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
876 "foreground-gdk", "e_color2,
878 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
879 "foreground-gdk", "e_color3,
883 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
884 "foreground-gdk", &signature_color,
887 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
888 "foreground-gdk", &uri_color,
890 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
891 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
893 #if !GTK_CHECK_VERSION(2, 24, 0)
894 color[0] = quote_color1;
895 color[1] = quote_color2;
896 color[2] = quote_color3;
897 color[3] = quote_bgcolor1;
898 color[4] = quote_bgcolor2;
899 color[5] = quote_bgcolor3;
900 color[6] = signature_color;
901 color[7] = uri_color;
903 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
904 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
906 for (i = 0; i < 8; i++) {
907 if (success[i] == FALSE) {
908 g_warning("Compose: color allocation failed.");
909 quote_color1 = quote_color2 = quote_color3 =
910 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
911 signature_color = uri_color = black;
917 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
920 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
923 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
925 return compose_generic_new(account, mailto, item, NULL, NULL);
928 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
930 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
933 #define SCROLL_TO_CURSOR(compose) { \
934 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
935 gtk_text_view_get_buffer( \
936 GTK_TEXT_VIEW(compose->text))); \
937 gtk_text_view_scroll_mark_onscreen( \
938 GTK_TEXT_VIEW(compose->text), \
942 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
945 if (folderidentifier) {
946 #if !GTK_CHECK_VERSION(2, 24, 0)
947 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
949 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
951 prefs_common.compose_save_to_history = add_history(
952 prefs_common.compose_save_to_history, folderidentifier);
953 #if !GTK_CHECK_VERSION(2, 24, 0)
954 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
955 prefs_common.compose_save_to_history);
957 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
958 prefs_common.compose_save_to_history);
962 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
963 if (folderidentifier)
964 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
966 gtk_entry_set_text(GTK_ENTRY(entry), "");
969 static gchar *compose_get_save_to(Compose *compose)
972 gchar *result = NULL;
973 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
974 result = gtk_editable_get_chars(entry, 0, -1);
977 #if !GTK_CHECK_VERSION(2, 24, 0)
978 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
980 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
982 prefs_common.compose_save_to_history = add_history(
983 prefs_common.compose_save_to_history, result);
984 #if !GTK_CHECK_VERSION(2, 24, 0)
985 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
986 prefs_common.compose_save_to_history);
988 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
989 prefs_common.compose_save_to_history);
995 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
996 GList *attach_files, GList *listAddress )
999 GtkTextView *textview;
1000 GtkTextBuffer *textbuf;
1002 const gchar *subject_format = NULL;
1003 const gchar *body_format = NULL;
1004 gchar *mailto_from = NULL;
1005 PrefsAccount *mailto_account = NULL;
1006 MsgInfo* dummyinfo = NULL;
1007 gint cursor_pos = -1;
1008 MailField mfield = NO_FIELD_PRESENT;
1012 /* check if mailto defines a from */
1013 if (mailto && *mailto != '\0') {
1014 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1015 /* mailto defines a from, check if we can get account prefs from it,
1016 if not, the account prefs will be guessed using other ways, but we'll keep
1019 mailto_account = account_find_from_address(mailto_from, TRUE);
1020 if (mailto_account == NULL) {
1022 Xstrdup_a(tmp_from, mailto_from, return NULL);
1023 extract_address(tmp_from);
1024 mailto_account = account_find_from_address(tmp_from, TRUE);
1028 account = mailto_account;
1031 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1032 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1033 account = account_find_from_id(item->prefs->default_account);
1035 /* if no account prefs set, fallback to the current one */
1036 if (!account) account = cur_account;
1037 cm_return_val_if_fail(account != NULL, NULL);
1039 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1041 /* override from name if mailto asked for it */
1043 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1044 g_free(mailto_from);
1046 /* override from name according to folder properties */
1047 if (item && item->prefs &&
1048 item->prefs->compose_with_format &&
1049 item->prefs->compose_override_from_format &&
1050 *item->prefs->compose_override_from_format != '\0') {
1055 dummyinfo = compose_msginfo_new_from_compose(compose);
1057 /* decode \-escape sequences in the internal representation of the quote format */
1058 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1059 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1062 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1063 compose->gtkaspell);
1065 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1067 quote_fmt_scan_string(tmp);
1070 buf = quote_fmt_get_buffer();
1072 alertpanel_error(_("New message From format error."));
1074 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1075 quote_fmt_reset_vartable();
1080 compose->replyinfo = NULL;
1081 compose->fwdinfo = NULL;
1083 textview = GTK_TEXT_VIEW(compose->text);
1084 textbuf = gtk_text_view_get_buffer(textview);
1085 compose_create_tags(textview, compose);
1087 undo_block(compose->undostruct);
1089 compose_set_dictionaries_from_folder_prefs(compose, item);
1092 if (account->auto_sig)
1093 compose_insert_sig(compose, FALSE);
1094 gtk_text_buffer_get_start_iter(textbuf, &iter);
1095 gtk_text_buffer_place_cursor(textbuf, &iter);
1097 if (account->protocol != A_NNTP) {
1098 if (mailto && *mailto != '\0') {
1099 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1102 compose_set_folder_prefs(compose, item, TRUE);
1104 if (item && item->ret_rcpt) {
1105 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1108 if (mailto && *mailto != '\0') {
1109 if (!strchr(mailto, '@'))
1110 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1112 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1113 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1114 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1115 mfield = TO_FIELD_PRESENT;
1118 * CLAWS: just don't allow return receipt request, even if the user
1119 * may want to send an email. simple but foolproof.
1121 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1123 compose_add_field_list( compose, listAddress );
1125 if (item && item->prefs && item->prefs->compose_with_format) {
1126 subject_format = item->prefs->compose_subject_format;
1127 body_format = item->prefs->compose_body_format;
1128 } else if (account->compose_with_format) {
1129 subject_format = account->compose_subject_format;
1130 body_format = account->compose_body_format;
1131 } else if (prefs_common.compose_with_format) {
1132 subject_format = prefs_common.compose_subject_format;
1133 body_format = prefs_common.compose_body_format;
1136 if (subject_format || body_format) {
1139 && *subject_format != '\0' )
1141 gchar *subject = NULL;
1146 dummyinfo = compose_msginfo_new_from_compose(compose);
1148 /* decode \-escape sequences in the internal representation of the quote format */
1149 tmp = g_malloc(strlen(subject_format)+1);
1150 pref_get_unescaped_pref(tmp, subject_format);
1152 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1154 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1155 compose->gtkaspell);
1157 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1159 quote_fmt_scan_string(tmp);
1162 buf = quote_fmt_get_buffer();
1164 alertpanel_error(_("New message subject format error."));
1166 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1167 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1168 quote_fmt_reset_vartable();
1172 mfield = SUBJECT_FIELD_PRESENT;
1176 && *body_format != '\0' )
1179 GtkTextBuffer *buffer;
1180 GtkTextIter start, end;
1184 dummyinfo = compose_msginfo_new_from_compose(compose);
1186 text = GTK_TEXT_VIEW(compose->text);
1187 buffer = gtk_text_view_get_buffer(text);
1188 gtk_text_buffer_get_start_iter(buffer, &start);
1189 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1190 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1192 compose_quote_fmt(compose, dummyinfo,
1194 NULL, tmp, FALSE, TRUE,
1195 _("The body of the \"New message\" template has an error at line %d."));
1196 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1197 quote_fmt_reset_vartable();
1201 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1202 gtkaspell_highlight_all(compose->gtkaspell);
1204 mfield = BODY_FIELD_PRESENT;
1208 procmsg_msginfo_free( dummyinfo );
1214 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1215 ainfo = (AttachInfo *) curr->data;
1216 compose_attach_append(compose, ainfo->file, ainfo->file,
1217 ainfo->content_type, ainfo->charset);
1221 compose_show_first_last_header(compose, TRUE);
1223 /* Set save folder */
1224 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1225 gchar *folderidentifier;
1227 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1228 folderidentifier = folder_item_get_identifier(item);
1229 compose_set_save_to(compose, folderidentifier);
1230 g_free(folderidentifier);
1233 /* Place cursor according to provided input (mfield) */
1235 case NO_FIELD_PRESENT:
1236 if (compose->header_last)
1237 gtk_widget_grab_focus(compose->header_last->entry);
1239 case TO_FIELD_PRESENT:
1240 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1242 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1245 gtk_widget_grab_focus(compose->subject_entry);
1247 case SUBJECT_FIELD_PRESENT:
1248 textview = GTK_TEXT_VIEW(compose->text);
1251 textbuf = gtk_text_view_get_buffer(textview);
1254 mark = gtk_text_buffer_get_insert(textbuf);
1255 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1256 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1258 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1259 * only defers where it comes to the variable body
1260 * is not null. If no body is present compose->text
1261 * will be null in which case you cannot place the
1262 * cursor inside the component so. An empty component
1263 * is therefore created before placing the cursor
1265 case BODY_FIELD_PRESENT:
1266 cursor_pos = quote_fmt_get_cursor_pos();
1267 if (cursor_pos == -1)
1268 gtk_widget_grab_focus(compose->header_last->entry);
1270 gtk_widget_grab_focus(compose->text);
1274 undo_unblock(compose->undostruct);
1276 if (prefs_common.auto_exteditor)
1277 compose_exec_ext_editor(compose);
1279 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1281 SCROLL_TO_CURSOR(compose);
1283 compose->modified = FALSE;
1284 compose_set_title(compose);
1286 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1291 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1292 gboolean override_pref, const gchar *system)
1294 const gchar *privacy = NULL;
1296 cm_return_if_fail(compose != NULL);
1297 cm_return_if_fail(account != NULL);
1299 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1302 if (account->default_privacy_system && strlen(account->default_privacy_system))
1303 privacy = account->default_privacy_system;
1307 GSList *privacy_avail = privacy_get_system_ids();
1308 if (privacy_avail && g_slist_length(privacy_avail)) {
1309 privacy = (gchar *)(privacy_avail->data);
1312 if (privacy != NULL) {
1314 g_free(compose->privacy_system);
1315 compose->privacy_system = NULL;
1316 g_free(compose->encdata);
1317 compose->encdata = NULL;
1319 if (compose->privacy_system == NULL)
1320 compose->privacy_system = g_strdup(privacy);
1321 else if (*(compose->privacy_system) == '\0') {
1322 g_free(compose->privacy_system);
1323 g_free(compose->encdata);
1324 compose->encdata = NULL;
1325 compose->privacy_system = g_strdup(privacy);
1327 compose_update_privacy_system_menu_item(compose, FALSE);
1328 compose_use_encryption(compose, TRUE);
1332 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1334 const gchar *privacy = NULL;
1336 if (account->default_privacy_system && strlen(account->default_privacy_system))
1337 privacy = account->default_privacy_system;
1341 GSList *privacy_avail = privacy_get_system_ids();
1342 if (privacy_avail && g_slist_length(privacy_avail)) {
1343 privacy = (gchar *)(privacy_avail->data);
1347 if (privacy != NULL) {
1349 g_free(compose->privacy_system);
1350 compose->privacy_system = NULL;
1351 g_free(compose->encdata);
1352 compose->encdata = NULL;
1354 if (compose->privacy_system == NULL)
1355 compose->privacy_system = g_strdup(privacy);
1356 compose_update_privacy_system_menu_item(compose, FALSE);
1357 compose_use_signing(compose, TRUE);
1361 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1365 Compose *compose = NULL;
1367 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1369 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1370 cm_return_val_if_fail(msginfo != NULL, NULL);
1372 list_len = g_slist_length(msginfo_list);
1376 case COMPOSE_REPLY_TO_ADDRESS:
1377 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1378 FALSE, prefs_common.default_reply_list, FALSE, body);
1380 case COMPOSE_REPLY_WITH_QUOTE:
1381 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1382 FALSE, prefs_common.default_reply_list, FALSE, body);
1384 case COMPOSE_REPLY_WITHOUT_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1386 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1388 case COMPOSE_REPLY_TO_SENDER:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1390 FALSE, FALSE, TRUE, body);
1392 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1393 compose = compose_followup_and_reply_to(msginfo,
1394 COMPOSE_QUOTE_CHECK,
1395 FALSE, FALSE, body);
1397 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1399 FALSE, FALSE, TRUE, body);
1401 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1403 FALSE, FALSE, TRUE, NULL);
1405 case COMPOSE_REPLY_TO_ALL:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1407 TRUE, FALSE, FALSE, body);
1409 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1410 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1411 TRUE, FALSE, FALSE, body);
1413 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1414 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1415 TRUE, FALSE, FALSE, NULL);
1417 case COMPOSE_REPLY_TO_LIST:
1418 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1419 FALSE, TRUE, FALSE, body);
1421 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1422 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1423 FALSE, TRUE, FALSE, body);
1425 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1426 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1427 FALSE, TRUE, FALSE, NULL);
1429 case COMPOSE_FORWARD:
1430 if (prefs_common.forward_as_attachment) {
1431 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1434 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1438 case COMPOSE_FORWARD_INLINE:
1439 /* check if we reply to more than one Message */
1440 if (list_len == 1) {
1441 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1444 /* more messages FALL THROUGH */
1445 case COMPOSE_FORWARD_AS_ATTACH:
1446 compose = compose_forward_multiple(NULL, msginfo_list);
1448 case COMPOSE_REDIRECT:
1449 compose = compose_redirect(NULL, msginfo, FALSE);
1452 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
1455 if (compose == NULL) {
1456 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1460 compose->rmode = mode;
1461 switch (compose->rmode) {
1463 case COMPOSE_REPLY_WITH_QUOTE:
1464 case COMPOSE_REPLY_WITHOUT_QUOTE:
1465 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1466 debug_print("reply mode Normal\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1468 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1470 case COMPOSE_REPLY_TO_SENDER:
1471 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1472 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1473 debug_print("reply mode Sender\n");
1474 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1476 case COMPOSE_REPLY_TO_ALL:
1477 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1478 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1479 debug_print("reply mode All\n");
1480 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1482 case COMPOSE_REPLY_TO_LIST:
1483 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1484 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1485 debug_print("reply mode List\n");
1486 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1488 case COMPOSE_REPLY_TO_ADDRESS:
1489 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1497 static Compose *compose_reply(MsgInfo *msginfo,
1498 ComposeQuoteMode quote_mode,
1504 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1505 to_sender, FALSE, body);
1508 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1509 ComposeQuoteMode quote_mode,
1514 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1515 to_sender, TRUE, body);
1518 static void compose_extract_original_charset(Compose *compose)
1520 MsgInfo *info = NULL;
1521 if (compose->replyinfo) {
1522 info = compose->replyinfo;
1523 } else if (compose->fwdinfo) {
1524 info = compose->fwdinfo;
1525 } else if (compose->targetinfo) {
1526 info = compose->targetinfo;
1529 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1530 MimeInfo *partinfo = mimeinfo;
1531 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1532 partinfo = procmime_mimeinfo_next(partinfo);
1534 compose->orig_charset =
1535 g_strdup(procmime_mimeinfo_get_parameter(
1536 partinfo, "charset"));
1538 procmime_mimeinfo_free_all(mimeinfo);
1542 #define SIGNAL_BLOCK(buffer) { \
1543 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1544 G_CALLBACK(compose_changed_cb), \
1546 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1547 G_CALLBACK(text_inserted), \
1551 #define SIGNAL_UNBLOCK(buffer) { \
1552 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1553 G_CALLBACK(compose_changed_cb), \
1555 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1556 G_CALLBACK(text_inserted), \
1560 static Compose *compose_generic_reply(MsgInfo *msginfo,
1561 ComposeQuoteMode quote_mode,
1562 gboolean to_all, gboolean to_ml,
1564 gboolean followup_and_reply_to,
1568 PrefsAccount *account = NULL;
1569 GtkTextView *textview;
1570 GtkTextBuffer *textbuf;
1571 gboolean quote = FALSE;
1572 const gchar *qmark = NULL;
1573 const gchar *body_fmt = NULL;
1574 gchar *s_system = NULL;
1576 cm_return_val_if_fail(msginfo != NULL, NULL);
1577 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1579 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1581 cm_return_val_if_fail(account != NULL, NULL);
1583 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1585 compose->updating = TRUE;
1587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1590 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1591 if (!compose->replyinfo)
1592 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1594 compose_extract_original_charset(compose);
1596 if (msginfo->folder && msginfo->folder->ret_rcpt)
1597 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1599 /* Set save folder */
1600 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1601 gchar *folderidentifier;
1603 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1604 folderidentifier = folder_item_get_identifier(msginfo->folder);
1605 compose_set_save_to(compose, folderidentifier);
1606 g_free(folderidentifier);
1609 if (compose_parse_header(compose, msginfo) < 0) {
1610 compose->updating = FALSE;
1611 compose_destroy(compose);
1615 /* override from name according to folder properties */
1616 if (msginfo->folder && msginfo->folder->prefs &&
1617 msginfo->folder->prefs->reply_with_format &&
1618 msginfo->folder->prefs->reply_override_from_format &&
1619 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1624 /* decode \-escape sequences in the internal representation of the quote format */
1625 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1626 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1629 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1630 compose->gtkaspell);
1632 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1634 quote_fmt_scan_string(tmp);
1637 buf = quote_fmt_get_buffer();
1639 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1641 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1642 quote_fmt_reset_vartable();
1647 textview = (GTK_TEXT_VIEW(compose->text));
1648 textbuf = gtk_text_view_get_buffer(textview);
1649 compose_create_tags(textview, compose);
1651 undo_block(compose->undostruct);
1653 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1654 gtkaspell_block_check(compose->gtkaspell);
1657 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1658 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1659 /* use the reply format of folder (if enabled), or the account's one
1660 (if enabled) or fallback to the global reply format, which is always
1661 enabled (even if empty), and use the relevant quotemark */
1663 if (msginfo->folder && msginfo->folder->prefs &&
1664 msginfo->folder->prefs->reply_with_format) {
1665 qmark = msginfo->folder->prefs->reply_quotemark;
1666 body_fmt = msginfo->folder->prefs->reply_body_format;
1668 } else if (account->reply_with_format) {
1669 qmark = account->reply_quotemark;
1670 body_fmt = account->reply_body_format;
1673 qmark = prefs_common.quotemark;
1674 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1675 body_fmt = gettext(prefs_common.quotefmt);
1682 /* empty quotemark is not allowed */
1683 if (qmark == NULL || *qmark == '\0')
1685 compose_quote_fmt(compose, compose->replyinfo,
1686 body_fmt, qmark, body, FALSE, TRUE,
1687 _("The body of the \"Reply\" template has an error at line %d."));
1688 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1689 quote_fmt_reset_vartable();
1692 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1693 compose_force_encryption(compose, account, FALSE, s_system);
1696 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1697 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1698 compose_force_signing(compose, account, s_system);
1702 SIGNAL_BLOCK(textbuf);
1704 if (account->auto_sig)
1705 compose_insert_sig(compose, FALSE);
1707 compose_wrap_all(compose);
1710 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1711 gtkaspell_highlight_all(compose->gtkaspell);
1712 gtkaspell_unblock_check(compose->gtkaspell);
1714 SIGNAL_UNBLOCK(textbuf);
1716 gtk_widget_grab_focus(compose->text);
1718 undo_unblock(compose->undostruct);
1720 if (prefs_common.auto_exteditor)
1721 compose_exec_ext_editor(compose);
1723 compose->modified = FALSE;
1724 compose_set_title(compose);
1726 compose->updating = FALSE;
1727 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1728 SCROLL_TO_CURSOR(compose);
1730 if (compose->deferred_destroy) {
1731 compose_destroy(compose);
1739 #define INSERT_FW_HEADER(var, hdr) \
1740 if (msginfo->var && *msginfo->var) { \
1741 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1743 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1746 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1747 gboolean as_attach, const gchar *body,
1748 gboolean no_extedit,
1752 GtkTextView *textview;
1753 GtkTextBuffer *textbuf;
1754 gint cursor_pos = -1;
1757 cm_return_val_if_fail(msginfo != NULL, NULL);
1758 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1761 !(account = compose_guess_forward_account_from_msginfo
1763 account = cur_account;
1765 if (!prefs_common.forward_as_attachment)
1766 mode = COMPOSE_FORWARD_INLINE;
1768 mode = COMPOSE_FORWARD;
1769 compose = compose_create(account, msginfo->folder, mode, batch);
1771 compose->updating = TRUE;
1772 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1773 if (!compose->fwdinfo)
1774 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1776 compose_extract_original_charset(compose);
1778 if (msginfo->subject && *msginfo->subject) {
1779 gchar *buf, *buf2, *p;
1781 buf = p = g_strdup(msginfo->subject);
1782 p += subject_get_prefix_length(p);
1783 memmove(buf, p, strlen(p) + 1);
1785 buf2 = g_strdup_printf("Fw: %s", buf);
1786 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1792 /* override from name according to folder properties */
1793 if (msginfo->folder && msginfo->folder->prefs &&
1794 msginfo->folder->prefs->forward_with_format &&
1795 msginfo->folder->prefs->forward_override_from_format &&
1796 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1800 MsgInfo *full_msginfo = NULL;
1803 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1805 full_msginfo = procmsg_msginfo_copy(msginfo);
1807 /* decode \-escape sequences in the internal representation of the quote format */
1808 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1809 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1812 gtkaspell_block_check(compose->gtkaspell);
1813 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1814 compose->gtkaspell);
1816 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1818 quote_fmt_scan_string(tmp);
1821 buf = quote_fmt_get_buffer();
1823 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1825 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1826 quote_fmt_reset_vartable();
1829 procmsg_msginfo_free(full_msginfo);
1832 textview = GTK_TEXT_VIEW(compose->text);
1833 textbuf = gtk_text_view_get_buffer(textview);
1834 compose_create_tags(textview, compose);
1836 undo_block(compose->undostruct);
1840 msgfile = procmsg_get_message_file(msginfo);
1841 if (!is_file_exist(msgfile))
1842 g_warning("%s: file does not exist", msgfile);
1844 compose_attach_append(compose, msgfile, msgfile,
1845 "message/rfc822", NULL);
1849 const gchar *qmark = NULL;
1850 const gchar *body_fmt = NULL;
1851 MsgInfo *full_msginfo;
1853 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1855 full_msginfo = procmsg_msginfo_copy(msginfo);
1857 /* use the forward format of folder (if enabled), or the account's one
1858 (if enabled) or fallback to the global forward format, which is always
1859 enabled (even if empty), and use the relevant quotemark */
1860 if (msginfo->folder && msginfo->folder->prefs &&
1861 msginfo->folder->prefs->forward_with_format) {
1862 qmark = msginfo->folder->prefs->forward_quotemark;
1863 body_fmt = msginfo->folder->prefs->forward_body_format;
1865 } else if (account->forward_with_format) {
1866 qmark = account->forward_quotemark;
1867 body_fmt = account->forward_body_format;
1870 qmark = prefs_common.fw_quotemark;
1871 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1872 body_fmt = gettext(prefs_common.fw_quotefmt);
1877 /* empty quotemark is not allowed */
1878 if (qmark == NULL || *qmark == '\0')
1881 compose_quote_fmt(compose, full_msginfo,
1882 body_fmt, qmark, body, FALSE, TRUE,
1883 _("The body of the \"Forward\" template has an error at line %d."));
1884 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1885 quote_fmt_reset_vartable();
1886 compose_attach_parts(compose, msginfo);
1888 procmsg_msginfo_free(full_msginfo);
1891 SIGNAL_BLOCK(textbuf);
1893 if (account->auto_sig)
1894 compose_insert_sig(compose, FALSE);
1896 compose_wrap_all(compose);
1899 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1900 gtkaspell_highlight_all(compose->gtkaspell);
1901 gtkaspell_unblock_check(compose->gtkaspell);
1903 SIGNAL_UNBLOCK(textbuf);
1905 cursor_pos = quote_fmt_get_cursor_pos();
1906 if (cursor_pos == -1)
1907 gtk_widget_grab_focus(compose->header_last->entry);
1909 gtk_widget_grab_focus(compose->text);
1911 if (!no_extedit && prefs_common.auto_exteditor)
1912 compose_exec_ext_editor(compose);
1915 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1916 gchar *folderidentifier;
1918 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1919 folderidentifier = folder_item_get_identifier(msginfo->folder);
1920 compose_set_save_to(compose, folderidentifier);
1921 g_free(folderidentifier);
1924 undo_unblock(compose->undostruct);
1926 compose->modified = FALSE;
1927 compose_set_title(compose);
1929 compose->updating = FALSE;
1930 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1931 SCROLL_TO_CURSOR(compose);
1933 if (compose->deferred_destroy) {
1934 compose_destroy(compose);
1938 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1943 #undef INSERT_FW_HEADER
1945 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1948 GtkTextView *textview;
1949 GtkTextBuffer *textbuf;
1953 gboolean single_mail = TRUE;
1955 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1957 if (g_slist_length(msginfo_list) > 1)
1958 single_mail = FALSE;
1960 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1961 if (((MsgInfo *)msginfo->data)->folder == NULL)
1964 /* guess account from first selected message */
1966 !(account = compose_guess_forward_account_from_msginfo
1967 (msginfo_list->data)))
1968 account = cur_account;
1970 cm_return_val_if_fail(account != NULL, NULL);
1972 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1973 if (msginfo->data) {
1974 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1975 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1979 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1980 g_warning("no msginfo_list");
1984 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1986 compose->updating = TRUE;
1988 /* override from name according to folder properties */
1989 if (msginfo_list->data) {
1990 MsgInfo *msginfo = msginfo_list->data;
1992 if (msginfo->folder && msginfo->folder->prefs &&
1993 msginfo->folder->prefs->forward_with_format &&
1994 msginfo->folder->prefs->forward_override_from_format &&
1995 *msginfo->folder->prefs->forward_override_from_format != '\0') {
2000 /* decode \-escape sequences in the internal representation of the quote format */
2001 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
2002 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
2005 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2006 compose->gtkaspell);
2008 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2010 quote_fmt_scan_string(tmp);
2013 buf = quote_fmt_get_buffer();
2015 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2017 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2018 quote_fmt_reset_vartable();
2024 textview = GTK_TEXT_VIEW(compose->text);
2025 textbuf = gtk_text_view_get_buffer(textview);
2026 compose_create_tags(textview, compose);
2028 undo_block(compose->undostruct);
2029 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2030 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2032 if (!is_file_exist(msgfile))
2033 g_warning("%s: file does not exist", msgfile);
2035 compose_attach_append(compose, msgfile, msgfile,
2036 "message/rfc822", NULL);
2041 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2042 if (info->subject && *info->subject) {
2043 gchar *buf, *buf2, *p;
2045 buf = p = g_strdup(info->subject);
2046 p += subject_get_prefix_length(p);
2047 memmove(buf, p, strlen(p) + 1);
2049 buf2 = g_strdup_printf("Fw: %s", buf);
2050 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2056 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2057 _("Fw: multiple emails"));
2060 SIGNAL_BLOCK(textbuf);
2062 if (account->auto_sig)
2063 compose_insert_sig(compose, FALSE);
2065 compose_wrap_all(compose);
2067 SIGNAL_UNBLOCK(textbuf);
2069 gtk_text_buffer_get_start_iter(textbuf, &iter);
2070 gtk_text_buffer_place_cursor(textbuf, &iter);
2072 gtk_widget_grab_focus(compose->header_last->entry);
2073 undo_unblock(compose->undostruct);
2074 compose->modified = FALSE;
2075 compose_set_title(compose);
2077 compose->updating = FALSE;
2078 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2079 SCROLL_TO_CURSOR(compose);
2081 if (compose->deferred_destroy) {
2082 compose_destroy(compose);
2086 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2091 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2093 GtkTextIter start = *iter;
2094 GtkTextIter end_iter;
2095 int start_pos = gtk_text_iter_get_offset(&start);
2097 if (!compose->account->sig_sep)
2100 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2101 start_pos+strlen(compose->account->sig_sep));
2103 /* check sig separator */
2104 str = gtk_text_iter_get_text(&start, &end_iter);
2105 if (!strcmp(str, compose->account->sig_sep)) {
2107 /* check end of line (\n) */
2108 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2109 start_pos+strlen(compose->account->sig_sep));
2110 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2111 start_pos+strlen(compose->account->sig_sep)+1);
2112 tmp = gtk_text_iter_get_text(&start, &end_iter);
2113 if (!strcmp(tmp,"\n")) {
2125 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2127 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2128 Compose *compose = (Compose *)data;
2129 FolderItem *old_item = NULL;
2130 FolderItem *new_item = NULL;
2131 gchar *old_id, *new_id;
2133 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2134 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2137 old_item = hookdata->item;
2138 new_item = hookdata->item2;
2140 old_id = folder_item_get_identifier(old_item);
2141 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2143 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2144 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2145 compose->targetinfo->folder = new_item;
2148 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2149 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2150 compose->replyinfo->folder = new_item;
2153 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2154 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2155 compose->fwdinfo->folder = new_item;
2163 static void compose_colorize_signature(Compose *compose)
2165 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2167 GtkTextIter end_iter;
2168 gtk_text_buffer_get_start_iter(buffer, &iter);
2169 while (gtk_text_iter_forward_line(&iter))
2170 if (compose_is_sig_separator(compose, buffer, &iter)) {
2171 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2172 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2176 #define BLOCK_WRAP() { \
2177 prev_autowrap = compose->autowrap; \
2178 buffer = gtk_text_view_get_buffer( \
2179 GTK_TEXT_VIEW(compose->text)); \
2180 compose->autowrap = FALSE; \
2182 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2183 G_CALLBACK(compose_changed_cb), \
2185 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(text_inserted), \
2189 #define UNBLOCK_WRAP() { \
2190 compose->autowrap = prev_autowrap; \
2191 if (compose->autowrap) { \
2192 gint old = compose->draft_timeout_tag; \
2193 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2194 compose_wrap_all(compose); \
2195 compose->draft_timeout_tag = old; \
2198 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2199 G_CALLBACK(compose_changed_cb), \
2201 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2202 G_CALLBACK(text_inserted), \
2206 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2208 Compose *compose = NULL;
2209 PrefsAccount *account = NULL;
2210 GtkTextView *textview;
2211 GtkTextBuffer *textbuf;
2215 gchar buf[BUFFSIZE];
2216 gboolean use_signing = FALSE;
2217 gboolean use_encryption = FALSE;
2218 gchar *privacy_system = NULL;
2219 int priority = PRIORITY_NORMAL;
2220 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2221 gboolean autowrap = prefs_common.autowrap;
2222 gboolean autoindent = prefs_common.auto_indent;
2223 HeaderEntry *manual_headers = NULL;
2225 cm_return_val_if_fail(msginfo != NULL, NULL);
2226 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2228 if (compose_put_existing_to_front(msginfo)) {
2232 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2233 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2234 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2235 gchar queueheader_buf[BUFFSIZE];
2238 /* Select Account from queue headers */
2239 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2240 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2241 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2242 account = account_find_from_id(id);
2244 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2245 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2246 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2247 account = account_find_from_id(id);
2249 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2250 sizeof(queueheader_buf), "NAID:")) {
2251 id = atoi(&queueheader_buf[strlen("NAID:")]);
2252 account = account_find_from_id(id);
2254 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2255 sizeof(queueheader_buf), "MAID:")) {
2256 id = atoi(&queueheader_buf[strlen("MAID:")]);
2257 account = account_find_from_id(id);
2259 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2260 sizeof(queueheader_buf), "S:")) {
2261 account = account_find_from_address(queueheader_buf, FALSE);
2263 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2264 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2265 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2266 use_signing = param;
2269 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2270 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2271 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2272 use_signing = param;
2275 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2276 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2277 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2278 use_encryption = param;
2280 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2281 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2282 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2283 use_encryption = param;
2285 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2286 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2287 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2290 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2291 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2292 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2295 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2296 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2297 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2299 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2300 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2301 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2303 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2304 sizeof(queueheader_buf), "X-Priority: ")) {
2305 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2308 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2309 sizeof(queueheader_buf), "RMID:")) {
2310 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2311 if (tokens[0] && tokens[1] && tokens[2]) {
2312 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2313 if (orig_item != NULL) {
2314 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2319 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2320 sizeof(queueheader_buf), "FMID:")) {
2321 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2322 if (tokens[0] && tokens[1] && tokens[2]) {
2323 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2324 if (orig_item != NULL) {
2325 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2330 /* Get manual headers */
2331 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2332 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2333 if (*listmh != '\0') {
2334 debug_print("Got manual headers: %s\n", listmh);
2335 manual_headers = procheader_entries_from_str(listmh);
2340 account = msginfo->folder->folder->account;
2343 if (!account && prefs_common.reedit_account_autosel) {
2344 gchar from[BUFFSIZE];
2345 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2346 extract_address(from);
2347 account = account_find_from_address(from, FALSE);
2351 account = cur_account;
2353 cm_return_val_if_fail(account != NULL, NULL);
2355 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2357 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2358 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2359 compose->autowrap = autowrap;
2360 compose->replyinfo = replyinfo;
2361 compose->fwdinfo = fwdinfo;
2363 compose->updating = TRUE;
2364 compose->priority = priority;
2366 if (privacy_system != NULL) {
2367 compose->privacy_system = privacy_system;
2368 compose_use_signing(compose, use_signing);
2369 compose_use_encryption(compose, use_encryption);
2370 compose_update_privacy_system_menu_item(compose, FALSE);
2372 activate_privacy_system(compose, account, FALSE);
2375 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2377 compose_extract_original_charset(compose);
2379 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2380 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2381 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2382 gchar queueheader_buf[BUFFSIZE];
2384 /* Set message save folder */
2385 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2386 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2387 compose_set_save_to(compose, &queueheader_buf[4]);
2389 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2390 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2392 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2397 if (compose_parse_header(compose, msginfo) < 0) {
2398 compose->updating = FALSE;
2399 compose_destroy(compose);
2402 compose_reedit_set_entry(compose, msginfo);
2404 textview = GTK_TEXT_VIEW(compose->text);
2405 textbuf = gtk_text_view_get_buffer(textview);
2406 compose_create_tags(textview, compose);
2408 mark = gtk_text_buffer_get_insert(textbuf);
2409 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2411 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2412 G_CALLBACK(compose_changed_cb),
2415 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2416 fp = procmime_get_first_encrypted_text_content(msginfo);
2418 compose_force_encryption(compose, account, TRUE, NULL);
2421 fp = procmime_get_first_text_content(msginfo);
2424 g_warning("Can't get text part");
2428 gboolean prev_autowrap;
2429 GtkTextBuffer *buffer;
2431 while (fgets(buf, sizeof(buf), fp) != NULL) {
2433 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2439 compose_attach_parts(compose, msginfo);
2441 compose_colorize_signature(compose);
2443 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2444 G_CALLBACK(compose_changed_cb),
2447 if (manual_headers != NULL) {
2448 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2449 procheader_entries_free(manual_headers);
2450 compose->updating = FALSE;
2451 compose_destroy(compose);
2454 procheader_entries_free(manual_headers);
2457 gtk_widget_grab_focus(compose->text);
2459 if (prefs_common.auto_exteditor) {
2460 compose_exec_ext_editor(compose);
2462 compose->modified = FALSE;
2463 compose_set_title(compose);
2465 compose->updating = FALSE;
2466 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2467 SCROLL_TO_CURSOR(compose);
2469 if (compose->deferred_destroy) {
2470 compose_destroy(compose);
2474 compose->sig_str = account_get_signature_str(compose->account);
2476 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2481 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2488 cm_return_val_if_fail(msginfo != NULL, NULL);
2491 account = account_get_reply_account(msginfo,
2492 prefs_common.reply_account_autosel);
2493 cm_return_val_if_fail(account != NULL, NULL);
2495 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2497 compose->updating = TRUE;
2499 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2500 compose->replyinfo = NULL;
2501 compose->fwdinfo = NULL;
2503 compose_show_first_last_header(compose, TRUE);
2505 gtk_widget_grab_focus(compose->header_last->entry);
2507 filename = procmsg_get_message_file(msginfo);
2509 if (filename == NULL) {
2510 compose->updating = FALSE;
2511 compose_destroy(compose);
2516 compose->redirect_filename = filename;
2518 /* Set save folder */
2519 item = msginfo->folder;
2520 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2521 gchar *folderidentifier;
2523 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2524 folderidentifier = folder_item_get_identifier(item);
2525 compose_set_save_to(compose, folderidentifier);
2526 g_free(folderidentifier);
2529 compose_attach_parts(compose, msginfo);
2531 if (msginfo->subject)
2532 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2534 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2536 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2537 _("The body of the \"Redirect\" template has an error at line %d."));
2538 quote_fmt_reset_vartable();
2539 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2541 compose_colorize_signature(compose);
2544 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2545 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2546 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2548 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2549 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2550 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2551 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2554 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2555 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2556 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2558 if (compose->toolbar->draft_btn)
2559 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2560 if (compose->toolbar->insert_btn)
2561 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2562 if (compose->toolbar->attach_btn)
2563 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2564 if (compose->toolbar->sig_btn)
2565 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2566 if (compose->toolbar->exteditor_btn)
2567 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2568 if (compose->toolbar->linewrap_current_btn)
2569 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2570 if (compose->toolbar->linewrap_all_btn)
2571 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2573 compose->modified = FALSE;
2574 compose_set_title(compose);
2575 compose->updating = FALSE;
2576 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2577 SCROLL_TO_CURSOR(compose);
2579 if (compose->deferred_destroy) {
2580 compose_destroy(compose);
2584 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2589 const GList *compose_get_compose_list(void)
2591 return compose_list;
2594 void compose_entry_append(Compose *compose, const gchar *address,
2595 ComposeEntryType type, ComposePrefType pref_type)
2597 const gchar *header;
2599 gboolean in_quote = FALSE;
2600 if (!address || *address == '\0') return;
2607 header = N_("Bcc:");
2609 case COMPOSE_REPLYTO:
2610 header = N_("Reply-To:");
2612 case COMPOSE_NEWSGROUPS:
2613 header = N_("Newsgroups:");
2615 case COMPOSE_FOLLOWUPTO:
2616 header = N_( "Followup-To:");
2618 case COMPOSE_INREPLYTO:
2619 header = N_( "In-Reply-To:");
2626 header = prefs_common_translated_header_name(header);
2628 cur = begin = (gchar *)address;
2630 /* we separate the line by commas, but not if we're inside a quoted
2632 while (*cur != '\0') {
2634 in_quote = !in_quote;
2635 if (*cur == ',' && !in_quote) {
2636 gchar *tmp = g_strdup(begin);
2638 tmp[cur-begin]='\0';
2641 while (*tmp == ' ' || *tmp == '\t')
2643 compose_add_header_entry(compose, header, tmp, pref_type);
2650 gchar *tmp = g_strdup(begin);
2652 tmp[cur-begin]='\0';
2653 while (*tmp == ' ' || *tmp == '\t')
2655 compose_add_header_entry(compose, header, tmp, pref_type);
2660 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2662 #if !GTK_CHECK_VERSION(3, 0, 0)
2663 static GdkColor yellow;
2664 static GdkColor black;
2665 static gboolean yellow_initialised = FALSE;
2667 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2668 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2673 #if !GTK_CHECK_VERSION(3, 0, 0)
2674 if (!yellow_initialised) {
2675 gdk_color_parse("#f5f6be", &yellow);
2676 gdk_color_parse("#000000", &black);
2677 yellow_initialised = gdk_colormap_alloc_color(
2678 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2679 yellow_initialised &= gdk_colormap_alloc_color(
2680 gdk_colormap_get_system(), &black, FALSE, TRUE);
2684 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2685 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2686 if (gtk_entry_get_text(entry) &&
2687 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2688 #if !GTK_CHECK_VERSION(3, 0, 0)
2689 if (yellow_initialised) {
2691 gtk_widget_modify_base(
2692 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2693 GTK_STATE_NORMAL, &yellow);
2694 gtk_widget_modify_text(
2695 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2696 GTK_STATE_NORMAL, &black);
2697 #if !GTK_CHECK_VERSION(3, 0, 0)
2704 void compose_toolbar_cb(gint action, gpointer data)
2706 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2707 Compose *compose = (Compose*)toolbar_item->parent;
2709 cm_return_if_fail(compose != NULL);
2713 compose_send_cb(NULL, compose);
2716 compose_send_later_cb(NULL, compose);
2719 compose_draft(compose, COMPOSE_QUIT_EDITING);
2722 compose_insert_file_cb(NULL, compose);
2725 compose_attach_cb(NULL, compose);
2728 compose_insert_sig(compose, FALSE);
2731 compose_insert_sig(compose, TRUE);
2734 compose_ext_editor_cb(NULL, compose);
2736 case A_LINEWRAP_CURRENT:
2737 compose_beautify_paragraph(compose, NULL, TRUE);
2739 case A_LINEWRAP_ALL:
2740 compose_wrap_all_full(compose, TRUE);
2743 compose_address_cb(NULL, compose);
2746 case A_CHECK_SPELLING:
2747 compose_check_all(NULL, compose);
2755 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2760 gchar *subject = NULL;
2764 gchar **attach = NULL;
2765 gchar *inreplyto = NULL;
2766 MailField mfield = NO_FIELD_PRESENT;
2768 /* get mailto parts but skip from */
2769 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2772 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2773 mfield = TO_FIELD_PRESENT;
2776 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2778 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2780 if (!g_utf8_validate (subject, -1, NULL)) {
2781 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2782 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2785 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2787 mfield = SUBJECT_FIELD_PRESENT;
2790 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2791 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2794 gboolean prev_autowrap = compose->autowrap;
2796 compose->autowrap = FALSE;
2798 mark = gtk_text_buffer_get_insert(buffer);
2799 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2801 if (!g_utf8_validate (body, -1, NULL)) {
2802 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2803 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2806 gtk_text_buffer_insert(buffer, &iter, body, -1);
2808 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2810 compose->autowrap = prev_autowrap;
2811 if (compose->autowrap)
2812 compose_wrap_all(compose);
2813 mfield = BODY_FIELD_PRESENT;
2817 gint i = 0, att = 0;
2818 gchar *warn_files = NULL;
2819 while (attach[i] != NULL) {
2820 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2821 if (utf8_filename) {
2822 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2823 gchar *tmp = g_strdup_printf("%s%s\n",
2824 warn_files?warn_files:"",
2830 g_free(utf8_filename);
2832 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2837 alertpanel_notice(ngettext(
2838 "The following file has been attached: \n%s",
2839 "The following files have been attached: \n%s", att), warn_files);
2844 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2857 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2859 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2860 {"Cc:", NULL, TRUE},
2861 {"References:", NULL, FALSE},
2862 {"Bcc:", NULL, TRUE},
2863 {"Newsgroups:", NULL, TRUE},
2864 {"Followup-To:", NULL, TRUE},
2865 {"List-Post:", NULL, FALSE},
2866 {"X-Priority:", NULL, FALSE},
2867 {NULL, NULL, FALSE}};
2883 cm_return_val_if_fail(msginfo != NULL, -1);
2885 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2886 procheader_get_header_fields(fp, hentry);
2889 if (hentry[H_REPLY_TO].body != NULL) {
2890 if (hentry[H_REPLY_TO].body[0] != '\0') {
2892 conv_unmime_header(hentry[H_REPLY_TO].body,
2895 g_free(hentry[H_REPLY_TO].body);
2896 hentry[H_REPLY_TO].body = NULL;
2898 if (hentry[H_CC].body != NULL) {
2899 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2900 g_free(hentry[H_CC].body);
2901 hentry[H_CC].body = NULL;
2903 if (hentry[H_REFERENCES].body != NULL) {
2904 if (compose->mode == COMPOSE_REEDIT)
2905 compose->references = hentry[H_REFERENCES].body;
2907 compose->references = compose_parse_references
2908 (hentry[H_REFERENCES].body, msginfo->msgid);
2909 g_free(hentry[H_REFERENCES].body);
2911 hentry[H_REFERENCES].body = NULL;
2913 if (hentry[H_BCC].body != NULL) {
2914 if (compose->mode == COMPOSE_REEDIT)
2916 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2917 g_free(hentry[H_BCC].body);
2918 hentry[H_BCC].body = NULL;
2920 if (hentry[H_NEWSGROUPS].body != NULL) {
2921 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2922 hentry[H_NEWSGROUPS].body = NULL;
2924 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2925 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2926 compose->followup_to =
2927 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2930 g_free(hentry[H_FOLLOWUP_TO].body);
2931 hentry[H_FOLLOWUP_TO].body = NULL;
2933 if (hentry[H_LIST_POST].body != NULL) {
2934 gchar *to = NULL, *start = NULL;
2936 extract_address(hentry[H_LIST_POST].body);
2937 if (hentry[H_LIST_POST].body[0] != '\0') {
2938 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2940 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2941 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2944 g_free(compose->ml_post);
2945 compose->ml_post = to;
2948 g_free(hentry[H_LIST_POST].body);
2949 hentry[H_LIST_POST].body = NULL;
2952 /* CLAWS - X-Priority */
2953 if (compose->mode == COMPOSE_REEDIT)
2954 if (hentry[H_X_PRIORITY].body != NULL) {
2957 priority = atoi(hentry[H_X_PRIORITY].body);
2958 g_free(hentry[H_X_PRIORITY].body);
2960 hentry[H_X_PRIORITY].body = NULL;
2962 if (priority < PRIORITY_HIGHEST ||
2963 priority > PRIORITY_LOWEST)
2964 priority = PRIORITY_NORMAL;
2966 compose->priority = priority;
2969 if (compose->mode == COMPOSE_REEDIT) {
2970 if (msginfo->inreplyto && *msginfo->inreplyto)
2971 compose->inreplyto = g_strdup(msginfo->inreplyto);
2975 if (msginfo->msgid && *msginfo->msgid)
2976 compose->inreplyto = g_strdup(msginfo->msgid);
2978 if (!compose->references) {
2979 if (msginfo->msgid && *msginfo->msgid) {
2980 if (msginfo->inreplyto && *msginfo->inreplyto)
2981 compose->references =
2982 g_strdup_printf("<%s>\n\t<%s>",
2986 compose->references =
2987 g_strconcat("<", msginfo->msgid, ">",
2989 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2990 compose->references =
2991 g_strconcat("<", msginfo->inreplyto, ">",
2999 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
3004 cm_return_val_if_fail(msginfo != NULL, -1);
3006 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
3007 procheader_get_header_fields(fp, entries);
3011 while (he != NULL && he->name != NULL) {
3013 GtkListStore *model = NULL;
3015 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3016 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3017 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3018 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3019 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3026 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3028 GSList *ref_id_list, *cur;
3032 ref_id_list = references_list_append(NULL, ref);
3033 if (!ref_id_list) return NULL;
3034 if (msgid && *msgid)
3035 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3040 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3041 /* "<" + Message-ID + ">" + CR+LF+TAB */
3042 len += strlen((gchar *)cur->data) + 5;
3044 if (len > MAX_REFERENCES_LEN) {
3045 /* remove second message-ID */
3046 if (ref_id_list && ref_id_list->next &&
3047 ref_id_list->next->next) {
3048 g_free(ref_id_list->next->data);
3049 ref_id_list = g_slist_remove
3050 (ref_id_list, ref_id_list->next->data);
3052 slist_free_strings_full(ref_id_list);
3059 new_ref = g_string_new("");
3060 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3061 if (new_ref->len > 0)
3062 g_string_append(new_ref, "\n\t");
3063 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3066 slist_free_strings_full(ref_id_list);
3068 new_ref_str = new_ref->str;
3069 g_string_free(new_ref, FALSE);
3074 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3075 const gchar *fmt, const gchar *qmark,
3076 const gchar *body, gboolean rewrap,
3077 gboolean need_unescape,
3078 const gchar *err_msg)
3080 MsgInfo* dummyinfo = NULL;
3081 gchar *quote_str = NULL;
3083 gboolean prev_autowrap;
3084 const gchar *trimmed_body = body;
3085 gint cursor_pos = -1;
3086 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3087 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3092 SIGNAL_BLOCK(buffer);
3095 dummyinfo = compose_msginfo_new_from_compose(compose);
3096 msginfo = dummyinfo;
3099 if (qmark != NULL) {
3101 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3102 compose->gtkaspell);
3104 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3106 quote_fmt_scan_string(qmark);
3109 buf = quote_fmt_get_buffer();
3111 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3113 Xstrdup_a(quote_str, buf, goto error)
3116 if (fmt && *fmt != '\0') {
3119 while (*trimmed_body == '\n')
3123 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3124 compose->gtkaspell);
3126 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3128 if (need_unescape) {
3131 /* decode \-escape sequences in the internal representation of the quote format */
3132 tmp = g_malloc(strlen(fmt)+1);
3133 pref_get_unescaped_pref(tmp, fmt);
3134 quote_fmt_scan_string(tmp);
3138 quote_fmt_scan_string(fmt);
3142 buf = quote_fmt_get_buffer();
3144 gint line = quote_fmt_get_line();
3145 alertpanel_error(err_msg, line);
3151 prev_autowrap = compose->autowrap;
3152 compose->autowrap = FALSE;
3154 mark = gtk_text_buffer_get_insert(buffer);
3155 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3156 if (g_utf8_validate(buf, -1, NULL)) {
3157 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3159 gchar *tmpout = NULL;
3160 tmpout = conv_codeset_strdup
3161 (buf, conv_get_locale_charset_str_no_utf8(),
3163 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3165 tmpout = g_malloc(strlen(buf)*2+1);
3166 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3168 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3172 cursor_pos = quote_fmt_get_cursor_pos();
3173 if (cursor_pos == -1)
3174 cursor_pos = gtk_text_iter_get_offset(&iter);
3175 compose->set_cursor_pos = cursor_pos;
3177 gtk_text_buffer_get_start_iter(buffer, &iter);
3178 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3179 gtk_text_buffer_place_cursor(buffer, &iter);
3181 compose->autowrap = prev_autowrap;
3182 if (compose->autowrap && rewrap)
3183 compose_wrap_all(compose);
3190 SIGNAL_UNBLOCK(buffer);
3192 procmsg_msginfo_free( dummyinfo );
3197 /* if ml_post is of type addr@host and from is of type
3198 * addr-anything@host, return TRUE
3200 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3202 gchar *left_ml = NULL;
3203 gchar *right_ml = NULL;
3204 gchar *left_from = NULL;
3205 gchar *right_from = NULL;
3206 gboolean result = FALSE;
3208 if (!ml_post || !from)
3211 left_ml = g_strdup(ml_post);
3212 if (strstr(left_ml, "@")) {
3213 right_ml = strstr(left_ml, "@")+1;
3214 *(strstr(left_ml, "@")) = '\0';
3217 left_from = g_strdup(from);
3218 if (strstr(left_from, "@")) {
3219 right_from = strstr(left_from, "@")+1;
3220 *(strstr(left_from, "@")) = '\0';
3223 if (right_ml && right_from
3224 && !strncmp(left_from, left_ml, strlen(left_ml))
3225 && !strcmp(right_from, right_ml)) {
3234 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3235 gboolean respect_default_to)
3239 if (!folder || !folder->prefs)
3242 if (respect_default_to && folder->prefs->enable_default_to) {
3243 compose_entry_append(compose, folder->prefs->default_to,
3244 COMPOSE_TO, PREF_FOLDER);
3245 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3247 if (folder->prefs->enable_default_cc)
3248 compose_entry_append(compose, folder->prefs->default_cc,
3249 COMPOSE_CC, PREF_FOLDER);
3250 if (folder->prefs->enable_default_bcc)
3251 compose_entry_append(compose, folder->prefs->default_bcc,
3252 COMPOSE_BCC, PREF_FOLDER);
3253 if (folder->prefs->enable_default_replyto)
3254 compose_entry_append(compose, folder->prefs->default_replyto,
3255 COMPOSE_REPLYTO, PREF_FOLDER);
3258 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3263 if (!compose || !msginfo)
3266 if (msginfo->subject && *msginfo->subject) {
3267 buf = p = g_strdup(msginfo->subject);
3268 p += subject_get_prefix_length(p);
3269 memmove(buf, p, strlen(p) + 1);
3271 buf2 = g_strdup_printf("Re: %s", buf);
3272 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3277 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3280 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3281 gboolean to_all, gboolean to_ml,
3283 gboolean followup_and_reply_to)
3285 GSList *cc_list = NULL;
3288 gchar *replyto = NULL;
3289 gchar *ac_email = NULL;
3291 gboolean reply_to_ml = FALSE;
3292 gboolean default_reply_to = FALSE;
3294 cm_return_if_fail(compose->account != NULL);
3295 cm_return_if_fail(msginfo != NULL);
3297 reply_to_ml = to_ml && compose->ml_post;
3299 default_reply_to = msginfo->folder &&
3300 msginfo->folder->prefs->enable_default_reply_to;
3302 if (compose->account->protocol != A_NNTP) {
3303 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3305 if (reply_to_ml && !default_reply_to) {
3307 gboolean is_subscr = is_subscription(compose->ml_post,
3310 /* normal answer to ml post with a reply-to */
3311 compose_entry_append(compose,
3313 COMPOSE_TO, PREF_ML);
3314 if (compose->replyto)
3315 compose_entry_append(compose,
3317 COMPOSE_CC, PREF_ML);
3319 /* answer to subscription confirmation */
3320 if (compose->replyto)
3321 compose_entry_append(compose,
3323 COMPOSE_TO, PREF_ML);
3324 else if (msginfo->from)
3325 compose_entry_append(compose,
3327 COMPOSE_TO, PREF_ML);
3330 else if (!(to_all || to_sender) && default_reply_to) {
3331 compose_entry_append(compose,
3332 msginfo->folder->prefs->default_reply_to,
3333 COMPOSE_TO, PREF_FOLDER);
3334 compose_entry_mark_default_to(compose,
3335 msginfo->folder->prefs->default_reply_to);
3341 compose_entry_append(compose, msginfo->from,
3342 COMPOSE_TO, PREF_NONE);
3344 Xstrdup_a(tmp1, msginfo->from, return);
3345 extract_address(tmp1);
3346 compose_entry_append(compose,
3347 (!account_find_from_address(tmp1, FALSE))
3350 COMPOSE_TO, PREF_NONE);
3352 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3353 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3354 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3355 if (compose->replyto) {
3356 compose_entry_append(compose,
3358 COMPOSE_TO, PREF_NONE);
3360 compose_entry_append(compose,
3361 msginfo->from ? msginfo->from : "",
3362 COMPOSE_TO, PREF_NONE);
3365 /* replying to own mail, use original recp */
3366 compose_entry_append(compose,
3367 msginfo->to ? msginfo->to : "",
3368 COMPOSE_TO, PREF_NONE);
3369 compose_entry_append(compose,
3370 msginfo->cc ? msginfo->cc : "",
3371 COMPOSE_CC, PREF_NONE);
3376 if (to_sender || (compose->followup_to &&
3377 !strncmp(compose->followup_to, "poster", 6)))
3378 compose_entry_append
3380 (compose->replyto ? compose->replyto :
3381 msginfo->from ? msginfo->from : ""),
3382 COMPOSE_TO, PREF_NONE);
3384 else if (followup_and_reply_to || to_all) {
3385 compose_entry_append
3387 (compose->replyto ? compose->replyto :
3388 msginfo->from ? msginfo->from : ""),
3389 COMPOSE_TO, PREF_NONE);
3391 compose_entry_append
3393 compose->followup_to ? compose->followup_to :
3394 compose->newsgroups ? compose->newsgroups : "",
3395 COMPOSE_NEWSGROUPS, PREF_NONE);
3398 compose_entry_append
3400 compose->followup_to ? compose->followup_to :
3401 compose->newsgroups ? compose->newsgroups : "",
3402 COMPOSE_NEWSGROUPS, PREF_NONE);
3404 compose_reply_set_subject(compose, msginfo);
3406 if (to_ml && compose->ml_post) return;
3407 if (!to_all || compose->account->protocol == A_NNTP) return;
3409 if (compose->replyto) {
3410 Xstrdup_a(replyto, compose->replyto, return);
3411 extract_address(replyto);
3413 if (msginfo->from) {
3414 Xstrdup_a(from, msginfo->from, return);
3415 extract_address(from);
3418 if (replyto && from)
3419 cc_list = address_list_append_with_comments(cc_list, from);
3420 if (to_all && msginfo->folder &&
3421 msginfo->folder->prefs->enable_default_reply_to)
3422 cc_list = address_list_append_with_comments(cc_list,
3423 msginfo->folder->prefs->default_reply_to);
3424 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3425 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3427 ac_email = g_utf8_strdown(compose->account->address, -1);
3430 for (cur = cc_list; cur != NULL; cur = cur->next) {
3431 gchar *addr = g_utf8_strdown(cur->data, -1);
3432 extract_address(addr);
3434 if (strcmp(ac_email, addr))
3435 compose_entry_append(compose, (gchar *)cur->data,
3436 COMPOSE_CC, PREF_NONE);
3438 debug_print("Cc address same as compose account's, ignoring\n");
3443 slist_free_strings_full(cc_list);
3449 #define SET_ENTRY(entry, str) \
3452 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3455 #define SET_ADDRESS(type, str) \
3458 compose_entry_append(compose, str, type, PREF_NONE); \
3461 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3463 cm_return_if_fail(msginfo != NULL);
3465 SET_ENTRY(subject_entry, msginfo->subject);
3466 SET_ENTRY(from_name, msginfo->from);
3467 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3468 SET_ADDRESS(COMPOSE_CC, compose->cc);
3469 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3470 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3471 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3472 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3474 compose_update_priority_menu_item(compose);
3475 compose_update_privacy_system_menu_item(compose, FALSE);
3476 compose_show_first_last_header(compose, TRUE);
3482 static void compose_insert_sig(Compose *compose, gboolean replace)
3484 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3485 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3487 GtkTextIter iter, iter_end;
3488 gint cur_pos, ins_pos;
3489 gboolean prev_autowrap;
3490 gboolean found = FALSE;
3491 gboolean exists = FALSE;
3493 cm_return_if_fail(compose->account != NULL);
3497 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3498 G_CALLBACK(compose_changed_cb),
3501 mark = gtk_text_buffer_get_insert(buffer);
3502 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3503 cur_pos = gtk_text_iter_get_offset (&iter);
3506 gtk_text_buffer_get_end_iter(buffer, &iter);
3508 exists = (compose->sig_str != NULL);
3511 GtkTextIter first_iter, start_iter, end_iter;
3513 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3515 if (!exists || compose->sig_str[0] == '\0')
3518 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3519 compose->signature_tag);
3522 /* include previous \n\n */
3523 gtk_text_iter_backward_chars(&first_iter, 1);
3524 start_iter = first_iter;
3525 end_iter = first_iter;
3527 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3528 compose->signature_tag);
3529 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3530 compose->signature_tag);
3532 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3538 g_free(compose->sig_str);
3539 compose->sig_str = account_get_signature_str(compose->account);
3541 cur_pos = gtk_text_iter_get_offset(&iter);
3543 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3544 g_free(compose->sig_str);
3545 compose->sig_str = NULL;
3547 if (compose->sig_inserted == FALSE)
3548 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3549 compose->sig_inserted = TRUE;
3551 cur_pos = gtk_text_iter_get_offset(&iter);
3552 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3554 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3555 gtk_text_iter_forward_chars(&iter, 1);
3556 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3557 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3559 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3560 cur_pos = gtk_text_buffer_get_char_count (buffer);
3563 /* put the cursor where it should be
3564 * either where the quote_fmt says, either where it was */
3565 if (compose->set_cursor_pos < 0)
3566 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3568 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3569 compose->set_cursor_pos);
3571 compose->set_cursor_pos = -1;
3572 gtk_text_buffer_place_cursor(buffer, &iter);
3573 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3574 G_CALLBACK(compose_changed_cb),
3580 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3583 GtkTextBuffer *buffer;
3586 const gchar *cur_encoding;
3587 gchar buf[BUFFSIZE];
3590 gboolean prev_autowrap;
3593 GString *file_contents = NULL;
3594 ComposeInsertResult result = COMPOSE_INSERT_SUCCESS;
3596 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3598 /* get the size of the file we are about to insert */
3599 ret = g_stat(file, &file_stat);
3601 gchar *shortfile = g_path_get_basename(file);
3602 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3604 return COMPOSE_INSERT_NO_FILE;
3605 } else if (prefs_common.warn_large_insert == TRUE) {
3607 /* ask user for confirmation if the file is large */
3608 if (prefs_common.warn_large_insert_size < 0 ||
3609 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3613 msg = g_strdup_printf(_("You are about to insert a file of %s "
3614 "in the message body. Are you sure you want to do that?"),
3615 to_human_readable(file_stat.st_size));
3616 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3617 g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3620 /* do we ask for confirmation next time? */
3621 if (aval & G_ALERTDISABLE) {
3622 /* no confirmation next time, disable feature in preferences */
3623 aval &= ~G_ALERTDISABLE;
3624 prefs_common.warn_large_insert = FALSE;
3627 /* abort file insertion if user canceled action */
3628 if (aval != G_ALERTALTERNATE) {
3629 return COMPOSE_INSERT_NO_FILE;
3635 if ((fp = g_fopen(file, "rb")) == NULL) {
3636 FILE_OP_ERROR(file, "fopen");
3637 return COMPOSE_INSERT_READ_ERROR;
3640 prev_autowrap = compose->autowrap;
3641 compose->autowrap = FALSE;
3643 text = GTK_TEXT_VIEW(compose->text);
3644 buffer = gtk_text_view_get_buffer(text);
3645 mark = gtk_text_buffer_get_insert(buffer);
3646 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3648 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3649 G_CALLBACK(text_inserted),
3652 cur_encoding = conv_get_locale_charset_str_no_utf8();
3654 file_contents = g_string_new("");
3655 while (fgets(buf, sizeof(buf), fp) != NULL) {
3658 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3659 str = g_strdup(buf);
3661 codeconv_set_strict(TRUE);
3662 str = conv_codeset_strdup
3663 (buf, cur_encoding, CS_INTERNAL);
3664 codeconv_set_strict(FALSE);
3667 result = COMPOSE_INSERT_INVALID_CHARACTER;
3673 /* strip <CR> if DOS/Windows file,
3674 replace <CR> with <LF> if Macintosh file. */
3677 if (len > 0 && str[len - 1] != '\n') {
3679 if (str[len] == '\r') str[len] = '\n';
3682 file_contents = g_string_append(file_contents, str);
3686 if (result == COMPOSE_INSERT_SUCCESS) {
3687 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3689 compose_changed_cb(NULL, compose);
3690 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3691 G_CALLBACK(text_inserted),
3693 compose->autowrap = prev_autowrap;
3694 if (compose->autowrap)
3695 compose_wrap_all(compose);
3698 g_string_free(file_contents, TRUE);
3704 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3705 const gchar *filename,
3706 const gchar *content_type,
3707 const gchar *charset)
3715 GtkListStore *store;
3717 gboolean has_binary = FALSE;
3719 if (!is_file_exist(file)) {
3720 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3721 gboolean result = FALSE;
3722 if (file_from_uri && is_file_exist(file_from_uri)) {
3723 result = compose_attach_append(
3724 compose, file_from_uri,
3725 filename, content_type,
3728 g_free(file_from_uri);
3731 alertpanel_error("File %s doesn't exist\n", filename);
3734 if ((size = get_file_size(file)) < 0) {
3735 alertpanel_error("Can't get file size of %s\n", filename);
3739 /* In batch mode, we allow 0-length files to be attached no questions asked */
3740 if (size == 0 && !compose->batch) {
3741 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3742 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3743 GTK_STOCK_CANCEL, g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
3744 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3747 if (aval != G_ALERTALTERNATE) {
3751 if ((fp = g_fopen(file, "rb")) == NULL) {
3752 alertpanel_error(_("Can't read %s."), filename);
3757 ainfo = g_new0(AttachInfo, 1);
3758 auto_ainfo = g_auto_pointer_new_with_free
3759 (ainfo, (GFreeFunc) compose_attach_info_free);
3760 ainfo->file = g_strdup(file);
3763 ainfo->content_type = g_strdup(content_type);
3764 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3766 MsgFlags flags = {0, 0};
3768 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3769 ainfo->encoding = ENC_7BIT;
3771 ainfo->encoding = ENC_8BIT;
3773 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3774 if (msginfo && msginfo->subject)
3775 name = g_strdup(msginfo->subject);
3777 name = g_path_get_basename(filename ? filename : file);
3779 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3781 procmsg_msginfo_free(msginfo);
3783 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3784 ainfo->charset = g_strdup(charset);
3785 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3787 ainfo->encoding = ENC_BASE64;
3789 name = g_path_get_basename(filename ? filename : file);
3790 ainfo->name = g_strdup(name);
3794 ainfo->content_type = procmime_get_mime_type(file);
3795 if (!ainfo->content_type) {
3796 ainfo->content_type =
3797 g_strdup("application/octet-stream");
3798 ainfo->encoding = ENC_BASE64;
3799 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3801 procmime_get_encoding_for_text_file(file, &has_binary);
3803 ainfo->encoding = ENC_BASE64;
3804 name = g_path_get_basename(filename ? filename : file);
3805 ainfo->name = g_strdup(name);
3809 if (ainfo->name != NULL
3810 && !strcmp(ainfo->name, ".")) {
3811 g_free(ainfo->name);
3815 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3816 g_free(ainfo->content_type);
3817 ainfo->content_type = g_strdup("application/octet-stream");
3818 g_free(ainfo->charset);
3819 ainfo->charset = NULL;
3822 ainfo->size = (goffset)size;
3823 size_text = to_human_readable((goffset)size);
3825 store = GTK_LIST_STORE(gtk_tree_view_get_model
3826 (GTK_TREE_VIEW(compose->attach_clist)));
3828 gtk_list_store_append(store, &iter);
3829 gtk_list_store_set(store, &iter,
3830 COL_MIMETYPE, ainfo->content_type,
3831 COL_SIZE, size_text,
3832 COL_NAME, ainfo->name,
3833 COL_CHARSET, ainfo->charset,
3835 COL_AUTODATA, auto_ainfo,
3838 g_auto_pointer_free(auto_ainfo);
3839 compose_attach_update_label(compose);
3843 static void compose_use_signing(Compose *compose, gboolean use_signing)
3845 compose->use_signing = use_signing;
3846 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3849 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3851 compose->use_encryption = use_encryption;
3852 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3855 #define NEXT_PART_NOT_CHILD(info) \
3857 node = info->node; \
3858 while (node->children) \
3859 node = g_node_last_child(node); \
3860 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3863 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3867 MimeInfo *firsttext = NULL;
3868 MimeInfo *encrypted = NULL;
3871 const gchar *partname = NULL;
3873 mimeinfo = procmime_scan_message(msginfo);
3874 if (!mimeinfo) return;
3876 if (mimeinfo->node->children == NULL) {
3877 procmime_mimeinfo_free_all(mimeinfo);
3881 /* find first content part */
3882 child = (MimeInfo *) mimeinfo->node->children->data;
3883 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3884 child = (MimeInfo *)child->node->children->data;
3887 if (child->type == MIMETYPE_TEXT) {
3889 debug_print("First text part found\n");
3890 } else if (compose->mode == COMPOSE_REEDIT &&
3891 child->type == MIMETYPE_APPLICATION &&
3892 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3893 encrypted = (MimeInfo *)child->node->parent->data;
3896 child = (MimeInfo *) mimeinfo->node->children->data;
3897 while (child != NULL) {
3900 if (child == encrypted) {
3901 /* skip this part of tree */
3902 NEXT_PART_NOT_CHILD(child);
3906 if (child->type == MIMETYPE_MULTIPART) {
3907 /* get the actual content */
3908 child = procmime_mimeinfo_next(child);
3912 if (child == firsttext) {
3913 child = procmime_mimeinfo_next(child);
3917 outfile = procmime_get_tmp_file_name(child);
3918 if ((err = procmime_get_part(outfile, child)) < 0)
3919 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err));
3921 gchar *content_type;
3923 content_type = procmime_get_content_type_str(child->type, child->subtype);
3925 /* if we meet a pgp signature, we don't attach it, but
3926 * we force signing. */
3927 if ((strcmp(content_type, "application/pgp-signature") &&
3928 strcmp(content_type, "application/pkcs7-signature") &&
3929 strcmp(content_type, "application/x-pkcs7-signature"))
3930 || compose->mode == COMPOSE_REDIRECT) {
3931 partname = procmime_mimeinfo_get_parameter(child, "filename");
3932 if (partname == NULL)
3933 partname = procmime_mimeinfo_get_parameter(child, "name");
3934 if (partname == NULL)
3936 compose_attach_append(compose, outfile,
3937 partname, content_type,
3938 procmime_mimeinfo_get_parameter(child, "charset"));
3940 compose_force_signing(compose, compose->account, NULL);
3942 g_free(content_type);
3945 NEXT_PART_NOT_CHILD(child);
3947 procmime_mimeinfo_free_all(mimeinfo);
3950 #undef NEXT_PART_NOT_CHILD
3955 WAIT_FOR_INDENT_CHAR,
3956 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3959 /* return indent length, we allow:
3960 indent characters followed by indent characters or spaces/tabs,
3961 alphabets and numbers immediately followed by indent characters,
3962 and the repeating sequences of the above
3963 If quote ends with multiple spaces, only the first one is included. */
3964 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3965 const GtkTextIter *start, gint *len)
3967 GtkTextIter iter = *start;
3971 IndentState state = WAIT_FOR_INDENT_CHAR;
3974 gint alnum_count = 0;
3975 gint space_count = 0;
3978 if (prefs_common.quote_chars == NULL) {
3982 while (!gtk_text_iter_ends_line(&iter)) {
3983 wc = gtk_text_iter_get_char(&iter);
3984 if (g_unichar_iswide(wc))
3986 clen = g_unichar_to_utf8(wc, ch);
3990 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3991 is_space = g_unichar_isspace(wc);
3993 if (state == WAIT_FOR_INDENT_CHAR) {
3994 if (!is_indent && !g_unichar_isalnum(wc))
3997 quote_len += alnum_count + space_count + 1;
3998 alnum_count = space_count = 0;
3999 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
4002 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
4003 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
4007 else if (is_indent) {
4008 quote_len += alnum_count + space_count + 1;
4009 alnum_count = space_count = 0;
4012 state = WAIT_FOR_INDENT_CHAR;
4016 gtk_text_iter_forward_char(&iter);
4019 if (quote_len > 0 && space_count > 0)
4025 if (quote_len > 0) {
4027 gtk_text_iter_forward_chars(&iter, quote_len);
4028 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4034 /* return >0 if the line is itemized */
4035 static int compose_itemized_length(GtkTextBuffer *buffer,
4036 const GtkTextIter *start)
4038 GtkTextIter iter = *start;
4043 if (gtk_text_iter_ends_line(&iter))
4048 wc = gtk_text_iter_get_char(&iter);
4049 if (!g_unichar_isspace(wc))
4051 gtk_text_iter_forward_char(&iter);
4052 if (gtk_text_iter_ends_line(&iter))
4056 clen = g_unichar_to_utf8(wc, ch);
4060 if (!strchr("*-+", ch[0]))
4063 gtk_text_iter_forward_char(&iter);
4064 if (gtk_text_iter_ends_line(&iter))
4066 wc = gtk_text_iter_get_char(&iter);
4067 if (g_unichar_isspace(wc)) {
4073 /* return the string at the start of the itemization */
4074 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4075 const GtkTextIter *start)
4077 GtkTextIter iter = *start;
4080 GString *item_chars = g_string_new("");
4083 if (gtk_text_iter_ends_line(&iter))
4088 wc = gtk_text_iter_get_char(&iter);
4089 if (!g_unichar_isspace(wc))
4091 gtk_text_iter_forward_char(&iter);
4092 if (gtk_text_iter_ends_line(&iter))
4094 g_string_append_unichar(item_chars, wc);
4097 str = item_chars->str;
4098 g_string_free(item_chars, FALSE);
4102 /* return the number of spaces at a line's start */
4103 static int compose_left_offset_length(GtkTextBuffer *buffer,
4104 const GtkTextIter *start)
4106 GtkTextIter iter = *start;
4109 if (gtk_text_iter_ends_line(&iter))
4113 wc = gtk_text_iter_get_char(&iter);
4114 if (!g_unichar_isspace(wc))
4117 gtk_text_iter_forward_char(&iter);
4118 if (gtk_text_iter_ends_line(&iter))
4122 gtk_text_iter_forward_char(&iter);
4123 if (gtk_text_iter_ends_line(&iter))
4128 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4129 const GtkTextIter *start,
4130 GtkTextIter *break_pos,
4134 GtkTextIter iter = *start, line_end = *start;
4135 PangoLogAttr *attrs;
4142 gboolean can_break = FALSE;
4143 gboolean do_break = FALSE;
4144 gboolean was_white = FALSE;
4145 gboolean prev_dont_break = FALSE;
4147 gtk_text_iter_forward_to_line_end(&line_end);
4148 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4149 len = g_utf8_strlen(str, -1);
4153 g_warning("compose_get_line_break_pos: len = 0!");
4157 /* g_print("breaking line: %d: %s (len = %d)\n",
4158 gtk_text_iter_get_line(&iter), str, len); */
4160 attrs = g_new(PangoLogAttr, len + 1);
4162 pango_default_break(str, -1, NULL, attrs, len + 1);
4166 /* skip quote and leading spaces */
4167 for (i = 0; *p != '\0' && i < len; i++) {
4170 wc = g_utf8_get_char(p);
4171 if (i >= quote_len && !g_unichar_isspace(wc))
4173 if (g_unichar_iswide(wc))
4175 else if (*p == '\t')
4179 p = g_utf8_next_char(p);
4182 for (; *p != '\0' && i < len; i++) {
4183 PangoLogAttr *attr = attrs + i;
4187 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4190 was_white = attr->is_white;
4192 /* don't wrap URI */
4193 if ((uri_len = get_uri_len(p)) > 0) {
4195 if (pos > 0 && col > max_col) {
4205 wc = g_utf8_get_char(p);
4206 if (g_unichar_iswide(wc)) {
4208 if (prev_dont_break && can_break && attr->is_line_break)
4210 } else if (*p == '\t')
4214 if (pos > 0 && col > max_col) {
4219 if (*p == '-' || *p == '/')
4220 prev_dont_break = TRUE;
4222 prev_dont_break = FALSE;
4224 p = g_utf8_next_char(p);
4228 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4233 *break_pos = *start;
4234 gtk_text_iter_set_line_offset(break_pos, pos);
4239 static gboolean compose_join_next_line(Compose *compose,
4240 GtkTextBuffer *buffer,
4242 const gchar *quote_str)
4244 GtkTextIter iter_ = *iter, cur, prev, next, end;
4245 PangoLogAttr attrs[3];
4247 gchar *next_quote_str;
4250 gboolean keep_cursor = FALSE;
4252 if (!gtk_text_iter_forward_line(&iter_) ||
4253 gtk_text_iter_ends_line(&iter_)) {
4256 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4258 if ((quote_str || next_quote_str) &&
4259 strcmp2(quote_str, next_quote_str) != 0) {
4260 g_free(next_quote_str);
4263 g_free(next_quote_str);
4266 if (quote_len > 0) {
4267 gtk_text_iter_forward_chars(&end, quote_len);
4268 if (gtk_text_iter_ends_line(&end)) {
4273 /* don't join itemized lines */
4274 if (compose_itemized_length(buffer, &end) > 0) {
4278 /* don't join signature separator */
4279 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4282 /* delete quote str */
4284 gtk_text_buffer_delete(buffer, &iter_, &end);
4286 /* don't join line breaks put by the user */
4288 gtk_text_iter_backward_char(&cur);
4289 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4290 gtk_text_iter_forward_char(&cur);
4294 gtk_text_iter_forward_char(&cur);
4295 /* delete linebreak and extra spaces */
4296 while (gtk_text_iter_backward_char(&cur)) {
4297 wc1 = gtk_text_iter_get_char(&cur);
4298 if (!g_unichar_isspace(wc1))
4303 while (!gtk_text_iter_ends_line(&cur)) {
4304 wc1 = gtk_text_iter_get_char(&cur);
4305 if (!g_unichar_isspace(wc1))
4307 gtk_text_iter_forward_char(&cur);
4310 if (!gtk_text_iter_equal(&prev, &next)) {
4313 mark = gtk_text_buffer_get_insert(buffer);
4314 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4315 if (gtk_text_iter_equal(&prev, &cur))
4317 gtk_text_buffer_delete(buffer, &prev, &next);
4321 /* insert space if required */
4322 gtk_text_iter_backward_char(&prev);
4323 wc1 = gtk_text_iter_get_char(&prev);
4324 wc2 = gtk_text_iter_get_char(&next);
4325 gtk_text_iter_forward_char(&next);
4326 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4327 pango_default_break(str, -1, NULL, attrs, 3);
4328 if (!attrs[1].is_line_break ||
4329 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4330 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4332 gtk_text_iter_backward_char(&iter_);
4333 gtk_text_buffer_place_cursor(buffer, &iter_);
4342 #define ADD_TXT_POS(bp_, ep_, pti_) \
4343 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4344 last = last->next; \
4345 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4346 last->next = NULL; \
4348 g_warning("alloc error scanning URIs"); \
4351 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4353 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4354 GtkTextBuffer *buffer;
4355 GtkTextIter iter, break_pos, end_of_line;
4356 gchar *quote_str = NULL;
4358 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4359 gboolean prev_autowrap = compose->autowrap;
4360 gint startq_offset = -1, noq_offset = -1;
4361 gint uri_start = -1, uri_stop = -1;
4362 gint nouri_start = -1, nouri_stop = -1;
4363 gint num_blocks = 0;
4364 gint quotelevel = -1;
4365 gboolean modified = force;
4366 gboolean removed = FALSE;
4367 gboolean modified_before_remove = FALSE;
4369 gboolean start = TRUE;
4370 gint itemized_len = 0, rem_item_len = 0;
4371 gchar *itemized_chars = NULL;
4372 gboolean item_continuation = FALSE;
4377 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4381 compose->autowrap = FALSE;
4383 buffer = gtk_text_view_get_buffer(text);
4384 undo_wrapping(compose->undostruct, TRUE);
4389 mark = gtk_text_buffer_get_insert(buffer);
4390 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4394 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4395 if (gtk_text_iter_ends_line(&iter)) {
4396 while (gtk_text_iter_ends_line(&iter) &&
4397 gtk_text_iter_forward_line(&iter))
4400 while (gtk_text_iter_backward_line(&iter)) {
4401 if (gtk_text_iter_ends_line(&iter)) {
4402 gtk_text_iter_forward_line(&iter);
4408 /* move to line start */
4409 gtk_text_iter_set_line_offset(&iter, 0);
4412 itemized_len = compose_itemized_length(buffer, &iter);
4414 if (!itemized_len) {
4415 itemized_len = compose_left_offset_length(buffer, &iter);
4416 item_continuation = TRUE;
4420 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4422 /* go until paragraph end (empty line) */
4423 while (start || !gtk_text_iter_ends_line(&iter)) {
4424 gchar *scanpos = NULL;
4425 /* parse table - in order of priority */
4427 const gchar *needle; /* token */
4429 /* token search function */
4430 gchar *(*search) (const gchar *haystack,
4431 const gchar *needle);
4432 /* part parsing function */
4433 gboolean (*parse) (const gchar *start,
4434 const gchar *scanpos,
4438 /* part to URI function */
4439 gchar *(*build_uri) (const gchar *bp,
4443 static struct table parser[] = {
4444 {"http://", strcasestr, get_uri_part, make_uri_string},
4445 {"https://", strcasestr, get_uri_part, make_uri_string},
4446 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4447 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4448 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4449 {"www.", strcasestr, get_uri_part, make_http_string},
4450 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4451 {"@", strcasestr, get_email_part, make_email_string}
4453 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4454 gint last_index = PARSE_ELEMS;
4456 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4460 if (!prev_autowrap && num_blocks == 0) {
4462 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4463 G_CALLBACK(text_inserted),
4466 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4469 uri_start = uri_stop = -1;
4471 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4474 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4475 if (startq_offset == -1)
4476 startq_offset = gtk_text_iter_get_offset(&iter);
4477 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4478 if (quotelevel > 2) {
4479 /* recycle colors */
4480 if (prefs_common.recycle_quote_colors)
4489 if (startq_offset == -1)
4490 noq_offset = gtk_text_iter_get_offset(&iter);
4494 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4497 if (gtk_text_iter_ends_line(&iter)) {
4499 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4500 prefs_common.linewrap_len,
4502 GtkTextIter prev, next, cur;
4503 if (prev_autowrap != FALSE || force) {
4504 compose->automatic_break = TRUE;
4506 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4507 compose->automatic_break = FALSE;
4508 if (itemized_len && compose->autoindent) {
4509 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4510 if (!item_continuation)
4511 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4513 } else if (quote_str && wrap_quote) {
4514 compose->automatic_break = TRUE;
4516 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4517 compose->automatic_break = FALSE;
4518 if (itemized_len && compose->autoindent) {
4519 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4520 if (!item_continuation)
4521 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4525 /* remove trailing spaces */
4527 rem_item_len = itemized_len;
4528 while (compose->autoindent && rem_item_len-- > 0)
4529 gtk_text_iter_backward_char(&cur);
4530 gtk_text_iter_backward_char(&cur);
4533 while (!gtk_text_iter_starts_line(&cur)) {
4536 gtk_text_iter_backward_char(&cur);
4537 wc = gtk_text_iter_get_char(&cur);
4538 if (!g_unichar_isspace(wc))
4542 if (!gtk_text_iter_equal(&prev, &next)) {
4543 gtk_text_buffer_delete(buffer, &prev, &next);
4545 gtk_text_iter_forward_char(&break_pos);
4549 gtk_text_buffer_insert(buffer, &break_pos,
4553 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4555 /* move iter to current line start */
4556 gtk_text_iter_set_line_offset(&iter, 0);
4563 /* move iter to next line start */
4569 if (!prev_autowrap && num_blocks > 0) {
4571 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4572 G_CALLBACK(text_inserted),
4576 while (!gtk_text_iter_ends_line(&end_of_line)) {
4577 gtk_text_iter_forward_char(&end_of_line);
4579 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4581 nouri_start = gtk_text_iter_get_offset(&iter);
4582 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4584 walk_pos = gtk_text_iter_get_offset(&iter);
4585 /* FIXME: this looks phony. scanning for anything in the parse table */
4586 for (n = 0; n < PARSE_ELEMS; n++) {
4589 tmp = parser[n].search(walk, parser[n].needle);
4591 if (scanpos == NULL || tmp < scanpos) {
4600 /* check if URI can be parsed */
4601 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4602 (const gchar **)&ep, FALSE)
4603 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4607 strlen(parser[last_index].needle);
4610 uri_start = walk_pos + (bp - o_walk);
4611 uri_stop = walk_pos + (ep - o_walk);
4615 gtk_text_iter_forward_line(&iter);
4618 if (startq_offset != -1) {
4619 GtkTextIter startquote, endquote;
4620 gtk_text_buffer_get_iter_at_offset(
4621 buffer, &startquote, startq_offset);
4624 switch (quotelevel) {
4626 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4627 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4628 gtk_text_buffer_apply_tag_by_name(
4629 buffer, "quote0", &startquote, &endquote);
4630 gtk_text_buffer_remove_tag_by_name(
4631 buffer, "quote1", &startquote, &endquote);
4632 gtk_text_buffer_remove_tag_by_name(
4633 buffer, "quote2", &startquote, &endquote);
4638 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4639 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4640 gtk_text_buffer_apply_tag_by_name(
4641 buffer, "quote1", &startquote, &endquote);
4642 gtk_text_buffer_remove_tag_by_name(
4643 buffer, "quote0", &startquote, &endquote);
4644 gtk_text_buffer_remove_tag_by_name(
4645 buffer, "quote2", &startquote, &endquote);
4650 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4651 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4652 gtk_text_buffer_apply_tag_by_name(
4653 buffer, "quote2", &startquote, &endquote);
4654 gtk_text_buffer_remove_tag_by_name(
4655 buffer, "quote0", &startquote, &endquote);
4656 gtk_text_buffer_remove_tag_by_name(
4657 buffer, "quote1", &startquote, &endquote);
4663 } else if (noq_offset != -1) {
4664 GtkTextIter startnoquote, endnoquote;
4665 gtk_text_buffer_get_iter_at_offset(
4666 buffer, &startnoquote, noq_offset);
4669 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4670 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4671 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4672 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4673 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4674 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4675 gtk_text_buffer_remove_tag_by_name(
4676 buffer, "quote0", &startnoquote, &endnoquote);
4677 gtk_text_buffer_remove_tag_by_name(
4678 buffer, "quote1", &startnoquote, &endnoquote);
4679 gtk_text_buffer_remove_tag_by_name(
4680 buffer, "quote2", &startnoquote, &endnoquote);
4686 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4687 GtkTextIter nouri_start_iter, nouri_end_iter;
4688 gtk_text_buffer_get_iter_at_offset(
4689 buffer, &nouri_start_iter, nouri_start);
4690 gtk_text_buffer_get_iter_at_offset(
4691 buffer, &nouri_end_iter, nouri_stop);
4692 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4693 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4694 gtk_text_buffer_remove_tag_by_name(
4695 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4696 modified_before_remove = modified;
4701 if (uri_start >= 0 && uri_stop > 0) {
4702 GtkTextIter uri_start_iter, uri_end_iter, back;
4703 gtk_text_buffer_get_iter_at_offset(
4704 buffer, &uri_start_iter, uri_start);
4705 gtk_text_buffer_get_iter_at_offset(
4706 buffer, &uri_end_iter, uri_stop);
4707 back = uri_end_iter;
4708 gtk_text_iter_backward_char(&back);
4709 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4710 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4711 gtk_text_buffer_apply_tag_by_name(
4712 buffer, "link", &uri_start_iter, &uri_end_iter);
4714 if (removed && !modified_before_remove) {
4720 // debug_print("not modified, out after %d lines\n", lines);
4724 // debug_print("modified, out after %d lines\n", lines);
4726 g_free(itemized_chars);
4729 undo_wrapping(compose->undostruct, FALSE);
4730 compose->autowrap = prev_autowrap;
4735 void compose_action_cb(void *data)
4737 Compose *compose = (Compose *)data;
4738 compose_wrap_all(compose);
4741 static void compose_wrap_all(Compose *compose)
4743 compose_wrap_all_full(compose, FALSE);
4746 static void compose_wrap_all_full(Compose *compose, gboolean force)
4748 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4749 GtkTextBuffer *buffer;
4751 gboolean modified = TRUE;
4753 buffer = gtk_text_view_get_buffer(text);
4755 gtk_text_buffer_get_start_iter(buffer, &iter);
4757 undo_wrapping(compose->undostruct, TRUE);
4759 while (!gtk_text_iter_is_end(&iter) && modified)
4760 modified = compose_beautify_paragraph(compose, &iter, force);
4762 undo_wrapping(compose->undostruct, FALSE);
4766 static void compose_set_title(Compose *compose)
4772 edited = compose->modified ? _(" [Edited]") : "";
4774 subject = gtk_editable_get_chars(
4775 GTK_EDITABLE(compose->subject_entry), 0, -1);
4777 #ifndef GENERIC_UMPC
4778 if (subject && strlen(subject))
4779 str = g_strdup_printf(_("%s - Compose message%s"),
4782 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4784 str = g_strdup(_("Compose message"));
4787 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4793 * compose_current_mail_account:
4795 * Find a current mail account (the currently selected account, or the
4796 * default account, if a news account is currently selected). If a
4797 * mail account cannot be found, display an error message.
4799 * Return value: Mail account, or NULL if not found.
4801 static PrefsAccount *
4802 compose_current_mail_account(void)
4806 if (cur_account && cur_account->protocol != A_NNTP)
4809 ac = account_get_default();
4810 if (!ac || ac->protocol == A_NNTP) {
4811 alertpanel_error(_("Account for sending mail is not specified.\n"
4812 "Please select a mail account before sending."));
4819 #define QUOTE_IF_REQUIRED(out, str) \
4821 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4825 len = strlen(str) + 3; \
4826 if ((__tmp = alloca(len)) == NULL) { \
4827 g_warning("can't allocate memory"); \
4828 g_string_free(header, TRUE); \
4831 g_snprintf(__tmp, len, "\"%s\"", str); \
4836 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4837 g_warning("can't allocate memory"); \
4838 g_string_free(header, TRUE); \
4841 strcpy(__tmp, str); \
4847 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4849 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4853 len = strlen(str) + 3; \
4854 if ((__tmp = alloca(len)) == NULL) { \
4855 g_warning("can't allocate memory"); \
4858 g_snprintf(__tmp, len, "\"%s\"", str); \
4863 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4864 g_warning("can't allocate memory"); \
4867 strcpy(__tmp, str); \
4873 static void compose_select_account(Compose *compose, PrefsAccount *account,
4876 gchar *from = NULL, *header = NULL;
4877 ComposeHeaderEntry *header_entry;
4878 #if GTK_CHECK_VERSION(2, 24, 0)
4882 cm_return_if_fail(account != NULL);
4884 compose->account = account;
4885 if (account->name && *account->name) {
4887 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4888 qbuf = escape_internal_quotes(buf, '"');
4889 from = g_strdup_printf("%s <%s>",
4890 qbuf, account->address);
4893 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4895 from = g_strdup_printf("<%s>",
4897 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4902 compose_set_title(compose);
4904 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4905 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4907 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4908 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4909 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4911 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4913 activate_privacy_system(compose, account, FALSE);
4915 if (!init && compose->mode != COMPOSE_REDIRECT) {
4916 undo_block(compose->undostruct);
4917 compose_insert_sig(compose, TRUE);
4918 undo_unblock(compose->undostruct);
4921 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4922 #if !GTK_CHECK_VERSION(2, 24, 0)
4923 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4925 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4926 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4927 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4930 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4931 if (account->protocol == A_NNTP) {
4932 if (!strcmp(header, _("To:")))
4933 combobox_select_by_text(
4934 GTK_COMBO_BOX(header_entry->combo),
4937 if (!strcmp(header, _("Newsgroups:")))
4938 combobox_select_by_text(
4939 GTK_COMBO_BOX(header_entry->combo),
4947 /* use account's dict info if set */
4948 if (compose->gtkaspell) {
4949 if (account->enable_default_dictionary)
4950 gtkaspell_change_dict(compose->gtkaspell,
4951 account->default_dictionary, FALSE);
4952 if (account->enable_default_alt_dictionary)
4953 gtkaspell_change_alt_dict(compose->gtkaspell,
4954 account->default_alt_dictionary);
4955 if (account->enable_default_dictionary
4956 || account->enable_default_alt_dictionary)
4957 compose_spell_menu_changed(compose);
4962 gboolean compose_check_for_valid_recipient(Compose *compose) {
4963 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4964 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4965 gboolean recipient_found = FALSE;
4969 /* free to and newsgroup list */
4970 slist_free_strings_full(compose->to_list);
4971 compose->to_list = NULL;
4973 slist_free_strings_full(compose->newsgroup_list);
4974 compose->newsgroup_list = NULL;
4976 /* search header entries for to and newsgroup entries */
4977 for (list = compose->header_list; list; list = list->next) {
4980 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4981 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4984 if (entry[0] != '\0') {
4985 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4986 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4987 compose->to_list = address_list_append(compose->to_list, entry);
4988 recipient_found = TRUE;
4991 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4992 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4993 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4994 recipient_found = TRUE;
5001 return recipient_found;
5004 static gboolean compose_check_for_set_recipients(Compose *compose)
5006 if (compose->account->set_autocc && compose->account->auto_cc) {
5007 gboolean found_other = FALSE;
5009 /* search header entries for to and newsgroup entries */
5010 for (list = compose->header_list; list; list = list->next) {
5013 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5014 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5017 if (strcmp(entry, compose->account->auto_cc)
5018 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5028 if (compose->batch) {
5029 gtk_widget_show_all(compose->window);
5031 aval = alertpanel(_("Send"),
5032 _("The only recipient is the default CC address. Send anyway?"),
5033 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5034 if (aval != G_ALERTALTERNATE)
5038 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5039 gboolean found_other = FALSE;
5041 /* search header entries for to and newsgroup entries */
5042 for (list = compose->header_list; list; list = list->next) {
5045 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5046 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5049 if (strcmp(entry, compose->account->auto_bcc)
5050 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5060 if (compose->batch) {
5061 gtk_widget_show_all(compose->window);
5063 aval = alertpanel(_("Send"),
5064 _("The only recipient is the default BCC address. Send anyway?"),
5065 GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
5066 if (aval != G_ALERTALTERNATE)
5073 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5077 if (compose_check_for_valid_recipient(compose) == FALSE) {
5078 if (compose->batch) {
5079 gtk_widget_show_all(compose->window);
5081 alertpanel_error(_("Recipient is not specified."));
5085 if (compose_check_for_set_recipients(compose) == FALSE) {
5089 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5090 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5091 if (*str == '\0' && check_everything == TRUE &&
5092 compose->mode != COMPOSE_REDIRECT) {
5094 gchar *button_label;
5097 if (compose->sending)
5098 button_label = g_strconcat("+", _("_Send"), NULL);
5100 button_label = g_strconcat("+", _("_Queue"), NULL);
5101 message = g_strdup_printf(_("Subject is empty. %s"),
5102 compose->sending?_("Send it anyway?"):
5103 _("Queue it anyway?"));
5105 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5106 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5107 ALERT_QUESTION, G_ALERTDEFAULT);
5109 if (aval & G_ALERTDISABLE) {
5110 aval &= ~G_ALERTDISABLE;
5111 prefs_common.warn_empty_subj = FALSE;
5113 if (aval != G_ALERTALTERNATE)
5118 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5124 gint compose_send(Compose *compose)
5127 FolderItem *folder = NULL;
5129 gchar *msgpath = NULL;
5130 gboolean discard_window = FALSE;
5131 gchar *errstr = NULL;
5132 gchar *tmsgid = NULL;
5133 MainWindow *mainwin = mainwindow_get_mainwindow();
5134 gboolean queued_removed = FALSE;
5136 if (prefs_common.send_dialog_invisible
5137 || compose->batch == TRUE)
5138 discard_window = TRUE;
5140 compose_allow_user_actions (compose, FALSE);
5141 compose->sending = TRUE;
5143 if (compose_check_entries(compose, TRUE) == FALSE) {
5144 if (compose->batch) {
5145 gtk_widget_show_all(compose->window);
5151 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5154 if (compose->batch) {
5155 gtk_widget_show_all(compose->window);
5158 alertpanel_error(_("Could not queue message for sending:\n\n"
5159 "Charset conversion failed."));
5160 } else if (val == -5) {
5161 alertpanel_error(_("Could not queue message for sending:\n\n"
5162 "Couldn't get recipient encryption key."));
5163 } else if (val == -6) {
5165 } else if (val == -3) {
5166 if (privacy_peek_error())
5167 alertpanel_error(_("Could not queue message for sending:\n\n"
5168 "Signature failed: %s"), privacy_get_error());
5169 } else if (val == -2 && errno != 0) {
5170 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno));
5172 alertpanel_error(_("Could not queue message for sending."));
5177 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5178 if (discard_window) {
5179 compose->sending = FALSE;
5180 compose_close(compose);
5181 /* No more compose access in the normal codepath
5182 * after this point! */
5187 alertpanel_error(_("The message was queued but could not be "
5188 "sent.\nUse \"Send queued messages\" from "
5189 "the main window to retry."));
5190 if (!discard_window) {
5197 if (msgpath == NULL) {
5198 msgpath = folder_item_fetch_msg(folder, msgnum);
5199 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5202 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5203 claws_unlink(msgpath);
5206 if (!discard_window) {
5208 if (!queued_removed)
5209 folder_item_remove_msg(folder, msgnum);
5210 folder_item_scan(folder);
5212 /* make sure we delete that */
5213 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5215 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5216 folder_item_remove_msg(folder, tmp->msgnum);
5217 procmsg_msginfo_free(tmp);
5224 if (!queued_removed)
5225 folder_item_remove_msg(folder, msgnum);
5226 folder_item_scan(folder);
5228 /* make sure we delete that */
5229 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5231 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5232 folder_item_remove_msg(folder, tmp->msgnum);
5233 procmsg_msginfo_free(tmp);
5236 if (!discard_window) {
5237 compose->sending = FALSE;
5238 compose_allow_user_actions (compose, TRUE);
5239 compose_close(compose);
5243 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5244 "the main window to retry."), errstr);
5247 alertpanel_error_log(_("The message was queued but could not be "
5248 "sent.\nUse \"Send queued messages\" from "
5249 "the main window to retry."));
5251 if (!discard_window) {
5260 toolbar_main_set_sensitive(mainwin);
5261 main_window_set_menu_sensitive(mainwin);
5267 compose_allow_user_actions (compose, TRUE);
5268 compose->sending = FALSE;
5269 compose->modified = TRUE;
5270 toolbar_main_set_sensitive(mainwin);
5271 main_window_set_menu_sensitive(mainwin);
5276 static gboolean compose_use_attach(Compose *compose)
5278 GtkTreeModel *model = gtk_tree_view_get_model
5279 (GTK_TREE_VIEW(compose->attach_clist));
5280 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5283 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5286 gchar buf[BUFFSIZE];
5288 gboolean first_to_address;
5289 gboolean first_cc_address;
5291 ComposeHeaderEntry *headerentry;
5292 const gchar *headerentryname;
5293 const gchar *cc_hdr;
5294 const gchar *to_hdr;
5295 gboolean err = FALSE;
5297 debug_print("Writing redirect header\n");
5299 cc_hdr = prefs_common_translated_header_name("Cc:");
5300 to_hdr = prefs_common_translated_header_name("To:");
5302 first_to_address = TRUE;
5303 for (list = compose->header_list; list; list = list->next) {
5304 headerentry = ((ComposeHeaderEntry *)list->data);
5305 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5307 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5308 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5309 Xstrdup_a(str, entstr, return -1);
5311 if (str[0] != '\0') {
5312 compose_convert_header
5313 (compose, buf, sizeof(buf), str,
5314 strlen("Resent-To") + 2, TRUE);
5316 if (first_to_address) {
5317 err |= (fprintf(fp, "Resent-To: ") < 0);
5318 first_to_address = FALSE;
5320 err |= (fprintf(fp, ",") < 0);
5322 err |= (fprintf(fp, "%s", buf) < 0);
5326 if (!first_to_address) {
5327 err |= (fprintf(fp, "\n") < 0);
5330 first_cc_address = TRUE;
5331 for (list = compose->header_list; list; list = list->next) {
5332 headerentry = ((ComposeHeaderEntry *)list->data);
5333 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5335 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5336 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5337 Xstrdup_a(str, strg, return -1);
5339 if (str[0] != '\0') {
5340 compose_convert_header
5341 (compose, buf, sizeof(buf), str,
5342 strlen("Resent-Cc") + 2, TRUE);
5344 if (first_cc_address) {
5345 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5346 first_cc_address = FALSE;
5348 err |= (fprintf(fp, ",") < 0);
5350 err |= (fprintf(fp, "%s", buf) < 0);
5354 if (!first_cc_address) {
5355 err |= (fprintf(fp, "\n") < 0);
5358 return (err ? -1:0);
5361 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5363 gchar buf[BUFFSIZE];
5365 const gchar *entstr;
5366 /* struct utsname utsbuf; */
5367 gboolean err = FALSE;
5369 cm_return_val_if_fail(fp != NULL, -1);
5370 cm_return_val_if_fail(compose->account != NULL, -1);
5371 cm_return_val_if_fail(compose->account->address != NULL, -1);
5374 get_rfc822_date(buf, sizeof(buf));
5375 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5378 if (compose->account->name && *compose->account->name) {
5379 compose_convert_header
5380 (compose, buf, sizeof(buf), compose->account->name,
5381 strlen("From: "), TRUE);
5382 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5383 buf, compose->account->address) < 0);
5385 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5388 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5389 if (*entstr != '\0') {
5390 Xstrdup_a(str, entstr, return -1);
5393 compose_convert_header(compose, buf, sizeof(buf), str,
5394 strlen("Subject: "), FALSE);
5395 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5399 /* Resent-Message-ID */
5400 if (compose->account->set_domain && compose->account->domain) {
5401 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5402 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5403 g_snprintf(buf, sizeof(buf), "%s",
5404 strchr(compose->account->address, '@') ?
5405 strchr(compose->account->address, '@')+1 :
5406 compose->account->address);
5408 g_snprintf(buf, sizeof(buf), "%s", "");
5411 if (compose->account->gen_msgid) {
5413 if (compose->account->msgid_with_addr) {
5414 addr = compose->account->address;
5416 generate_msgid(buf, sizeof(buf), addr);
5417 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5419 g_free(compose->msgid);
5420 compose->msgid = g_strdup(buf);
5422 compose->msgid = NULL;
5425 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5428 /* separator between header and body */
5429 err |= (fputs("\n", fp) == EOF);
5431 return (err ? -1:0);
5434 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5438 gchar buf[BUFFSIZE];
5440 gboolean skip = FALSE;
5441 gboolean err = FALSE;
5442 gchar *not_included[]={
5443 "Return-Path:", "Delivered-To:", "Received:",
5444 "Subject:", "X-UIDL:", "AF:",
5445 "NF:", "PS:", "SRH:",
5446 "SFN:", "DSR:", "MID:",
5447 "CFG:", "PT:", "S:",
5448 "RQ:", "SSV:", "NSV:",
5449 "SSH:", "R:", "MAID:",
5450 "NAID:", "RMID:", "FMID:",
5451 "SCF:", "RRCPT:", "NG:",
5452 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5453 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5454 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5455 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5456 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5459 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5460 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5464 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5466 for (i = 0; not_included[i] != NULL; i++) {
5467 if (g_ascii_strncasecmp(buf, not_included[i],
5468 strlen(not_included[i])) == 0) {
5475 if (fputs(buf, fdest) == -1)
5478 if (!prefs_common.redirect_keep_from) {
5479 if (g_ascii_strncasecmp(buf, "From:",
5480 strlen("From:")) == 0) {
5481 err |= (fputs(" (by way of ", fdest) == EOF);
5482 if (compose->account->name
5483 && *compose->account->name) {
5484 compose_convert_header
5485 (compose, buf, sizeof(buf),
5486 compose->account->name,
5489 err |= (fprintf(fdest, "%s <%s>",
5491 compose->account->address) < 0);
5493 err |= (fprintf(fdest, "%s",
5494 compose->account->address) < 0);
5495 err |= (fputs(")", fdest) == EOF);
5499 if (fputs("\n", fdest) == -1)
5506 if (compose_redirect_write_headers(compose, fdest))
5509 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5510 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5523 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5525 GtkTextBuffer *buffer;
5526 GtkTextIter start, end;
5527 gchar *chars, *tmp_enc_file, *content;
5529 const gchar *out_codeset;
5530 EncodingType encoding = ENC_UNKNOWN;
5531 MimeInfo *mimemsg, *mimetext;
5533 const gchar *src_codeset = CS_INTERNAL;
5534 gchar *from_addr = NULL;
5535 gchar *from_name = NULL;
5538 if (action == COMPOSE_WRITE_FOR_SEND)
5539 attach_parts = TRUE;
5541 /* create message MimeInfo */
5542 mimemsg = procmime_mimeinfo_new();
5543 mimemsg->type = MIMETYPE_MESSAGE;
5544 mimemsg->subtype = g_strdup("rfc822");
5545 mimemsg->content = MIMECONTENT_MEM;
5546 mimemsg->tmp = TRUE; /* must free content later */
5547 mimemsg->data.mem = compose_get_header(compose);
5549 /* Create text part MimeInfo */
5550 /* get all composed text */
5551 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5552 gtk_text_buffer_get_start_iter(buffer, &start);
5553 gtk_text_buffer_get_end_iter(buffer, &end);
5554 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5556 out_codeset = conv_get_charset_str(compose->out_encoding);
5558 if (!out_codeset && is_ascii_str(chars)) {
5559 out_codeset = CS_US_ASCII;
5560 } else if (prefs_common.outgoing_fallback_to_ascii &&
5561 is_ascii_str(chars)) {
5562 out_codeset = CS_US_ASCII;
5563 encoding = ENC_7BIT;
5567 gchar *test_conv_global_out = NULL;
5568 gchar *test_conv_reply = NULL;
5570 /* automatic mode. be automatic. */
5571 codeconv_set_strict(TRUE);
5573 out_codeset = conv_get_outgoing_charset_str();
5575 debug_print("trying to convert to %s\n", out_codeset);
5576 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5579 if (!test_conv_global_out && compose->orig_charset
5580 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5581 out_codeset = compose->orig_charset;
5582 debug_print("failure; trying to convert to %s\n", out_codeset);
5583 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5586 if (!test_conv_global_out && !test_conv_reply) {
5588 out_codeset = CS_INTERNAL;
5589 debug_print("failure; finally using %s\n", out_codeset);
5591 g_free(test_conv_global_out);
5592 g_free(test_conv_reply);
5593 codeconv_set_strict(FALSE);
5596 if (encoding == ENC_UNKNOWN) {
5597 if (prefs_common.encoding_method == CTE_BASE64)
5598 encoding = ENC_BASE64;
5599 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5600 encoding = ENC_QUOTED_PRINTABLE;
5601 else if (prefs_common.encoding_method == CTE_8BIT)
5602 encoding = ENC_8BIT;
5604 encoding = procmime_get_encoding_for_charset(out_codeset);
5607 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5608 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5610 if (action == COMPOSE_WRITE_FOR_SEND) {
5611 codeconv_set_strict(TRUE);
5612 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5613 codeconv_set_strict(FALSE);
5618 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5619 "to the specified %s charset.\n"
5620 "Send it as %s?"), out_codeset, src_codeset);
5621 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
5622 g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
5623 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5626 if (aval != G_ALERTALTERNATE) {
5631 out_codeset = src_codeset;
5637 out_codeset = src_codeset;
5642 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5643 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5644 strstr(buf, "\nFrom ") != NULL) {
5645 encoding = ENC_QUOTED_PRINTABLE;
5649 mimetext = procmime_mimeinfo_new();
5650 mimetext->content = MIMECONTENT_MEM;
5651 mimetext->tmp = TRUE; /* must free content later */
5652 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5653 * and free the data, which we need later. */
5654 mimetext->data.mem = g_strdup(buf);
5655 mimetext->type = MIMETYPE_TEXT;
5656 mimetext->subtype = g_strdup("plain");
5657 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5658 g_strdup(out_codeset));
5660 /* protect trailing spaces when signing message */
5661 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5662 privacy_system_can_sign(compose->privacy_system)) {
5663 encoding = ENC_QUOTED_PRINTABLE;
5666 debug_print("main text: %zd bytes encoded as %s in %d\n",
5667 strlen(buf), out_codeset, encoding);
5669 /* check for line length limit */
5670 if (action == COMPOSE_WRITE_FOR_SEND &&
5671 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5672 check_line_length(buf, 1000, &line) < 0) {
5675 msg = g_strdup_printf
5676 (_("Line %d exceeds the line length limit (998 bytes).\n"
5677 "The contents of the message might be broken on the way to the delivery.\n"
5679 "Send it anyway?"), line + 1);
5680 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5682 if (aval != G_ALERTALTERNATE) {
5688 if (encoding != ENC_UNKNOWN)
5689 procmime_encode_content(mimetext, encoding);
5691 /* append attachment parts */
5692 if (compose_use_attach(compose) && attach_parts) {
5693 MimeInfo *mimempart;
5694 gchar *boundary = NULL;
5695 mimempart = procmime_mimeinfo_new();
5696 mimempart->content = MIMECONTENT_EMPTY;
5697 mimempart->type = MIMETYPE_MULTIPART;
5698 mimempart->subtype = g_strdup("mixed");
5702 boundary = generate_mime_boundary(NULL);
5703 } while (strstr(buf, boundary) != NULL);
5705 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5708 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5710 g_node_append(mimempart->node, mimetext->node);
5711 g_node_append(mimemsg->node, mimempart->node);
5713 if (compose_add_attachments(compose, mimempart) < 0)
5716 g_node_append(mimemsg->node, mimetext->node);
5720 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5721 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5722 /* extract name and address */
5723 if (strstr(spec, " <") && strstr(spec, ">")) {
5724 from_addr = g_strdup(strrchr(spec, '<')+1);
5725 *(strrchr(from_addr, '>')) = '\0';
5726 from_name = g_strdup(spec);
5727 *(strrchr(from_name, '<')) = '\0';
5734 /* sign message if sending */
5735 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5736 privacy_system_can_sign(compose->privacy_system))
5737 if (!privacy_sign(compose->privacy_system, mimemsg,
5738 compose->account, from_addr)) {
5746 if (compose->use_encryption) {
5747 if (compose->encdata != NULL &&
5748 strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
5750 /* First, write an unencrypted copy and save it to outbox, if
5751 * user wants that. */
5752 if (compose->account->save_encrypted_as_clear_text) {
5753 debug_print("saving sent message unencrypted...\n");
5754 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
5758 /* fp now points to a file with headers written,
5759 * let's make a copy. */
5761 content = file_read_stream_to_str(fp);
5763 str_write_to_file(content, tmp_enc_file);
5766 /* Now write the unencrypted body. */
5767 if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
5768 procmime_write_mimeinfo(mimemsg, tmpfp);
5771 outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
5773 outbox = folder_get_default_outbox();
5775 procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
5776 claws_unlink(tmp_enc_file);
5778 g_warning("Can't open file '%s'", tmp_enc_file);
5781 g_warning("couldn't get tempfile");
5784 if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
5785 debug_print("Couldn't encrypt mime structure: %s.\n",
5786 privacy_get_error());
5787 alertpanel_error(_("Couldn't encrypt the email: %s"),
5788 privacy_get_error());
5793 procmime_write_mimeinfo(mimemsg, fp);
5795 procmime_mimeinfo_free_all(mimemsg);
5800 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5802 GtkTextBuffer *buffer;
5803 GtkTextIter start, end;
5808 if ((fp = g_fopen(file, "wb")) == NULL) {
5809 FILE_OP_ERROR(file, "fopen");
5813 /* chmod for security */
5814 if (change_file_mode_rw(fp, file) < 0) {
5815 FILE_OP_ERROR(file, "chmod");
5816 g_warning("can't change file mode");
5819 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5820 gtk_text_buffer_get_start_iter(buffer, &start);
5821 gtk_text_buffer_get_end_iter(buffer, &end);
5822 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5824 chars = conv_codeset_strdup
5825 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5834 len = strlen(chars);
5835 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5836 FILE_OP_ERROR(file, "fwrite");
5845 if (fclose(fp) == EOF) {
5846 FILE_OP_ERROR(file, "fclose");
5853 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5856 MsgInfo *msginfo = compose->targetinfo;
5858 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5859 if (!msginfo) return -1;
5861 if (!force && MSG_IS_LOCKED(msginfo->flags))
5864 item = msginfo->folder;
5865 cm_return_val_if_fail(item != NULL, -1);
5867 if (procmsg_msg_exist(msginfo) &&
5868 (folder_has_parent_of_type(item, F_QUEUE) ||
5869 folder_has_parent_of_type(item, F_DRAFT)
5870 || msginfo == compose->autosaved_draft)) {
5871 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5872 g_warning("can't remove the old message");
5875 debug_print("removed reedit target %d\n", msginfo->msgnum);
5882 static void compose_remove_draft(Compose *compose)
5885 MsgInfo *msginfo = compose->targetinfo;
5886 drafts = account_get_special_folder(compose->account, F_DRAFT);
5888 if (procmsg_msg_exist(msginfo)) {
5889 folder_item_remove_msg(drafts, msginfo->msgnum);
5894 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5895 gboolean remove_reedit_target)
5897 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5900 static gboolean compose_warn_encryption(Compose *compose)
5902 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5903 AlertValue val = G_ALERTALTERNATE;
5905 if (warning == NULL)
5908 val = alertpanel_full(_("Encryption warning"), warning,
5909 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
5910 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5911 if (val & G_ALERTDISABLE) {
5912 val &= ~G_ALERTDISABLE;
5913 if (val == G_ALERTALTERNATE)
5914 privacy_inhibit_encrypt_warning(compose->privacy_system,
5918 if (val == G_ALERTALTERNATE) {
5925 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5926 gchar **msgpath, gboolean check_subject,
5927 gboolean remove_reedit_target)
5934 PrefsAccount *mailac = NULL, *newsac = NULL;
5935 gboolean err = FALSE;
5937 debug_print("queueing message...\n");
5938 cm_return_val_if_fail(compose->account != NULL, -1);
5940 if (compose_check_entries(compose, check_subject) == FALSE) {
5941 if (compose->batch) {
5942 gtk_widget_show_all(compose->window);
5947 if (!compose->to_list && !compose->newsgroup_list) {
5948 g_warning("can't get recipient list.");
5952 if (compose->to_list) {
5953 if (compose->account->protocol != A_NNTP)
5954 mailac = compose->account;
5955 else if (cur_account && cur_account->protocol != A_NNTP)
5956 mailac = cur_account;
5957 else if (!(mailac = compose_current_mail_account())) {
5958 alertpanel_error(_("No account for sending mails available!"));
5963 if (compose->newsgroup_list) {
5964 if (compose->account->protocol == A_NNTP)
5965 newsac = compose->account;
5967 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5972 /* write queue header */
5973 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5974 G_DIR_SEPARATOR, compose, (guint) rand());
5975 debug_print("queuing to %s\n", tmp);
5976 if ((fp = g_fopen(tmp, "w+b")) == NULL) {
5977 FILE_OP_ERROR(tmp, "fopen");
5982 if (change_file_mode_rw(fp, tmp) < 0) {
5983 FILE_OP_ERROR(tmp, "chmod");
5984 g_warning("can't change file mode");
5987 /* queueing variables */
5988 err |= (fprintf(fp, "AF:\n") < 0);
5989 err |= (fprintf(fp, "NF:0\n") < 0);
5990 err |= (fprintf(fp, "PS:10\n") < 0);
5991 err |= (fprintf(fp, "SRH:1\n") < 0);
5992 err |= (fprintf(fp, "SFN:\n") < 0);
5993 err |= (fprintf(fp, "DSR:\n") < 0);
5995 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5997 err |= (fprintf(fp, "MID:\n") < 0);
5998 err |= (fprintf(fp, "CFG:\n") < 0);
5999 err |= (fprintf(fp, "PT:0\n") < 0);
6000 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
6001 err |= (fprintf(fp, "RQ:\n") < 0);
6003 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
6005 err |= (fprintf(fp, "SSV:\n") < 0);
6007 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
6009 err |= (fprintf(fp, "NSV:\n") < 0);
6010 err |= (fprintf(fp, "SSH:\n") < 0);
6011 /* write recepient list */
6012 if (compose->to_list) {
6013 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
6014 for (cur = compose->to_list->next; cur != NULL;
6016 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
6017 err |= (fprintf(fp, "\n") < 0);
6019 /* write newsgroup list */
6020 if (compose->newsgroup_list) {
6021 err |= (fprintf(fp, "NG:") < 0);
6022 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
6023 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
6024 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
6025 err |= (fprintf(fp, "\n") < 0);
6027 /* Sylpheed account IDs */
6029 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
6031 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
6034 if (compose->privacy_system != NULL) {
6035 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
6036 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
6037 if (compose->use_encryption) {
6038 if (!compose_warn_encryption(compose)) {
6044 if (mailac && mailac->encrypt_to_self) {
6045 GSList *tmp_list = g_slist_copy(compose->to_list);
6046 tmp_list = g_slist_append(tmp_list, compose->account->address);
6047 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
6048 g_slist_free(tmp_list);
6050 compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
6052 if (compose->encdata != NULL) {
6053 if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
6054 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6055 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
6056 compose->encdata) < 0);
6057 } /* else we finally dont want to encrypt */
6059 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
6060 /* and if encdata was null, it means there's been a problem in
6063 g_warning("failed to write queue message");
6072 /* Save copy folder */
6073 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6074 gchar *savefolderid;
6076 savefolderid = compose_get_save_to(compose);
6077 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6078 g_free(savefolderid);
6080 /* Save copy folder */
6081 if (compose->return_receipt) {
6082 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6084 /* Message-ID of message replying to */
6085 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6086 gchar *folderid = NULL;
6088 if (compose->replyinfo->folder)
6089 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6090 if (folderid == NULL)
6091 folderid = g_strdup("NULL");
6093 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6096 /* Message-ID of message forwarding to */
6097 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6098 gchar *folderid = NULL;
6100 if (compose->fwdinfo->folder)
6101 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6102 if (folderid == NULL)
6103 folderid = g_strdup("NULL");
6105 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6109 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6110 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6112 /* end of headers */
6113 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6115 if (compose->redirect_filename != NULL) {
6116 if (compose_redirect_write_to_file(compose, fp) < 0) {
6124 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6128 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6132 g_warning("failed to write queue message");
6138 if (fclose(fp) == EOF) {
6139 FILE_OP_ERROR(tmp, "fclose");
6145 if (item && *item) {
6148 queue = account_get_special_folder(compose->account, F_QUEUE);
6151 g_warning("can't find queue folder");
6156 folder_item_scan(queue);
6157 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6158 g_warning("can't queue the message");
6164 if (msgpath == NULL) {
6170 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6171 compose_remove_reedit_target(compose, FALSE);
6174 if ((msgnum != NULL) && (item != NULL)) {
6182 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6185 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6188 gchar *type, *subtype;
6189 GtkTreeModel *model;
6192 model = gtk_tree_view_get_model(tree_view);
6194 if (!gtk_tree_model_get_iter_first(model, &iter))
6197 gtk_tree_model_get(model, &iter,
6201 if (!is_file_exist(ainfo->file)) {
6202 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6203 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6204 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6206 if (val == G_ALERTDEFAULT) {
6211 if (g_stat(ainfo->file, &statbuf) < 0)
6214 mimepart = procmime_mimeinfo_new();
6215 mimepart->content = MIMECONTENT_FILE;
6216 mimepart->data.filename = g_strdup(ainfo->file);
6217 mimepart->tmp = FALSE; /* or we destroy our attachment */
6218 mimepart->offset = 0;
6219 mimepart->length = statbuf.st_size;
6221 type = g_strdup(ainfo->content_type);
6223 if (!strchr(type, '/')) {
6225 type = g_strdup("application/octet-stream");
6228 subtype = strchr(type, '/') + 1;
6229 *(subtype - 1) = '\0';
6230 mimepart->type = procmime_get_media_type(type);
6231 mimepart->subtype = g_strdup(subtype);
6234 if (mimepart->type == MIMETYPE_MESSAGE &&
6235 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6236 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6237 } else if (mimepart->type == MIMETYPE_TEXT) {
6238 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6239 /* Text parts with no name come from multipart/alternative
6240 * forwards. Make sure the recipient won't look at the
6241 * original HTML part by mistake. */
6242 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6243 ainfo->name = g_strdup_printf(_("Original %s part"),
6247 g_hash_table_insert(mimepart->typeparameters,
6248 g_strdup("charset"), g_strdup(ainfo->charset));
6250 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6251 if (mimepart->type == MIMETYPE_APPLICATION &&
6252 !strcmp2(mimepart->subtype, "octet-stream"))
6253 g_hash_table_insert(mimepart->typeparameters,
6254 g_strdup("name"), g_strdup(ainfo->name));
6255 g_hash_table_insert(mimepart->dispositionparameters,
6256 g_strdup("filename"), g_strdup(ainfo->name));
6257 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6260 if (mimepart->type == MIMETYPE_MESSAGE
6261 || mimepart->type == MIMETYPE_MULTIPART)
6262 ainfo->encoding = ENC_BINARY;
6263 else if (compose->use_signing) {
6264 if (ainfo->encoding == ENC_7BIT)
6265 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6266 else if (ainfo->encoding == ENC_8BIT)
6267 ainfo->encoding = ENC_BASE64;
6272 procmime_encode_content(mimepart, ainfo->encoding);
6274 g_node_append(parent->node, mimepart->node);
6275 } while (gtk_tree_model_iter_next(model, &iter));
6280 static gchar *compose_quote_list_of_addresses(gchar *str)
6282 GSList *list = NULL, *item = NULL;
6283 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6285 list = address_list_append_with_comments(list, str);
6286 for (item = list; item != NULL; item = item->next) {
6287 gchar *spec = item->data;
6288 gchar *endofname = strstr(spec, " <");
6289 if (endofname != NULL) {
6292 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6293 qqname = escape_internal_quotes(qname, '"');
6295 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6296 gchar *addr = g_strdup(endofname);
6297 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6298 faddr = g_strconcat(name, addr, NULL);
6301 debug_print("new auto-quoted address: '%s'", faddr);
6305 result = g_strdup((faddr != NULL)? faddr: spec);
6307 result = g_strconcat(result,
6309 (faddr != NULL)? faddr: spec,
6312 if (faddr != NULL) {
6317 slist_free_strings_full(list);
6322 #define IS_IN_CUSTOM_HEADER(header) \
6323 (compose->account->add_customhdr && \
6324 custom_header_find(compose->account->customhdr_list, header) != NULL)
6326 static void compose_add_headerfield_from_headerlist(Compose *compose,
6328 const gchar *fieldname,
6329 const gchar *seperator)
6331 gchar *str, *fieldname_w_colon;
6332 gboolean add_field = FALSE;
6334 ComposeHeaderEntry *headerentry;
6335 const gchar *headerentryname;
6336 const gchar *trans_fieldname;
6339 if (IS_IN_CUSTOM_HEADER(fieldname))
6342 debug_print("Adding %s-fields\n", fieldname);
6344 fieldstr = g_string_sized_new(64);
6346 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6347 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6349 for (list = compose->header_list; list; list = list->next) {
6350 headerentry = ((ComposeHeaderEntry *)list->data);
6351 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6353 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6354 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6356 str = compose_quote_list_of_addresses(ustr);
6358 if (str != NULL && str[0] != '\0') {
6360 g_string_append(fieldstr, seperator);
6361 g_string_append(fieldstr, str);
6370 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6371 compose_convert_header
6372 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6373 strlen(fieldname) + 2, TRUE);
6374 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6378 g_free(fieldname_w_colon);
6379 g_string_free(fieldstr, TRUE);
6384 static gchar *compose_get_manual_headers_info(Compose *compose)
6386 GString *sh_header = g_string_new(" ");
6388 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6390 for (list = compose->header_list; list; list = list->next) {
6391 ComposeHeaderEntry *headerentry;
6394 gchar *headername_wcolon;
6395 const gchar *headername_trans;
6397 gboolean standard_header = FALSE;
6399 headerentry = ((ComposeHeaderEntry *)list->data);
6401 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6403 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6408 if (!strstr(tmp, ":")) {
6409 headername_wcolon = g_strconcat(tmp, ":", NULL);
6410 headername = g_strdup(tmp);
6412 headername_wcolon = g_strdup(tmp);
6413 headername = g_strdup(strtok(tmp, ":"));
6417 string = std_headers;
6418 while (*string != NULL) {
6419 headername_trans = prefs_common_translated_header_name(*string);
6420 if (!strcmp(headername_trans, headername_wcolon))
6421 standard_header = TRUE;
6424 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6425 g_string_append_printf(sh_header, "%s ", headername);
6427 g_free(headername_wcolon);
6429 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6430 return g_string_free(sh_header, FALSE);
6433 static gchar *compose_get_header(Compose *compose)
6435 gchar buf[BUFFSIZE];
6436 const gchar *entry_str;
6440 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6442 gchar *from_name = NULL, *from_address = NULL;
6445 cm_return_val_if_fail(compose->account != NULL, NULL);
6446 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6448 header = g_string_sized_new(64);
6451 get_rfc822_date(buf, sizeof(buf));
6452 g_string_append_printf(header, "Date: %s\n", buf);
6456 if (compose->account->name && *compose->account->name) {
6458 QUOTE_IF_REQUIRED(buf, compose->account->name);
6459 tmp = g_strdup_printf("%s <%s>",
6460 buf, compose->account->address);
6462 tmp = g_strdup_printf("%s",
6463 compose->account->address);
6465 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6466 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6468 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6469 from_address = g_strdup(compose->account->address);
6471 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6472 /* extract name and address */
6473 if (strstr(spec, " <") && strstr(spec, ">")) {
6474 from_address = g_strdup(strrchr(spec, '<')+1);
6475 *(strrchr(from_address, '>')) = '\0';
6476 from_name = g_strdup(spec);
6477 *(strrchr(from_name, '<')) = '\0';
6480 from_address = g_strdup(spec);
6487 if (from_name && *from_name) {
6489 compose_convert_header
6490 (compose, buf, sizeof(buf), from_name,
6491 strlen("From: "), TRUE);
6492 QUOTE_IF_REQUIRED(name, buf);
6493 qname = escape_internal_quotes(name, '"');
6495 g_string_append_printf(header, "From: %s <%s>\n",
6496 qname, from_address);
6500 g_string_append_printf(header, "From: %s\n", from_address);
6503 g_free(from_address);
6506 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6509 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6512 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6516 * If this account is a NNTP account remove Bcc header from
6517 * message body since it otherwise will be publicly shown
6519 if (compose->account->protocol != A_NNTP)
6520 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6523 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6525 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6528 compose_convert_header(compose, buf, sizeof(buf), str,
6529 strlen("Subject: "), FALSE);
6530 g_string_append_printf(header, "Subject: %s\n", buf);
6536 if (compose->account->set_domain && compose->account->domain) {
6537 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6538 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6539 g_snprintf(buf, sizeof(buf), "%s",
6540 strchr(compose->account->address, '@') ?
6541 strchr(compose->account->address, '@')+1 :
6542 compose->account->address);
6544 g_snprintf(buf, sizeof(buf), "%s", "");
6547 if (compose->account->gen_msgid) {
6549 if (compose->account->msgid_with_addr) {
6550 addr = compose->account->address;
6552 generate_msgid(buf, sizeof(buf), addr);
6553 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6555 g_free(compose->msgid);
6556 compose->msgid = g_strdup(buf);
6558 compose->msgid = NULL;
6561 if (compose->remove_references == FALSE) {
6563 if (compose->inreplyto && compose->to_list)
6564 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6567 if (compose->references)
6568 g_string_append_printf(header, "References: %s\n", compose->references);
6572 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6575 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6578 if (compose->account->organization &&
6579 strlen(compose->account->organization) &&
6580 !IS_IN_CUSTOM_HEADER("Organization")) {
6581 compose_convert_header(compose, buf, sizeof(buf),
6582 compose->account->organization,
6583 strlen("Organization: "), FALSE);
6584 g_string_append_printf(header, "Organization: %s\n", buf);
6587 /* Program version and system info */
6588 if (compose->account->gen_xmailer &&
6589 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6590 !compose->newsgroup_list) {
6591 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6593 gtk_major_version, gtk_minor_version, gtk_micro_version,
6596 if (compose->account->gen_xmailer &&
6597 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6598 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6600 gtk_major_version, gtk_minor_version, gtk_micro_version,
6604 /* custom headers */
6605 if (compose->account->add_customhdr) {
6608 for (cur = compose->account->customhdr_list; cur != NULL;
6610 CustomHeader *chdr = (CustomHeader *)cur->data;
6612 if (custom_header_is_allowed(chdr->name)
6613 && chdr->value != NULL
6614 && *(chdr->value) != '\0') {
6615 compose_convert_header
6616 (compose, buf, sizeof(buf),
6618 strlen(chdr->name) + 2, FALSE);
6619 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6624 /* Automatic Faces and X-Faces */
6625 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6626 g_string_append_printf(header, "X-Face: %s\n", buf);
6628 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6629 g_string_append_printf(header, "X-Face: %s\n", buf);
6631 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6632 g_string_append_printf(header, "Face: %s\n", buf);
6634 else if (get_default_face (buf, sizeof(buf)) == 0) {
6635 g_string_append_printf(header, "Face: %s\n", buf);
6639 switch (compose->priority) {
6640 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6641 "X-Priority: 1 (Highest)\n");
6643 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6644 "X-Priority: 2 (High)\n");
6646 case PRIORITY_NORMAL: break;
6647 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6648 "X-Priority: 4 (Low)\n");
6650 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6651 "X-Priority: 5 (Lowest)\n");
6653 default: debug_print("compose: priority unknown : %d\n",
6657 /* Request Return Receipt */
6658 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6659 if (compose->return_receipt) {
6660 if (compose->account->name
6661 && *compose->account->name) {
6662 compose_convert_header(compose, buf, sizeof(buf),
6663 compose->account->name,
6664 strlen("Disposition-Notification-To: "),
6666 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6668 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6672 /* get special headers */
6673 for (list = compose->header_list; list; list = list->next) {
6674 ComposeHeaderEntry *headerentry;
6677 gchar *headername_wcolon;
6678 const gchar *headername_trans;
6681 gboolean standard_header = FALSE;
6683 headerentry = ((ComposeHeaderEntry *)list->data);
6685 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6687 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6692 if (!strstr(tmp, ":")) {
6693 headername_wcolon = g_strconcat(tmp, ":", NULL);
6694 headername = g_strdup(tmp);
6696 headername_wcolon = g_strdup(tmp);
6697 headername = g_strdup(strtok(tmp, ":"));
6701 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6702 Xstrdup_a(headervalue, entry_str, return NULL);
6703 subst_char(headervalue, '\r', ' ');
6704 subst_char(headervalue, '\n', ' ');
6705 string = std_headers;
6706 while (*string != NULL) {
6707 headername_trans = prefs_common_translated_header_name(*string);
6708 if (!strcmp(headername_trans, headername_wcolon))
6709 standard_header = TRUE;
6712 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6713 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6716 g_free(headername_wcolon);
6720 g_string_free(header, FALSE);
6725 #undef IS_IN_CUSTOM_HEADER
6727 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6728 gint header_len, gboolean addr_field)
6730 gchar *tmpstr = NULL;
6731 const gchar *out_codeset = NULL;
6733 cm_return_if_fail(src != NULL);
6734 cm_return_if_fail(dest != NULL);
6736 if (len < 1) return;
6738 tmpstr = g_strdup(src);
6740 subst_char(tmpstr, '\n', ' ');
6741 subst_char(tmpstr, '\r', ' ');
6744 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6745 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6746 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6751 codeconv_set_strict(TRUE);
6752 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6753 conv_get_charset_str(compose->out_encoding));
6754 codeconv_set_strict(FALSE);
6756 if (!dest || *dest == '\0') {
6757 gchar *test_conv_global_out = NULL;
6758 gchar *test_conv_reply = NULL;
6760 /* automatic mode. be automatic. */
6761 codeconv_set_strict(TRUE);
6763 out_codeset = conv_get_outgoing_charset_str();
6765 debug_print("trying to convert to %s\n", out_codeset);
6766 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6769 if (!test_conv_global_out && compose->orig_charset
6770 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6771 out_codeset = compose->orig_charset;
6772 debug_print("failure; trying to convert to %s\n", out_codeset);
6773 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6776 if (!test_conv_global_out && !test_conv_reply) {
6778 out_codeset = CS_INTERNAL;
6779 debug_print("finally using %s\n", out_codeset);
6781 g_free(test_conv_global_out);
6782 g_free(test_conv_reply);
6783 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6785 codeconv_set_strict(FALSE);
6790 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6794 cm_return_if_fail(user_data != NULL);
6796 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6797 g_strstrip(address);
6798 if (*address != '\0') {
6799 gchar *name = procheader_get_fromname(address);
6800 extract_address(address);
6801 #ifndef USE_NEW_ADDRBOOK
6802 addressbook_add_contact(name, address, NULL, NULL);
6804 debug_print("%s: %s\n", name, address);
6805 if (addressadd_selection(name, address, NULL, NULL)) {
6806 debug_print( "addressbook_add_contact - added\n" );
6813 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6815 GtkWidget *menuitem;
6818 cm_return_if_fail(menu != NULL);
6819 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6821 menuitem = gtk_separator_menu_item_new();
6822 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6823 gtk_widget_show(menuitem);
6825 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6826 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6828 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6829 g_strstrip(address);
6830 if (*address == '\0') {
6831 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6834 g_signal_connect(G_OBJECT(menuitem), "activate",
6835 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6836 gtk_widget_show(menuitem);
6839 void compose_add_extra_header(gchar *header, GtkListStore *model)
6842 if (strcmp(header, "")) {
6843 COMBOBOX_ADD(model, header, COMPOSE_TO);
6847 void compose_add_extra_header_entries(GtkListStore *model)
6851 gchar buf[BUFFSIZE];
6854 if (extra_headers == NULL) {
6855 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6856 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6857 debug_print("extra headers file not found\n");
6858 goto extra_headers_done;
6860 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6861 lastc = strlen(buf) - 1; /* remove trailing control chars */
6862 while (lastc >= 0 && buf[lastc] != ':')
6863 buf[lastc--] = '\0';
6864 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6865 buf[lastc] = '\0'; /* remove trailing : for comparison */
6866 if (custom_header_is_allowed(buf)) {
6868 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6871 g_message("disallowed extra header line: %s\n", buf);
6875 g_message("invalid extra header line: %s\n", buf);
6881 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6882 extra_headers = g_slist_reverse(extra_headers);
6884 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6887 static void compose_create_header_entry(Compose *compose)
6889 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6896 const gchar *header = NULL;
6897 ComposeHeaderEntry *headerentry;
6898 gboolean standard_header = FALSE;
6899 GtkListStore *model;
6902 headerentry = g_new0(ComposeHeaderEntry, 1);
6904 /* Combo box model */
6905 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6906 #if !GTK_CHECK_VERSION(2, 24, 0)
6907 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6909 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6911 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6913 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6915 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6916 COMPOSE_NEWSGROUPS);
6917 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6919 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6920 COMPOSE_FOLLOWUPTO);
6921 compose_add_extra_header_entries(model);
6924 #if GTK_CHECK_VERSION(2, 24, 0)
6925 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6926 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6927 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6928 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6929 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6931 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6932 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6933 G_CALLBACK(compose_grab_focus_cb), compose);
6934 gtk_widget_show(combo);
6936 /* Putting only the combobox child into focus chain of its parent causes
6937 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6938 * This eliminates need to pres Tab twice in order to really get from the
6939 * combobox to next widget. */
6941 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6942 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6945 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6946 compose->header_nextrow, compose->header_nextrow+1,
6947 GTK_SHRINK, GTK_FILL, 0, 0);
6948 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6949 const gchar *last_header_entry = gtk_entry_get_text(
6950 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6952 while (*string != NULL) {
6953 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6954 standard_header = TRUE;
6957 if (standard_header)
6958 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6960 if (!compose->header_last || !standard_header) {
6961 switch(compose->account->protocol) {
6963 header = prefs_common_translated_header_name("Newsgroups:");
6966 header = prefs_common_translated_header_name("To:");
6971 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6973 gtk_editable_set_editable(
6974 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
6975 prefs_common.type_any_header);
6977 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6978 G_CALLBACK(compose_grab_focus_cb), compose);
6980 /* Entry field with cleanup button */
6981 button = gtk_button_new();
6982 gtk_button_set_image(GTK_BUTTON(button),
6983 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6984 gtk_widget_show(button);
6985 CLAWS_SET_TIP(button,
6986 _("Delete entry contents"));
6987 entry = gtk_entry_new();
6988 gtk_widget_show(entry);
6989 CLAWS_SET_TIP(entry,
6990 _("Use <tab> to autocomplete from addressbook"));
6991 hbox = gtk_hbox_new (FALSE, 0);
6992 gtk_widget_show(hbox);
6993 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6994 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6995 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6996 compose->header_nextrow, compose->header_nextrow+1,
6997 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6999 g_signal_connect(G_OBJECT(entry), "key-press-event",
7000 G_CALLBACK(compose_headerentry_key_press_event_cb),
7002 g_signal_connect(G_OBJECT(entry), "changed",
7003 G_CALLBACK(compose_headerentry_changed_cb),
7005 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
7006 G_CALLBACK(compose_grab_focus_cb), compose);
7008 g_signal_connect(G_OBJECT(button), "clicked",
7009 G_CALLBACK(compose_headerentry_button_clicked_cb),
7013 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7014 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7015 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7016 g_signal_connect(G_OBJECT(entry), "drag_data_received",
7017 G_CALLBACK(compose_header_drag_received_cb),
7019 g_signal_connect(G_OBJECT(entry), "drag-drop",
7020 G_CALLBACK(compose_drag_drop),
7022 g_signal_connect(G_OBJECT(entry), "populate-popup",
7023 G_CALLBACK(compose_entry_popup_extend),
7026 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
7028 headerentry->compose = compose;
7029 headerentry->combo = combo;
7030 headerentry->entry = entry;
7031 headerentry->button = button;
7032 headerentry->hbox = hbox;
7033 headerentry->headernum = compose->header_nextrow;
7034 headerentry->type = PREF_NONE;
7036 compose->header_nextrow++;
7037 compose->header_last = headerentry;
7038 compose->header_list =
7039 g_slist_append(compose->header_list,
7043 static void compose_add_header_entry(Compose *compose, const gchar *header,
7044 gchar *text, ComposePrefType pref_type)
7046 ComposeHeaderEntry *last_header = compose->header_last;
7047 gchar *tmp = g_strdup(text), *email;
7048 gboolean replyto_hdr;
7050 replyto_hdr = (!strcasecmp(header,
7051 prefs_common_translated_header_name("Reply-To:")) ||
7053 prefs_common_translated_header_name("Followup-To:")) ||
7055 prefs_common_translated_header_name("In-Reply-To:")));
7057 extract_address(tmp);
7058 email = g_utf8_strdown(tmp, -1);
7060 if (replyto_hdr == FALSE &&
7061 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
7063 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7064 header, text, (gint) pref_type);
7070 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
7071 gtk_entry_set_text(GTK_ENTRY(
7072 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7074 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7075 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7076 last_header->type = pref_type;
7078 if (replyto_hdr == FALSE)
7079 g_hash_table_insert(compose->email_hashtable, email,
7080 GUINT_TO_POINTER(1));
7087 static void compose_destroy_headerentry(Compose *compose,
7088 ComposeHeaderEntry *headerentry)
7090 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7093 extract_address(text);
7094 email = g_utf8_strdown(text, -1);
7095 g_hash_table_remove(compose->email_hashtable, email);
7099 gtk_widget_destroy(headerentry->combo);
7100 gtk_widget_destroy(headerentry->entry);
7101 gtk_widget_destroy(headerentry->button);
7102 gtk_widget_destroy(headerentry->hbox);
7103 g_free(headerentry);
7106 static void compose_remove_header_entries(Compose *compose)
7109 for (list = compose->header_list; list; list = list->next)
7110 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7112 compose->header_last = NULL;
7113 g_slist_free(compose->header_list);
7114 compose->header_list = NULL;
7115 compose->header_nextrow = 1;
7116 compose_create_header_entry(compose);
7119 static GtkWidget *compose_create_header(Compose *compose)
7121 GtkWidget *from_optmenu_hbox;
7122 GtkWidget *header_scrolledwin_main;
7123 GtkWidget *header_table_main;
7124 GtkWidget *header_scrolledwin;
7125 GtkWidget *header_table;
7127 /* parent with account selection and from header */
7128 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7129 gtk_widget_show(header_scrolledwin_main);
7130 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7132 header_table_main = gtk_table_new(2, 2, FALSE);
7133 gtk_widget_show(header_table_main);
7134 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7135 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7136 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7138 from_optmenu_hbox = compose_account_option_menu_create(compose);
7139 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7140 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7142 /* child with header labels and entries */
7143 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7144 gtk_widget_show(header_scrolledwin);
7145 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7147 header_table = gtk_table_new(2, 2, FALSE);
7148 gtk_widget_show(header_table);
7149 gtk_container_set_border_width(GTK_CONTAINER(header_table), 0);
7150 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7151 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table),
7152 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin)));
7153 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin))), GTK_SHADOW_NONE);
7155 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7156 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7158 compose->header_table = header_table;
7159 compose->header_list = NULL;
7160 compose->header_nextrow = 0;
7162 compose_create_header_entry(compose);
7164 compose->table = NULL;
7166 return header_scrolledwin_main;
7169 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7171 Compose *compose = (Compose *)data;
7172 GdkEventButton event;
7175 event.time = gtk_get_current_event_time();
7177 return attach_button_pressed(compose->attach_clist, &event, compose);
7180 static GtkWidget *compose_create_attach(Compose *compose)
7182 GtkWidget *attach_scrwin;
7183 GtkWidget *attach_clist;
7185 GtkListStore *store;
7186 GtkCellRenderer *renderer;
7187 GtkTreeViewColumn *column;
7188 GtkTreeSelection *selection;
7190 /* attachment list */
7191 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7192 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7193 GTK_POLICY_AUTOMATIC,
7194 GTK_POLICY_AUTOMATIC);
7195 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7197 store = gtk_list_store_new(N_ATTACH_COLS,
7203 G_TYPE_AUTO_POINTER,
7205 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7206 (GTK_TREE_MODEL(store)));
7207 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7208 g_object_unref(store);
7210 renderer = gtk_cell_renderer_text_new();
7211 column = gtk_tree_view_column_new_with_attributes
7212 (_("Mime type"), renderer, "text",
7213 COL_MIMETYPE, NULL);
7214 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7216 renderer = gtk_cell_renderer_text_new();
7217 column = gtk_tree_view_column_new_with_attributes
7218 (_("Size"), renderer, "text",
7220 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7222 renderer = gtk_cell_renderer_text_new();
7223 column = gtk_tree_view_column_new_with_attributes
7224 (_("Name"), renderer, "text",
7226 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7228 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7229 prefs_common.use_stripes_everywhere);
7230 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7231 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7233 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7234 G_CALLBACK(attach_selected), compose);
7235 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7236 G_CALLBACK(attach_button_pressed), compose);
7237 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7238 G_CALLBACK(popup_attach_button_pressed), compose);
7239 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7240 G_CALLBACK(attach_key_pressed), compose);
7243 gtk_drag_dest_set(attach_clist,
7244 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7245 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7246 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7247 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7248 G_CALLBACK(compose_attach_drag_received_cb),
7250 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7251 G_CALLBACK(compose_drag_drop),
7254 compose->attach_scrwin = attach_scrwin;
7255 compose->attach_clist = attach_clist;
7257 return attach_scrwin;
7260 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7261 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7263 static GtkWidget *compose_create_others(Compose *compose)
7266 GtkWidget *savemsg_checkbtn;
7267 GtkWidget *savemsg_combo;
7268 GtkWidget *savemsg_select;
7271 gchar *folderidentifier;
7273 /* Table for settings */
7274 table = gtk_table_new(3, 1, FALSE);
7275 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7276 gtk_widget_show(table);
7277 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7280 /* Save Message to folder */
7281 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7282 gtk_widget_show(savemsg_checkbtn);
7283 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7284 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7285 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7287 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7288 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7290 #if !GTK_CHECK_VERSION(2, 24, 0)
7291 savemsg_combo = gtk_combo_box_entry_new_text();
7293 savemsg_combo = gtk_combo_box_text_new_with_entry();
7295 compose->savemsg_checkbtn = savemsg_checkbtn;
7296 compose->savemsg_combo = savemsg_combo;
7297 gtk_widget_show(savemsg_combo);
7299 if (prefs_common.compose_save_to_history)
7300 #if !GTK_CHECK_VERSION(2, 24, 0)
7301 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7302 prefs_common.compose_save_to_history);
7304 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7305 prefs_common.compose_save_to_history);
7307 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7308 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7309 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7310 G_CALLBACK(compose_grab_focus_cb), compose);
7311 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7312 folderidentifier = folder_item_get_identifier(account_get_special_folder
7313 (compose->account, F_OUTBOX));
7314 compose_set_save_to(compose, folderidentifier);
7315 g_free(folderidentifier);
7318 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7319 gtk_widget_show(savemsg_select);
7320 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7321 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7322 G_CALLBACK(compose_savemsg_select_cb),
7328 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7330 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7331 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7334 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7339 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7342 path = folder_item_get_identifier(dest);
7344 compose_set_save_to(compose, path);
7348 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7349 GdkAtom clip, GtkTextIter *insert_place);
7352 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7356 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7358 if (event->button == 3) {
7360 GtkTextIter sel_start, sel_end;
7361 gboolean stuff_selected;
7363 /* move the cursor to allow GtkAspell to check the word
7364 * under the mouse */
7365 if (event->x && event->y) {
7366 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7367 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7369 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7372 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7373 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7376 stuff_selected = gtk_text_buffer_get_selection_bounds(
7378 &sel_start, &sel_end);
7380 gtk_text_buffer_place_cursor (buffer, &iter);
7381 /* reselect stuff */
7383 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7384 gtk_text_buffer_select_range(buffer,
7385 &sel_start, &sel_end);
7387 return FALSE; /* pass the event so that the right-click goes through */
7390 if (event->button == 2) {
7395 /* get the middle-click position to paste at the correct place */
7396 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7397 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7399 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7402 entry_paste_clipboard(compose, text,
7403 prefs_common.linewrap_pastes,
7404 GDK_SELECTION_PRIMARY, &iter);
7412 static void compose_spell_menu_changed(void *data)
7414 Compose *compose = (Compose *)data;
7416 GtkWidget *menuitem;
7417 GtkWidget *parent_item;
7418 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7421 if (compose->gtkaspell == NULL)
7424 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7425 "/Menu/Spelling/Options");
7427 /* setting the submenu removes /Spelling/Options from the factory
7428 * so we need to save it */
7430 if (parent_item == NULL) {
7431 parent_item = compose->aspell_options_menu;
7432 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7434 compose->aspell_options_menu = parent_item;
7436 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7438 spell_menu = g_slist_reverse(spell_menu);
7439 for (items = spell_menu;
7440 items; items = items->next) {
7441 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7442 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7443 gtk_widget_show(GTK_WIDGET(menuitem));
7445 g_slist_free(spell_menu);
7447 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7448 gtk_widget_show(parent_item);
7451 static void compose_dict_changed(void *data)
7453 Compose *compose = (Compose *) data;
7455 if(!compose->gtkaspell)
7457 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7460 gtkaspell_highlight_all(compose->gtkaspell);
7461 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7465 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7467 Compose *compose = (Compose *)data;
7468 GdkEventButton event;
7471 event.time = gtk_get_current_event_time();
7475 return text_clicked(compose->text, &event, compose);
7478 static gboolean compose_force_window_origin = TRUE;
7479 static Compose *compose_create(PrefsAccount *account,
7488 GtkWidget *handlebox;
7490 GtkWidget *notebook;
7492 GtkWidget *attach_hbox;
7493 GtkWidget *attach_lab1;
7494 GtkWidget *attach_lab2;
7499 GtkWidget *subject_hbox;
7500 GtkWidget *subject_frame;
7501 GtkWidget *subject_entry;
7505 GtkWidget *edit_vbox;
7506 GtkWidget *ruler_hbox;
7508 GtkWidget *scrolledwin;
7510 GtkTextBuffer *buffer;
7511 GtkClipboard *clipboard;
7513 UndoMain *undostruct;
7515 GtkWidget *popupmenu;
7516 GtkWidget *tmpl_menu;
7517 GtkActionGroup *action_group = NULL;
7520 GtkAspell * gtkaspell = NULL;
7523 static GdkGeometry geometry;
7525 cm_return_val_if_fail(account != NULL, NULL);
7527 debug_print("Creating compose window...\n");
7528 compose = g_new0(Compose, 1);
7530 compose->batch = batch;
7531 compose->account = account;
7532 compose->folder = folder;
7534 compose->mutex = cm_mutex_new();
7535 compose->set_cursor_pos = -1;
7537 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7539 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7540 gtk_widget_set_size_request(window, prefs_common.compose_width,
7541 prefs_common.compose_height);
7543 if (!geometry.max_width) {
7544 geometry.max_width = gdk_screen_width();
7545 geometry.max_height = gdk_screen_height();
7548 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7549 &geometry, GDK_HINT_MAX_SIZE);
7550 if (!geometry.min_width) {
7551 geometry.min_width = 600;
7552 geometry.min_height = 440;
7554 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7555 &geometry, GDK_HINT_MIN_SIZE);
7557 #ifndef GENERIC_UMPC
7558 if (compose_force_window_origin)
7559 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7560 prefs_common.compose_y);
7562 g_signal_connect(G_OBJECT(window), "delete_event",
7563 G_CALLBACK(compose_delete_cb), compose);
7564 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7565 gtk_widget_realize(window);
7567 gtkut_widget_set_composer_icon(window);
7569 vbox = gtk_vbox_new(FALSE, 0);
7570 gtk_container_add(GTK_CONTAINER(window), vbox);
7572 compose->ui_manager = gtk_ui_manager_new();
7573 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7574 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7575 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7576 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7577 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7578 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7579 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7580 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7581 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7582 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7634 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7636 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7638 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7640 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7642 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7644 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7646 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7648 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7675 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7687 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7688 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7689 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7690 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7692 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7694 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7695 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7696 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7697 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7698 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7700 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7701 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)
7702 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)
7703 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7705 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7707 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7708 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)
7709 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)
7711 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7713 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7714 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)
7715 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7717 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7718 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)
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7724 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)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR, "Options/Encoding/Cyrillic/"CS_MACCYR, GTK_UI_MANAGER_MENUITEM)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7731 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)
7732 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)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7734 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7741 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7743 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7744 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7745 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)
7747 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7748 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7749 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7753 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7754 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7755 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7756 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7757 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7758 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7761 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7763 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7764 gtk_widget_show_all(menubar);
7766 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7767 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7769 if (prefs_common.toolbar_detachable) {
7770 handlebox = gtk_handle_box_new();
7772 handlebox = gtk_hbox_new(FALSE, 0);
7774 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7776 gtk_widget_realize(handlebox);
7777 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7780 vbox2 = gtk_vbox_new(FALSE, 2);
7781 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7782 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7785 notebook = gtk_notebook_new();
7786 gtk_widget_show(notebook);
7788 /* header labels and entries */
7789 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7790 compose_create_header(compose),
7791 gtk_label_new_with_mnemonic(_("Hea_der")));
7792 /* attachment list */
7793 attach_hbox = gtk_hbox_new(FALSE, 0);
7794 gtk_widget_show(attach_hbox);
7796 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7797 gtk_widget_show(attach_lab1);
7798 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7800 attach_lab2 = gtk_label_new("");
7801 gtk_widget_show(attach_lab2);
7802 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7804 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7805 compose_create_attach(compose),
7808 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7809 compose_create_others(compose),
7810 gtk_label_new_with_mnemonic(_("Othe_rs")));
7813 subject_hbox = gtk_hbox_new(FALSE, 0);
7814 gtk_widget_show(subject_hbox);
7816 subject_frame = gtk_frame_new(NULL);
7817 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7818 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7819 gtk_widget_show(subject_frame);
7821 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7822 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7823 gtk_widget_show(subject);
7825 label = gtk_label_new_with_mnemonic(_("S_ubject:"));
7826 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7827 gtk_widget_show(label);
7830 subject_entry = claws_spell_entry_new();
7832 subject_entry = gtk_entry_new();
7834 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7835 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7836 G_CALLBACK(compose_grab_focus_cb), compose);
7837 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7838 gtk_widget_show(subject_entry);
7839 compose->subject_entry = subject_entry;
7840 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7842 edit_vbox = gtk_vbox_new(FALSE, 0);
7844 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7847 ruler_hbox = gtk_hbox_new(FALSE, 0);
7848 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7850 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7851 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7852 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7856 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7857 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7858 GTK_POLICY_AUTOMATIC,
7859 GTK_POLICY_AUTOMATIC);
7860 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7862 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7864 text = gtk_text_view_new();
7865 if (prefs_common.show_compose_margin) {
7866 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7867 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7869 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7870 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7871 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7872 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7873 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7875 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7876 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7877 G_CALLBACK(compose_edit_size_alloc),
7879 g_signal_connect(G_OBJECT(buffer), "changed",
7880 G_CALLBACK(compose_changed_cb), compose);
7881 g_signal_connect(G_OBJECT(text), "grab_focus",
7882 G_CALLBACK(compose_grab_focus_cb), compose);
7883 g_signal_connect(G_OBJECT(buffer), "insert_text",
7884 G_CALLBACK(text_inserted), compose);
7885 g_signal_connect(G_OBJECT(text), "button_press_event",
7886 G_CALLBACK(text_clicked), compose);
7887 g_signal_connect(G_OBJECT(text), "popup-menu",
7888 G_CALLBACK(compose_popup_menu), compose);
7889 g_signal_connect(G_OBJECT(subject_entry), "changed",
7890 G_CALLBACK(compose_changed_cb), compose);
7891 g_signal_connect(G_OBJECT(subject_entry), "activate",
7892 G_CALLBACK(compose_subject_entry_activated), compose);
7895 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7896 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7897 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7898 g_signal_connect(G_OBJECT(text), "drag_data_received",
7899 G_CALLBACK(compose_insert_drag_received_cb),
7901 g_signal_connect(G_OBJECT(text), "drag-drop",
7902 G_CALLBACK(compose_drag_drop),
7904 g_signal_connect(G_OBJECT(text), "key-press-event",
7905 G_CALLBACK(completion_set_focus_to_subject),
7907 gtk_widget_show_all(vbox);
7909 /* pane between attach clist and text */
7910 paned = gtk_vpaned_new();
7911 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7912 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7913 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7914 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7915 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7916 G_CALLBACK(compose_notebook_size_alloc), paned);
7918 gtk_widget_show_all(paned);
7921 if (prefs_common.textfont) {
7922 PangoFontDescription *font_desc;
7924 font_desc = pango_font_description_from_string
7925 (prefs_common.textfont);
7927 gtk_widget_modify_font(text, font_desc);
7928 pango_font_description_free(font_desc);
7932 gtk_action_group_add_actions(action_group, compose_popup_entries,
7933 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7934 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7935 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7936 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7937 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7938 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7939 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7941 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7943 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7944 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7945 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7947 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7949 undostruct = undo_init(text);
7950 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7953 address_completion_start(window);
7955 compose->window = window;
7956 compose->vbox = vbox;
7957 compose->menubar = menubar;
7958 compose->handlebox = handlebox;
7960 compose->vbox2 = vbox2;
7962 compose->paned = paned;
7964 compose->attach_label = attach_lab2;
7966 compose->notebook = notebook;
7967 compose->edit_vbox = edit_vbox;
7968 compose->ruler_hbox = ruler_hbox;
7969 compose->ruler = ruler;
7970 compose->scrolledwin = scrolledwin;
7971 compose->text = text;
7973 compose->focused_editable = NULL;
7975 compose->popupmenu = popupmenu;
7977 compose->tmpl_menu = tmpl_menu;
7979 compose->mode = mode;
7980 compose->rmode = mode;
7982 compose->targetinfo = NULL;
7983 compose->replyinfo = NULL;
7984 compose->fwdinfo = NULL;
7986 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7987 g_str_equal, (GDestroyNotify) g_free, NULL);
7989 compose->replyto = NULL;
7991 compose->bcc = NULL;
7992 compose->followup_to = NULL;
7994 compose->ml_post = NULL;
7996 compose->inreplyto = NULL;
7997 compose->references = NULL;
7998 compose->msgid = NULL;
7999 compose->boundary = NULL;
8001 compose->autowrap = prefs_common.autowrap;
8002 compose->autoindent = prefs_common.auto_indent;
8003 compose->use_signing = FALSE;
8004 compose->use_encryption = FALSE;
8005 compose->privacy_system = NULL;
8006 compose->encdata = NULL;
8008 compose->modified = FALSE;
8010 compose->return_receipt = FALSE;
8012 compose->to_list = NULL;
8013 compose->newsgroup_list = NULL;
8015 compose->undostruct = undostruct;
8017 compose->sig_str = NULL;
8019 compose->exteditor_file = NULL;
8020 compose->exteditor_pid = -1;
8021 compose->exteditor_tag = -1;
8022 compose->exteditor_socket = NULL;
8023 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
8025 compose->folder_update_callback_id =
8026 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
8027 compose_update_folder_hook,
8028 (gpointer) compose);
8031 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
8032 if (mode != COMPOSE_REDIRECT) {
8033 if (prefs_common.enable_aspell && prefs_common.dictionary &&
8034 strcmp(prefs_common.dictionary, "")) {
8035 gtkaspell = gtkaspell_new(prefs_common.dictionary,
8036 prefs_common.alt_dictionary,
8037 conv_get_locale_charset_str(),
8038 prefs_common.misspelled_col,
8039 prefs_common.check_while_typing,
8040 prefs_common.recheck_when_changing_dict,
8041 prefs_common.use_alternate,
8042 prefs_common.use_both_dicts,
8043 GTK_TEXT_VIEW(text),
8044 GTK_WINDOW(compose->window),
8045 compose_dict_changed,
8046 compose_spell_menu_changed,
8049 alertpanel_error(_("Spell checker could not "
8051 gtkaspell_checkers_strerror());
8052 gtkaspell_checkers_reset_error();
8054 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
8058 compose->gtkaspell = gtkaspell;
8059 compose_spell_menu_changed(compose);
8060 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
8063 compose_select_account(compose, account, TRUE);
8065 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
8066 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
8068 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
8069 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8071 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
8072 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8074 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
8075 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8077 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8078 if (account->protocol != A_NNTP)
8079 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8080 prefs_common_translated_header_name("To:"));
8082 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8083 prefs_common_translated_header_name("Newsgroups:"));
8085 #ifndef USE_NEW_ADDRBOOK
8086 addressbook_set_target_compose(compose);
8088 if (mode != COMPOSE_REDIRECT)
8089 compose_set_template_menu(compose);
8091 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8094 compose_list = g_list_append(compose_list, compose);
8096 if (!prefs_common.show_ruler)
8097 gtk_widget_hide(ruler_hbox);
8099 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8102 compose->priority = PRIORITY_NORMAL;
8103 compose_update_priority_menu_item(compose);
8105 compose_set_out_encoding(compose);
8108 compose_update_actions_menu(compose);
8110 /* Privacy Systems menu */
8111 compose_update_privacy_systems_menu(compose);
8113 activate_privacy_system(compose, account, TRUE);
8114 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8116 gtk_widget_realize(window);
8118 gtk_widget_show(window);
8124 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8129 GtkWidget *optmenubox;
8130 GtkWidget *fromlabel;
8133 GtkWidget *from_name = NULL;
8135 gint num = 0, def_menu = 0;
8137 accounts = account_get_list();
8138 cm_return_val_if_fail(accounts != NULL, NULL);
8140 optmenubox = gtk_event_box_new();
8141 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8142 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8144 hbox = gtk_hbox_new(FALSE, 4);
8145 from_name = gtk_entry_new();
8147 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8148 G_CALLBACK(compose_grab_focus_cb), compose);
8149 g_signal_connect_after(G_OBJECT(from_name), "activate",
8150 G_CALLBACK(from_name_activate_cb), optmenu);
8152 for (; accounts != NULL; accounts = accounts->next, num++) {
8153 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8154 gchar *name, *from = NULL;
8156 if (ac == compose->account) def_menu = num;
8158 name = g_markup_printf_escaped("<i>%s</i>",
8161 if (ac == compose->account) {
8162 if (ac->name && *ac->name) {
8164 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8165 from = g_strdup_printf("%s <%s>",
8167 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8169 from = g_strdup_printf("%s",
8171 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8174 COMBOBOX_ADD(menu, name, ac->account_id);
8179 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8181 g_signal_connect(G_OBJECT(optmenu), "changed",
8182 G_CALLBACK(account_activated),
8184 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8185 G_CALLBACK(compose_entry_popup_extend),
8188 fromlabel = gtk_label_new_with_mnemonic(_("_From:"));
8189 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel), from_name);
8191 gtk_box_pack_start(GTK_BOX(hbox), fromlabel, FALSE, FALSE, 4);
8192 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8193 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8195 /* Putting only the GtkEntry into focus chain of parent hbox causes
8196 * the account selector combobox next to it to be unreachable when
8197 * navigating widgets in GtkTable with up/down arrow keys.
8198 * Note: gtk_widget_set_can_focus() was not enough. */
8200 l = g_list_prepend(l, from_name);
8201 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8204 CLAWS_SET_TIP(optmenubox,
8205 _("Account to use for this email"));
8206 CLAWS_SET_TIP(from_name,
8207 _("Sender address to be used"));
8209 compose->account_combo = optmenu;
8210 compose->from_name = from_name;
8215 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8217 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8218 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8219 Compose *compose = (Compose *) data;
8221 compose->priority = value;
8225 static void compose_reply_change_mode(Compose *compose,
8228 gboolean was_modified = compose->modified;
8230 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8232 cm_return_if_fail(compose->replyinfo != NULL);
8234 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8236 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8238 if (action == COMPOSE_REPLY_TO_ALL)
8240 if (action == COMPOSE_REPLY_TO_SENDER)
8242 if (action == COMPOSE_REPLY_TO_LIST)
8245 compose_remove_header_entries(compose);
8246 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8247 if (compose->account->set_autocc && compose->account->auto_cc)
8248 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8250 if (compose->account->set_autobcc && compose->account->auto_bcc)
8251 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8253 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8254 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8255 compose_show_first_last_header(compose, TRUE);
8256 compose->modified = was_modified;
8257 compose_set_title(compose);
8260 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8262 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8263 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8264 Compose *compose = (Compose *) data;
8267 compose_reply_change_mode(compose, value);
8270 static void compose_update_priority_menu_item(Compose * compose)
8272 GtkWidget *menuitem = NULL;
8273 switch (compose->priority) {
8274 case PRIORITY_HIGHEST:
8275 menuitem = gtk_ui_manager_get_widget
8276 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8279 menuitem = gtk_ui_manager_get_widget
8280 (compose->ui_manager, "/Menu/Options/Priority/High");
8282 case PRIORITY_NORMAL:
8283 menuitem = gtk_ui_manager_get_widget
8284 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8287 menuitem = gtk_ui_manager_get_widget
8288 (compose->ui_manager, "/Menu/Options/Priority/Low");
8290 case PRIORITY_LOWEST:
8291 menuitem = gtk_ui_manager_get_widget
8292 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8295 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8298 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8300 Compose *compose = (Compose *) data;
8302 gboolean can_sign = FALSE, can_encrypt = FALSE;
8304 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8306 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8309 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8310 g_free(compose->privacy_system);
8311 compose->privacy_system = NULL;
8312 g_free(compose->encdata);
8313 compose->encdata = NULL;
8314 if (systemid != NULL) {
8315 compose->privacy_system = g_strdup(systemid);
8317 can_sign = privacy_system_can_sign(systemid);
8318 can_encrypt = privacy_system_can_encrypt(systemid);
8321 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8323 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8327 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8329 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8330 GtkWidget *menuitem = NULL;
8331 GList *children, *amenu;
8332 gboolean can_sign = FALSE, can_encrypt = FALSE;
8333 gboolean found = FALSE;
8335 if (compose->privacy_system != NULL) {
8337 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8338 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8339 cm_return_if_fail(menuitem != NULL);
8341 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8344 while (amenu != NULL) {
8345 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8346 if (systemid != NULL) {
8347 if (strcmp(systemid, compose->privacy_system) == 0 &&
8348 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8349 menuitem = GTK_WIDGET(amenu->data);
8351 can_sign = privacy_system_can_sign(systemid);
8352 can_encrypt = privacy_system_can_encrypt(systemid);
8356 } else if (strlen(compose->privacy_system) == 0 &&
8357 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8358 menuitem = GTK_WIDGET(amenu->data);
8361 can_encrypt = FALSE;
8366 amenu = amenu->next;
8368 g_list_free(children);
8369 if (menuitem != NULL)
8370 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8372 if (warn && !found && strlen(compose->privacy_system)) {
8373 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8374 "will not be able to sign or encrypt this message."),
8375 compose->privacy_system);
8379 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8380 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8383 static void compose_set_out_encoding(Compose *compose)
8385 CharSet out_encoding;
8386 const gchar *branch = NULL;
8387 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8389 switch(out_encoding) {
8390 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8391 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8392 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8393 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8394 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8395 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8396 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8397 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8398 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8399 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8400 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8401 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8402 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8403 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8404 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8405 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8406 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8407 case C_MACCYR: branch = "Menu/Options/Encoding/Cyrillic/" CS_MACCYR; break;
8408 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8409 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8410 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8411 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8412 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8413 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8414 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8415 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8416 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8417 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8418 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8419 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8420 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8421 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8422 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8423 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8425 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8428 static void compose_set_template_menu(Compose *compose)
8430 GSList *tmpl_list, *cur;
8434 tmpl_list = template_get_config();
8436 menu = gtk_menu_new();
8438 gtk_menu_set_accel_group (GTK_MENU (menu),
8439 gtk_ui_manager_get_accel_group(compose->ui_manager));
8440 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8441 Template *tmpl = (Template *)cur->data;
8442 gchar *accel_path = NULL;
8443 item = gtk_menu_item_new_with_label(tmpl->name);
8444 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8445 g_signal_connect(G_OBJECT(item), "activate",
8446 G_CALLBACK(compose_template_activate_cb),
8448 g_object_set_data(G_OBJECT(item), "template", tmpl);
8449 gtk_widget_show(item);
8450 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8451 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8455 gtk_widget_show(menu);
8456 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8459 void compose_update_actions_menu(Compose *compose)
8461 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8464 static void compose_update_privacy_systems_menu(Compose *compose)
8466 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8467 GSList *systems, *cur;
8469 GtkWidget *system_none;
8471 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8472 GtkWidget *privacy_menu = gtk_menu_new();
8474 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8475 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8477 g_signal_connect(G_OBJECT(system_none), "activate",
8478 G_CALLBACK(compose_set_privacy_system_cb), compose);
8480 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8481 gtk_widget_show(system_none);
8483 systems = privacy_get_system_ids();
8484 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8485 gchar *systemid = cur->data;
8487 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8488 widget = gtk_radio_menu_item_new_with_label(group,
8489 privacy_system_get_name(systemid));
8490 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8491 g_strdup(systemid), g_free);
8492 g_signal_connect(G_OBJECT(widget), "activate",
8493 G_CALLBACK(compose_set_privacy_system_cb), compose);
8495 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8496 gtk_widget_show(widget);
8499 g_slist_free(systems);
8500 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8501 gtk_widget_show_all(privacy_menu);
8502 gtk_widget_show_all(privacy_menuitem);
8505 void compose_reflect_prefs_all(void)
8510 for (cur = compose_list; cur != NULL; cur = cur->next) {
8511 compose = (Compose *)cur->data;
8512 compose_set_template_menu(compose);
8516 void compose_reflect_prefs_pixmap_theme(void)
8521 for (cur = compose_list; cur != NULL; cur = cur->next) {
8522 compose = (Compose *)cur->data;
8523 toolbar_update(TOOLBAR_COMPOSE, compose);
8527 static const gchar *compose_quote_char_from_context(Compose *compose)
8529 const gchar *qmark = NULL;
8531 cm_return_val_if_fail(compose != NULL, NULL);
8533 switch (compose->mode) {
8534 /* use forward-specific quote char */
8535 case COMPOSE_FORWARD:
8536 case COMPOSE_FORWARD_AS_ATTACH:
8537 case COMPOSE_FORWARD_INLINE:
8538 if (compose->folder && compose->folder->prefs &&
8539 compose->folder->prefs->forward_with_format)
8540 qmark = compose->folder->prefs->forward_quotemark;
8541 else if (compose->account->forward_with_format)
8542 qmark = compose->account->forward_quotemark;
8544 qmark = prefs_common.fw_quotemark;
8547 /* use reply-specific quote char in all other modes */
8549 if (compose->folder && compose->folder->prefs &&
8550 compose->folder->prefs->reply_with_format)
8551 qmark = compose->folder->prefs->reply_quotemark;
8552 else if (compose->account->reply_with_format)
8553 qmark = compose->account->reply_quotemark;
8555 qmark = prefs_common.quotemark;
8559 if (qmark == NULL || *qmark == '\0')
8565 static void compose_template_apply(Compose *compose, Template *tmpl,
8569 GtkTextBuffer *buffer;
8573 gchar *parsed_str = NULL;
8574 gint cursor_pos = 0;
8575 const gchar *err_msg = _("The body of the template has an error at line %d.");
8578 /* process the body */
8580 text = GTK_TEXT_VIEW(compose->text);
8581 buffer = gtk_text_view_get_buffer(text);
8584 qmark = compose_quote_char_from_context(compose);
8586 if (compose->replyinfo != NULL) {
8589 gtk_text_buffer_set_text(buffer, "", -1);
8590 mark = gtk_text_buffer_get_insert(buffer);
8591 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8593 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8594 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8596 } else if (compose->fwdinfo != NULL) {
8599 gtk_text_buffer_set_text(buffer, "", -1);
8600 mark = gtk_text_buffer_get_insert(buffer);
8601 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8603 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8604 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8607 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8609 GtkTextIter start, end;
8612 gtk_text_buffer_get_start_iter(buffer, &start);
8613 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8614 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8616 /* clear the buffer now */
8618 gtk_text_buffer_set_text(buffer, "", -1);
8620 parsed_str = compose_quote_fmt(compose, dummyinfo,
8621 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8622 procmsg_msginfo_free( dummyinfo );
8628 gtk_text_buffer_set_text(buffer, "", -1);
8629 mark = gtk_text_buffer_get_insert(buffer);
8630 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8633 if (replace && parsed_str && compose->account->auto_sig)
8634 compose_insert_sig(compose, FALSE);
8636 if (replace && parsed_str) {
8637 gtk_text_buffer_get_start_iter(buffer, &iter);
8638 gtk_text_buffer_place_cursor(buffer, &iter);
8642 cursor_pos = quote_fmt_get_cursor_pos();
8643 compose->set_cursor_pos = cursor_pos;
8644 if (cursor_pos == -1)
8646 gtk_text_buffer_get_start_iter(buffer, &iter);
8647 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8648 gtk_text_buffer_place_cursor(buffer, &iter);
8651 /* process the other fields */
8653 compose_template_apply_fields(compose, tmpl);
8654 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8655 quote_fmt_reset_vartable();
8656 compose_changed_cb(NULL, compose);
8659 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8660 gtkaspell_highlight_all(compose->gtkaspell);
8664 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8666 MsgInfo* dummyinfo = NULL;
8667 MsgInfo *msginfo = NULL;
8670 if (compose->replyinfo != NULL)
8671 msginfo = compose->replyinfo;
8672 else if (compose->fwdinfo != NULL)
8673 msginfo = compose->fwdinfo;
8675 dummyinfo = compose_msginfo_new_from_compose(compose);
8676 msginfo = dummyinfo;
8679 if (tmpl->from && *tmpl->from != '\0') {
8681 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8682 compose->gtkaspell);
8684 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8686 quote_fmt_scan_string(tmpl->from);
8689 buf = quote_fmt_get_buffer();
8691 alertpanel_error(_("Template From format error."));
8693 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8697 if (tmpl->to && *tmpl->to != '\0') {
8699 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8700 compose->gtkaspell);
8702 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8704 quote_fmt_scan_string(tmpl->to);
8707 buf = quote_fmt_get_buffer();
8709 alertpanel_error(_("Template To format error."));
8711 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8715 if (tmpl->cc && *tmpl->cc != '\0') {
8717 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8718 compose->gtkaspell);
8720 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8722 quote_fmt_scan_string(tmpl->cc);
8725 buf = quote_fmt_get_buffer();
8727 alertpanel_error(_("Template Cc format error."));
8729 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8733 if (tmpl->bcc && *tmpl->bcc != '\0') {
8735 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8736 compose->gtkaspell);
8738 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8740 quote_fmt_scan_string(tmpl->bcc);
8743 buf = quote_fmt_get_buffer();
8745 alertpanel_error(_("Template Bcc format error."));
8747 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8751 if (tmpl->replyto && *tmpl->replyto != '\0') {
8753 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8754 compose->gtkaspell);
8756 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8758 quote_fmt_scan_string(tmpl->replyto);
8761 buf = quote_fmt_get_buffer();
8763 alertpanel_error(_("Template Reply-To format error."));
8765 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8769 /* process the subject */
8770 if (tmpl->subject && *tmpl->subject != '\0') {
8772 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8773 compose->gtkaspell);
8775 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8777 quote_fmt_scan_string(tmpl->subject);
8780 buf = quote_fmt_get_buffer();
8782 alertpanel_error(_("Template subject format error."));
8784 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8788 procmsg_msginfo_free( dummyinfo );
8791 static void compose_destroy(Compose *compose)
8793 GtkAllocation allocation;
8794 GtkTextBuffer *buffer;
8795 GtkClipboard *clipboard;
8797 compose_list = g_list_remove(compose_list, compose);
8799 if (compose->updating) {
8800 debug_print("danger, not destroying anything now\n");
8801 compose->deferred_destroy = TRUE;
8805 /* NOTE: address_completion_end() does nothing with the window
8806 * however this may change. */
8807 address_completion_end(compose->window);
8809 slist_free_strings_full(compose->to_list);
8810 slist_free_strings_full(compose->newsgroup_list);
8811 slist_free_strings_full(compose->header_list);
8813 slist_free_strings_full(extra_headers);
8814 extra_headers = NULL;
8816 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8818 g_hash_table_destroy(compose->email_hashtable);
8820 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8821 compose->folder_update_callback_id);
8823 procmsg_msginfo_free(compose->targetinfo);
8824 procmsg_msginfo_free(compose->replyinfo);
8825 procmsg_msginfo_free(compose->fwdinfo);
8827 g_free(compose->replyto);
8828 g_free(compose->cc);
8829 g_free(compose->bcc);
8830 g_free(compose->newsgroups);
8831 g_free(compose->followup_to);
8833 g_free(compose->ml_post);
8835 g_free(compose->inreplyto);
8836 g_free(compose->references);
8837 g_free(compose->msgid);
8838 g_free(compose->boundary);
8840 g_free(compose->redirect_filename);
8841 if (compose->undostruct)
8842 undo_destroy(compose->undostruct);
8844 g_free(compose->sig_str);
8846 g_free(compose->exteditor_file);
8848 g_free(compose->orig_charset);
8850 g_free(compose->privacy_system);
8851 g_free(compose->encdata);
8853 #ifndef USE_NEW_ADDRBOOK
8854 if (addressbook_get_target_compose() == compose)
8855 addressbook_set_target_compose(NULL);
8858 if (compose->gtkaspell) {
8859 gtkaspell_delete(compose->gtkaspell);
8860 compose->gtkaspell = NULL;
8864 if (!compose->batch) {
8865 gtk_widget_get_allocation(compose->window, &allocation);
8866 prefs_common.compose_width = allocation.width;
8867 prefs_common.compose_height = allocation.height;
8870 if (!gtk_widget_get_parent(compose->paned))
8871 gtk_widget_destroy(compose->paned);
8872 gtk_widget_destroy(compose->popupmenu);
8874 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8875 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8876 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8878 gtk_widget_destroy(compose->window);
8879 toolbar_destroy(compose->toolbar);
8880 g_free(compose->toolbar);
8881 cm_mutex_free(compose->mutex);
8885 static void compose_attach_info_free(AttachInfo *ainfo)
8887 g_free(ainfo->file);
8888 g_free(ainfo->content_type);
8889 g_free(ainfo->name);
8890 g_free(ainfo->charset);
8894 static void compose_attach_update_label(Compose *compose)
8899 GtkTreeModel *model;
8904 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8905 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8906 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8910 while(gtk_tree_model_iter_next(model, &iter))
8913 text = g_strdup_printf("(%d)", i);
8914 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8918 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8920 Compose *compose = (Compose *)data;
8921 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8922 GtkTreeSelection *selection;
8924 GtkTreeModel *model;
8926 selection = gtk_tree_view_get_selection(tree_view);
8927 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8932 for (cur = sel; cur != NULL; cur = cur->next) {
8933 GtkTreePath *path = cur->data;
8934 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8937 gtk_tree_path_free(path);
8940 for (cur = sel; cur != NULL; cur = cur->next) {
8941 GtkTreeRowReference *ref = cur->data;
8942 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8945 if (gtk_tree_model_get_iter(model, &iter, path))
8946 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8948 gtk_tree_path_free(path);
8949 gtk_tree_row_reference_free(ref);
8953 compose_attach_update_label(compose);
8956 static struct _AttachProperty
8959 GtkWidget *mimetype_entry;
8960 GtkWidget *encoding_optmenu;
8961 GtkWidget *path_entry;
8962 GtkWidget *filename_entry;
8964 GtkWidget *cancel_btn;
8967 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8969 gtk_tree_path_free((GtkTreePath *)ptr);
8972 static void compose_attach_property(GtkAction *action, gpointer data)
8974 Compose *compose = (Compose *)data;
8975 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8977 GtkComboBox *optmenu;
8978 GtkTreeSelection *selection;
8980 GtkTreeModel *model;
8983 static gboolean cancelled;
8985 /* only if one selected */
8986 selection = gtk_tree_view_get_selection(tree_view);
8987 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8990 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8994 path = (GtkTreePath *) sel->data;
8995 gtk_tree_model_get_iter(model, &iter, path);
8996 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8999 g_list_foreach(sel, gtk_tree_path_free_, NULL);
9005 if (!attach_prop.window)
9006 compose_attach_property_create(&cancelled);
9007 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
9008 gtk_widget_grab_focus(attach_prop.ok_btn);
9009 gtk_widget_show(attach_prop.window);
9010 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
9011 GTK_WINDOW(compose->window));
9013 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
9014 if (ainfo->encoding == ENC_UNKNOWN)
9015 combobox_select_by_data(optmenu, ENC_BASE64);
9017 combobox_select_by_data(optmenu, ainfo->encoding);
9019 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
9020 ainfo->content_type ? ainfo->content_type : "");
9021 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
9022 ainfo->file ? ainfo->file : "");
9023 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
9024 ainfo->name ? ainfo->name : "");
9027 const gchar *entry_text;
9029 gchar *cnttype = NULL;
9036 gtk_widget_hide(attach_prop.window);
9037 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
9042 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
9043 if (*entry_text != '\0') {
9046 text = g_strstrip(g_strdup(entry_text));
9047 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
9048 cnttype = g_strdup(text);
9051 alertpanel_error(_("Invalid MIME type."));
9057 ainfo->encoding = combobox_get_active_data(optmenu);
9059 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
9060 if (*entry_text != '\0') {
9061 if (is_file_exist(entry_text) &&
9062 (size = get_file_size(entry_text)) > 0)
9063 file = g_strdup(entry_text);
9066 (_("File doesn't exist or is empty."));
9072 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
9073 if (*entry_text != '\0') {
9074 g_free(ainfo->name);
9075 ainfo->name = g_strdup(entry_text);
9079 g_free(ainfo->content_type);
9080 ainfo->content_type = cnttype;
9083 g_free(ainfo->file);
9087 ainfo->size = (goffset)size;
9089 /* update tree store */
9090 text = to_human_readable(ainfo->size);
9091 gtk_tree_model_get_iter(model, &iter, path);
9092 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9093 COL_MIMETYPE, ainfo->content_type,
9095 COL_NAME, ainfo->name,
9096 COL_CHARSET, ainfo->charset,
9102 gtk_tree_path_free(path);
9105 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9107 label = gtk_label_new(str); \
9108 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9109 GTK_FILL, 0, 0, 0); \
9110 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9112 entry = gtk_entry_new(); \
9113 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9114 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9117 static void compose_attach_property_create(gboolean *cancelled)
9123 GtkWidget *mimetype_entry;
9126 GtkListStore *optmenu_menu;
9127 GtkWidget *path_entry;
9128 GtkWidget *filename_entry;
9131 GtkWidget *cancel_btn;
9132 GList *mime_type_list, *strlist;
9135 debug_print("Creating attach_property window...\n");
9137 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9138 gtk_widget_set_size_request(window, 480, -1);
9139 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9140 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9141 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9142 g_signal_connect(G_OBJECT(window), "delete_event",
9143 G_CALLBACK(attach_property_delete_event),
9145 g_signal_connect(G_OBJECT(window), "key_press_event",
9146 G_CALLBACK(attach_property_key_pressed),
9149 vbox = gtk_vbox_new(FALSE, 8);
9150 gtk_container_add(GTK_CONTAINER(window), vbox);
9152 table = gtk_table_new(4, 2, FALSE);
9153 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9154 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9155 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9157 label = gtk_label_new(_("MIME type"));
9158 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9160 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9161 #if !GTK_CHECK_VERSION(2, 24, 0)
9162 mimetype_entry = gtk_combo_box_entry_new_text();
9164 mimetype_entry = gtk_combo_box_text_new_with_entry();
9166 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9167 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9169 /* stuff with list */
9170 mime_type_list = procmime_get_mime_type_list();
9172 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9173 MimeType *type = (MimeType *) mime_type_list->data;
9176 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9178 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9181 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9182 (GCompareFunc)strcmp2);
9185 for (mime_type_list = strlist; mime_type_list != NULL;
9186 mime_type_list = mime_type_list->next) {
9187 #if !GTK_CHECK_VERSION(2, 24, 0)
9188 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9190 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9192 g_free(mime_type_list->data);
9194 g_list_free(strlist);
9195 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9196 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9198 label = gtk_label_new(_("Encoding"));
9199 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9201 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9203 hbox = gtk_hbox_new(FALSE, 0);
9204 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9205 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9207 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9208 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9210 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9211 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9212 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9213 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9214 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9216 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9218 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9219 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9221 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9222 &ok_btn, GTK_STOCK_OK,
9224 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9225 gtk_widget_grab_default(ok_btn);
9227 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9228 G_CALLBACK(attach_property_ok),
9230 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9231 G_CALLBACK(attach_property_cancel),
9234 gtk_widget_show_all(vbox);
9236 attach_prop.window = window;
9237 attach_prop.mimetype_entry = mimetype_entry;
9238 attach_prop.encoding_optmenu = optmenu;
9239 attach_prop.path_entry = path_entry;
9240 attach_prop.filename_entry = filename_entry;
9241 attach_prop.ok_btn = ok_btn;
9242 attach_prop.cancel_btn = cancel_btn;
9245 #undef SET_LABEL_AND_ENTRY
9247 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9253 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9259 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9260 gboolean *cancelled)
9268 static gboolean attach_property_key_pressed(GtkWidget *widget,
9270 gboolean *cancelled)
9272 if (event && event->keyval == GDK_KEY_Escape) {
9276 if (event && event->keyval == GDK_KEY_Return) {
9284 static void compose_exec_ext_editor(Compose *compose)
9289 GdkNativeWindow socket_wid = 0;
9293 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9294 G_DIR_SEPARATOR, compose);
9296 if (compose_get_ext_editor_uses_socket()) {
9297 /* Only allow one socket */
9298 if (compose->exteditor_socket != NULL) {
9299 if (gtk_widget_is_focus(compose->exteditor_socket)) {
9300 /* Move the focus off of the socket */
9301 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9306 /* Create the receiving GtkSocket */
9307 socket = gtk_socket_new ();
9308 g_signal_connect (GTK_OBJECT(socket), "plug-removed",
9309 G_CALLBACK(compose_ext_editor_plug_removed_cb),
9311 gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
9312 gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
9313 /* Realize the socket so that we can use its ID */
9314 gtk_widget_realize(socket);
9315 socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
9316 compose->exteditor_socket = socket;
9319 if (pipe(pipe_fds) < 0) {
9325 if ((pid = fork()) < 0) {
9332 /* close the write side of the pipe */
9335 compose->exteditor_file = g_strdup(tmp);
9336 compose->exteditor_pid = pid;
9338 compose_set_ext_editor_sensitive(compose, FALSE);
9341 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9343 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9345 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9349 } else { /* process-monitoring process */
9355 /* close the read side of the pipe */
9358 if (compose_write_body_to_file(compose, tmp) < 0) {
9359 fd_write_all(pipe_fds[1], "2\n", 2);
9363 pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
9365 fd_write_all(pipe_fds[1], "1\n", 2);
9369 /* wait until editor is terminated */
9370 waitpid(pid_ed, NULL, 0);
9372 fd_write_all(pipe_fds[1], "0\n", 2);
9379 #endif /* G_OS_UNIX */
9383 static gboolean compose_get_ext_editor_cmd_valid()
9385 gboolean has_s = FALSE;
9386 gboolean has_w = FALSE;
9387 const gchar *p = prefs_common_get_ext_editor_cmd();
9390 while ((p = strchr(p, '%'))) {
9396 } else if (*p == 'w') {
9407 static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
9414 cm_return_val_if_fail(file != NULL, -1);
9416 if ((pid = fork()) < 0) {
9421 if (pid != 0) return pid;
9423 /* grandchild process */
9425 if (setpgid(0, getppid()))
9428 if (compose_get_ext_editor_cmd_valid()) {
9429 if (compose_get_ext_editor_uses_socket()) {
9430 p = g_strdup(prefs_common_get_ext_editor_cmd());
9431 s = strstr(p, "%w");
9433 if (strstr(p, "%s") < s)
9434 g_snprintf(buf, sizeof(buf), p, file, socket_wid);
9436 g_snprintf(buf, sizeof(buf), p, socket_wid, file);
9439 g_snprintf(buf, sizeof(buf),
9440 prefs_common_get_ext_editor_cmd(), file);
9443 if (prefs_common_get_ext_editor_cmd())
9444 g_warning("External editor command-line is invalid: '%s'",
9445 prefs_common_get_ext_editor_cmd());
9446 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9449 cmdline = strsplit_with_quote(buf, " ", 1024);
9450 execvp(cmdline[0], cmdline);
9453 g_strfreev(cmdline);
9458 static gboolean compose_ext_editor_kill(Compose *compose)
9460 pid_t pgid = compose->exteditor_pid * -1;
9463 ret = kill(pgid, 0);
9465 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9469 msg = g_strdup_printf
9470 (_("The external editor is still working.\n"
9471 "Force terminating the process?\n"
9472 "process group id: %d"), -pgid);
9473 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9474 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9478 if (val == G_ALERTALTERNATE) {
9479 g_source_remove(compose->exteditor_tag);
9480 g_io_channel_shutdown(compose->exteditor_ch,
9482 g_io_channel_unref(compose->exteditor_ch);
9484 if (kill(pgid, SIGTERM) < 0) perror("kill");
9485 waitpid(compose->exteditor_pid, NULL, 0);
9487 g_warning("Terminated process group id: %d. "
9488 "Temporary file: %s", -pgid, compose->exteditor_file);
9490 compose_set_ext_editor_sensitive(compose, TRUE);
9492 g_free(compose->exteditor_file);
9493 compose->exteditor_file = NULL;
9494 compose->exteditor_pid = -1;
9495 compose->exteditor_ch = NULL;
9496 compose->exteditor_tag = -1;
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);
9532 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9534 if (claws_unlink(compose->exteditor_file) < 0)
9535 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9537 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
9538 gtk_text_buffer_get_start_iter(buffer, &start);
9539 gtk_text_buffer_get_end_iter(buffer, &end);
9540 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
9541 if (chars && strlen(chars) > 0)
9542 compose->modified = TRUE;
9544 } else if (buf[0] == '1') { /* failed */
9545 g_warning("Couldn't exec external editor");
9546 if (claws_unlink(compose->exteditor_file) < 0)
9547 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9548 } else if (buf[0] == '2') {
9549 g_warning("Couldn't write to file");
9550 } else if (buf[0] == '3') {
9551 g_warning("Pipe read failed");
9554 compose_set_ext_editor_sensitive(compose, TRUE);
9556 g_free(compose->exteditor_file);
9557 compose->exteditor_file = NULL;
9558 compose->exteditor_pid = -1;
9559 compose->exteditor_ch = NULL;
9560 compose->exteditor_tag = -1;
9561 if (compose->exteditor_socket) {
9562 gtk_widget_destroy(compose->exteditor_socket);
9563 compose->exteditor_socket = NULL;
9570 static void compose_set_ext_editor_sensitive(Compose *compose,
9573 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9574 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9577 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9579 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9582 if (compose_get_ext_editor_uses_socket()) {
9584 if (compose->exteditor_socket)
9585 gtk_widget_hide(compose->exteditor_socket);
9586 gtk_widget_show(compose->scrolledwin);
9587 if (prefs_common.show_ruler)
9588 gtk_widget_show(compose->ruler_hbox);
9589 /* Fix the focus, as it doesn't go anywhere when the
9590 * socket is hidden or destroyed */
9591 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9593 g_assert (compose->exteditor_socket != NULL);
9594 /* Fix the focus, as it doesn't go anywhere when the
9595 * edit box is hidden */
9596 if (gtk_widget_is_focus(compose->text))
9597 gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
9598 gtk_widget_hide(compose->scrolledwin);
9599 gtk_widget_hide(compose->ruler_hbox);
9600 gtk_widget_show(compose->exteditor_socket);
9603 gtk_widget_set_sensitive(compose->text, sensitive);
9605 if (compose->toolbar->send_btn)
9606 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9607 if (compose->toolbar->sendl_btn)
9608 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9609 if (compose->toolbar->draft_btn)
9610 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9611 if (compose->toolbar->insert_btn)
9612 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9613 if (compose->toolbar->sig_btn)
9614 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9615 if (compose->toolbar->exteditor_btn)
9616 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9617 if (compose->toolbar->linewrap_current_btn)
9618 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9619 if (compose->toolbar->linewrap_all_btn)
9620 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9623 static gboolean compose_get_ext_editor_uses_socket()
9625 return (prefs_common_get_ext_editor_cmd() &&
9626 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9629 static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
9631 compose->exteditor_socket = NULL;
9632 /* returning FALSE allows destruction of the socket */
9635 #endif /* G_OS_UNIX */
9638 * compose_undo_state_changed:
9640 * Change the sensivity of the menuentries undo and redo
9642 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9643 gint redo_state, gpointer data)
9645 Compose *compose = (Compose *)data;
9647 switch (undo_state) {
9648 case UNDO_STATE_TRUE:
9649 if (!undostruct->undo_state) {
9650 undostruct->undo_state = TRUE;
9651 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9654 case UNDO_STATE_FALSE:
9655 if (undostruct->undo_state) {
9656 undostruct->undo_state = FALSE;
9657 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9660 case UNDO_STATE_UNCHANGED:
9662 case UNDO_STATE_REFRESH:
9663 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9666 g_warning("Undo state not recognized");
9670 switch (redo_state) {
9671 case UNDO_STATE_TRUE:
9672 if (!undostruct->redo_state) {
9673 undostruct->redo_state = TRUE;
9674 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9677 case UNDO_STATE_FALSE:
9678 if (undostruct->redo_state) {
9679 undostruct->redo_state = FALSE;
9680 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9683 case UNDO_STATE_UNCHANGED:
9685 case UNDO_STATE_REFRESH:
9686 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9689 g_warning("Redo state not recognized");
9694 /* callback functions */
9696 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9697 GtkAllocation *allocation,
9700 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9703 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9704 * includes "non-client" (windows-izm) in calculation, so this calculation
9705 * may not be accurate.
9707 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9708 GtkAllocation *allocation,
9709 GtkSHRuler *shruler)
9711 if (prefs_common.show_ruler) {
9712 gint char_width = 0, char_height = 0;
9713 gint line_width_in_chars;
9715 gtkut_get_font_size(GTK_WIDGET(widget),
9716 &char_width, &char_height);
9717 line_width_in_chars =
9718 (allocation->width - allocation->x) / char_width;
9720 /* got the maximum */
9721 gtk_shruler_set_range(GTK_SHRULER(shruler),
9722 0.0, line_width_in_chars, 0);
9731 ComposePrefType type;
9732 gboolean entry_marked;
9735 static void account_activated(GtkComboBox *optmenu, gpointer data)
9737 Compose *compose = (Compose *)data;
9740 gchar *folderidentifier;
9741 gint account_id = 0;
9744 GSList *list, *saved_list = NULL;
9745 HeaderEntryState *state;
9746 GtkRcStyle *style = NULL;
9747 #if !GTK_CHECK_VERSION(3, 0, 0)
9748 static GdkColor yellow;
9749 static gboolean color_set = FALSE;
9751 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9754 /* Get ID of active account in the combo box */
9755 menu = gtk_combo_box_get_model(optmenu);
9756 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9757 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9759 ac = account_find_from_id(account_id);
9760 cm_return_if_fail(ac != NULL);
9762 if (ac != compose->account) {
9763 compose_select_account(compose, ac, FALSE);
9765 for (list = compose->header_list; list; list = list->next) {
9766 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9768 if (hentry->type == PREF_ACCOUNT || !list->next) {
9769 compose_destroy_headerentry(compose, hentry);
9773 state = g_malloc0(sizeof(HeaderEntryState));
9774 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9775 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9776 state->entry = gtk_editable_get_chars(
9777 GTK_EDITABLE(hentry->entry), 0, -1);
9778 state->type = hentry->type;
9780 #if !GTK_CHECK_VERSION(3, 0, 0)
9782 gdk_color_parse("#f5f6be", &yellow);
9783 color_set = gdk_colormap_alloc_color(
9784 gdk_colormap_get_system(),
9785 &yellow, FALSE, TRUE);
9789 style = gtk_widget_get_modifier_style(hentry->entry);
9790 state->entry_marked = gdk_color_equal(&yellow,
9791 &style->base[GTK_STATE_NORMAL]);
9793 saved_list = g_slist_append(saved_list, state);
9794 compose_destroy_headerentry(compose, hentry);
9797 compose->header_last = NULL;
9798 g_slist_free(compose->header_list);
9799 compose->header_list = NULL;
9800 compose->header_nextrow = 1;
9801 compose_create_header_entry(compose);
9803 if (ac->set_autocc && ac->auto_cc)
9804 compose_entry_append(compose, ac->auto_cc,
9805 COMPOSE_CC, PREF_ACCOUNT);
9807 if (ac->set_autobcc && ac->auto_bcc)
9808 compose_entry_append(compose, ac->auto_bcc,
9809 COMPOSE_BCC, PREF_ACCOUNT);
9811 if (ac->set_autoreplyto && ac->auto_replyto)
9812 compose_entry_append(compose, ac->auto_replyto,
9813 COMPOSE_REPLYTO, PREF_ACCOUNT);
9815 for (list = saved_list; list; list = list->next) {
9816 state = (HeaderEntryState *) list->data;
9818 compose_add_header_entry(compose, state->header,
9819 state->entry, state->type);
9820 if (state->entry_marked)
9821 compose_entry_mark_default_to(compose, state->entry);
9823 g_free(state->header);
9824 g_free(state->entry);
9827 g_slist_free(saved_list);
9829 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9830 (ac->protocol == A_NNTP) ?
9831 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9834 /* Set message save folder */
9835 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9836 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9838 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9839 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9841 compose_set_save_to(compose, NULL);
9842 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9843 folderidentifier = folder_item_get_identifier(account_get_special_folder
9844 (compose->account, F_OUTBOX));
9845 compose_set_save_to(compose, folderidentifier);
9846 g_free(folderidentifier);
9850 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9851 GtkTreeViewColumn *column, Compose *compose)
9853 compose_attach_property(NULL, compose);
9856 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9859 Compose *compose = (Compose *)data;
9860 GtkTreeSelection *attach_selection;
9861 gint attach_nr_selected;
9864 if (!event) return FALSE;
9866 if (event->button == 3) {
9867 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9868 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9870 /* If no rows, or just one row is selected, right-click should
9871 * open menu relevant to the row being right-clicked on. We
9872 * achieve that by selecting the clicked row first. If more
9873 * than one row is selected, we shouldn't modify the selection,
9874 * as user may want to remove selected rows (attachments). */
9875 if (attach_nr_selected < 2) {
9876 gtk_tree_selection_unselect_all(attach_selection);
9877 attach_nr_selected = 0;
9878 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
9879 event->x, event->y, &path, NULL, NULL, NULL);
9881 gtk_tree_selection_select_path(attach_selection, path);
9882 gtk_tree_path_free(path);
9883 attach_nr_selected++;
9887 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9888 /* Properties menu item makes no sense with more than one row
9889 * selected, the properties dialog can only edit one attachment. */
9890 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
9892 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9893 NULL, NULL, event->button, event->time);
9900 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9903 Compose *compose = (Compose *)data;
9905 if (!event) return FALSE;
9907 switch (event->keyval) {
9908 case GDK_KEY_Delete:
9909 compose_attach_remove_selected(NULL, compose);
9915 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9917 toolbar_comp_set_sensitive(compose, allow);
9918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9921 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9923 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9924 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9925 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9927 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9931 static void compose_send_cb(GtkAction *action, gpointer data)
9933 Compose *compose = (Compose *)data;
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"));
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"));
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 gboolean compose_can_autosave(Compose *compose)
10398 if (compose->privacy_system && compose->use_encryption)
10399 return prefs_common.autosave && prefs_common.autosave_encrypted;
10401 return prefs_common.autosave;
10404 static void compose_close_cb(GtkAction *action, gpointer data)
10406 Compose *compose = (Compose *)data;
10410 if (compose->exteditor_tag != -1) {
10411 if (!compose_ext_editor_kill(compose))
10416 if (compose->modified) {
10417 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10418 if (!g_mutex_trylock(compose->mutex)) {
10419 /* we don't want to lock the mutex once it's available,
10420 * because as the only other part of compose.c locking
10421 * it is compose_close - which means once unlocked,
10422 * the compose struct will be freed */
10423 debug_print("couldn't lock mutex, probably sending\n");
10427 val = alertpanel(_("Discard message"),
10428 _("This message has been modified. Discard it?"),
10429 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10431 val = alertpanel(_("Save changes"),
10432 _("This message has been modified. Save the latest changes?"),
10433 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
10436 g_mutex_unlock(compose->mutex);
10438 case G_ALERTDEFAULT:
10439 if (compose_can_autosave(compose) && !reedit)
10440 compose_remove_draft(compose);
10442 case G_ALERTALTERNATE:
10443 compose_draft(data, COMPOSE_QUIT_EDITING);
10450 compose_close(compose);
10453 static void compose_print_cb(GtkAction *action, gpointer data)
10455 Compose *compose = (Compose *) data;
10457 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10458 if (compose->targetinfo)
10459 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10462 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10464 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10465 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10466 Compose *compose = (Compose *) data;
10469 compose->out_encoding = (CharSet)value;
10472 static void compose_address_cb(GtkAction *action, gpointer data)
10474 Compose *compose = (Compose *)data;
10476 #ifndef USE_NEW_ADDRBOOK
10477 addressbook_open(compose);
10479 GError* error = NULL;
10480 addressbook_connect_signals(compose);
10481 addressbook_dbus_open(TRUE, &error);
10483 g_warning("%s", error->message);
10484 g_error_free(error);
10489 static void about_show_cb(GtkAction *action, gpointer data)
10494 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10496 Compose *compose = (Compose *)data;
10501 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10502 cm_return_if_fail(tmpl != NULL);
10504 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10506 val = alertpanel(_("Apply template"), msg,
10507 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10510 if (val == G_ALERTDEFAULT)
10511 compose_template_apply(compose, tmpl, TRUE);
10512 else if (val == G_ALERTALTERNATE)
10513 compose_template_apply(compose, tmpl, FALSE);
10516 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10518 Compose *compose = (Compose *)data;
10520 compose_exec_ext_editor(compose);
10523 static void compose_undo_cb(GtkAction *action, gpointer data)
10525 Compose *compose = (Compose *)data;
10526 gboolean prev_autowrap = compose->autowrap;
10528 compose->autowrap = FALSE;
10529 undo_undo(compose->undostruct);
10530 compose->autowrap = prev_autowrap;
10533 static void compose_redo_cb(GtkAction *action, gpointer data)
10535 Compose *compose = (Compose *)data;
10536 gboolean prev_autowrap = compose->autowrap;
10538 compose->autowrap = FALSE;
10539 undo_redo(compose->undostruct);
10540 compose->autowrap = prev_autowrap;
10543 static void entry_cut_clipboard(GtkWidget *entry)
10545 if (GTK_IS_EDITABLE(entry))
10546 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10547 else if (GTK_IS_TEXT_VIEW(entry))
10548 gtk_text_buffer_cut_clipboard(
10549 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10550 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10554 static void entry_copy_clipboard(GtkWidget *entry)
10556 if (GTK_IS_EDITABLE(entry))
10557 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10558 else if (GTK_IS_TEXT_VIEW(entry))
10559 gtk_text_buffer_copy_clipboard(
10560 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10561 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10564 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10565 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10567 if (GTK_IS_TEXT_VIEW(entry)) {
10568 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10569 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10570 GtkTextIter start_iter, end_iter;
10572 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10574 if (contents == NULL)
10577 /* we shouldn't delete the selection when middle-click-pasting, or we
10578 * can't mid-click-paste our own selection */
10579 if (clip != GDK_SELECTION_PRIMARY) {
10580 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10581 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10584 if (insert_place == NULL) {
10585 /* if insert_place isn't specified, insert at the cursor.
10586 * used for Ctrl-V pasting */
10587 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10588 start = gtk_text_iter_get_offset(&start_iter);
10589 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10591 /* if insert_place is specified, paste here.
10592 * used for mid-click-pasting */
10593 start = gtk_text_iter_get_offset(insert_place);
10594 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10595 if (prefs_common.primary_paste_unselects)
10596 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10600 /* paste unwrapped: mark the paste so it's not wrapped later */
10601 end = start + strlen(contents);
10602 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10603 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10604 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10605 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10606 /* rewrap paragraph now (after a mid-click-paste) */
10607 mark_start = gtk_text_buffer_get_insert(buffer);
10608 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10609 gtk_text_iter_backward_char(&start_iter);
10610 compose_beautify_paragraph(compose, &start_iter, TRUE);
10612 } else if (GTK_IS_EDITABLE(entry))
10613 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10615 compose->modified = TRUE;
10618 static void entry_allsel(GtkWidget *entry)
10620 if (GTK_IS_EDITABLE(entry))
10621 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10622 else if (GTK_IS_TEXT_VIEW(entry)) {
10623 GtkTextIter startiter, enditer;
10624 GtkTextBuffer *textbuf;
10626 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10627 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10628 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10630 gtk_text_buffer_move_mark_by_name(textbuf,
10631 "selection_bound", &startiter);
10632 gtk_text_buffer_move_mark_by_name(textbuf,
10633 "insert", &enditer);
10637 static void compose_cut_cb(GtkAction *action, gpointer data)
10639 Compose *compose = (Compose *)data;
10640 if (compose->focused_editable
10641 #ifndef GENERIC_UMPC
10642 && gtk_widget_has_focus(compose->focused_editable)
10645 entry_cut_clipboard(compose->focused_editable);
10648 static void compose_copy_cb(GtkAction *action, gpointer data)
10650 Compose *compose = (Compose *)data;
10651 if (compose->focused_editable
10652 #ifndef GENERIC_UMPC
10653 && gtk_widget_has_focus(compose->focused_editable)
10656 entry_copy_clipboard(compose->focused_editable);
10659 static void compose_paste_cb(GtkAction *action, gpointer data)
10661 Compose *compose = (Compose *)data;
10662 gint prev_autowrap;
10663 GtkTextBuffer *buffer;
10665 if (compose->focused_editable &&
10666 #ifndef GENERIC_UMPC
10667 gtk_widget_has_focus(compose->focused_editable)
10670 entry_paste_clipboard(compose, compose->focused_editable,
10671 prefs_common.linewrap_pastes,
10672 GDK_SELECTION_CLIPBOARD, NULL);
10677 #ifndef GENERIC_UMPC
10678 gtk_widget_has_focus(compose->text) &&
10680 compose->gtkaspell &&
10681 compose->gtkaspell->check_while_typing)
10682 gtkaspell_highlight_all(compose->gtkaspell);
10686 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10688 Compose *compose = (Compose *)data;
10689 gint wrap_quote = prefs_common.linewrap_quote;
10690 if (compose->focused_editable
10691 #ifndef GENERIC_UMPC
10692 && gtk_widget_has_focus(compose->focused_editable)
10695 /* let text_insert() (called directly or at a later time
10696 * after the gtk_editable_paste_clipboard) know that
10697 * text is to be inserted as a quotation. implemented
10698 * by using a simple refcount... */
10699 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10700 G_OBJECT(compose->focused_editable),
10701 "paste_as_quotation"));
10702 g_object_set_data(G_OBJECT(compose->focused_editable),
10703 "paste_as_quotation",
10704 GINT_TO_POINTER(paste_as_quotation + 1));
10705 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10706 entry_paste_clipboard(compose, compose->focused_editable,
10707 prefs_common.linewrap_pastes,
10708 GDK_SELECTION_CLIPBOARD, NULL);
10709 prefs_common.linewrap_quote = wrap_quote;
10713 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10715 Compose *compose = (Compose *)data;
10716 gint prev_autowrap;
10717 GtkTextBuffer *buffer;
10719 if (compose->focused_editable
10720 #ifndef GENERIC_UMPC
10721 && gtk_widget_has_focus(compose->focused_editable)
10724 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10725 GDK_SELECTION_CLIPBOARD, NULL);
10730 #ifndef GENERIC_UMPC
10731 gtk_widget_has_focus(compose->text) &&
10733 compose->gtkaspell &&
10734 compose->gtkaspell->check_while_typing)
10735 gtkaspell_highlight_all(compose->gtkaspell);
10739 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10741 Compose *compose = (Compose *)data;
10742 gint prev_autowrap;
10743 GtkTextBuffer *buffer;
10745 if (compose->focused_editable
10746 #ifndef GENERIC_UMPC
10747 && gtk_widget_has_focus(compose->focused_editable)
10750 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10751 GDK_SELECTION_CLIPBOARD, NULL);
10756 #ifndef GENERIC_UMPC
10757 gtk_widget_has_focus(compose->text) &&
10759 compose->gtkaspell &&
10760 compose->gtkaspell->check_while_typing)
10761 gtkaspell_highlight_all(compose->gtkaspell);
10765 static void compose_allsel_cb(GtkAction *action, gpointer data)
10767 Compose *compose = (Compose *)data;
10768 if (compose->focused_editable
10769 #ifndef GENERIC_UMPC
10770 && gtk_widget_has_focus(compose->focused_editable)
10773 entry_allsel(compose->focused_editable);
10776 static void textview_move_beginning_of_line (GtkTextView *text)
10778 GtkTextBuffer *buffer;
10782 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10784 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10785 mark = gtk_text_buffer_get_insert(buffer);
10786 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10787 gtk_text_iter_set_line_offset(&ins, 0);
10788 gtk_text_buffer_place_cursor(buffer, &ins);
10791 static void textview_move_forward_character (GtkTextView *text)
10793 GtkTextBuffer *buffer;
10797 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10799 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10800 mark = gtk_text_buffer_get_insert(buffer);
10801 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10802 if (gtk_text_iter_forward_cursor_position(&ins))
10803 gtk_text_buffer_place_cursor(buffer, &ins);
10806 static void textview_move_backward_character (GtkTextView *text)
10808 GtkTextBuffer *buffer;
10812 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10814 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10815 mark = gtk_text_buffer_get_insert(buffer);
10816 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10817 if (gtk_text_iter_backward_cursor_position(&ins))
10818 gtk_text_buffer_place_cursor(buffer, &ins);
10821 static void textview_move_forward_word (GtkTextView *text)
10823 GtkTextBuffer *buffer;
10828 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10830 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10831 mark = gtk_text_buffer_get_insert(buffer);
10832 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10833 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10834 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10835 gtk_text_iter_backward_word_start(&ins);
10836 gtk_text_buffer_place_cursor(buffer, &ins);
10840 static void textview_move_backward_word (GtkTextView *text)
10842 GtkTextBuffer *buffer;
10846 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10848 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10849 mark = gtk_text_buffer_get_insert(buffer);
10850 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10851 if (gtk_text_iter_backward_word_starts(&ins, 1))
10852 gtk_text_buffer_place_cursor(buffer, &ins);
10855 static void textview_move_end_of_line (GtkTextView *text)
10857 GtkTextBuffer *buffer;
10861 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10863 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10864 mark = gtk_text_buffer_get_insert(buffer);
10865 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10866 if (gtk_text_iter_forward_to_line_end(&ins))
10867 gtk_text_buffer_place_cursor(buffer, &ins);
10870 static void textview_move_next_line (GtkTextView *text)
10872 GtkTextBuffer *buffer;
10877 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10879 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10880 mark = gtk_text_buffer_get_insert(buffer);
10881 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10882 offset = gtk_text_iter_get_line_offset(&ins);
10883 if (gtk_text_iter_forward_line(&ins)) {
10884 gtk_text_iter_set_line_offset(&ins, offset);
10885 gtk_text_buffer_place_cursor(buffer, &ins);
10889 static void textview_move_previous_line (GtkTextView *text)
10891 GtkTextBuffer *buffer;
10896 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10898 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10899 mark = gtk_text_buffer_get_insert(buffer);
10900 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10901 offset = gtk_text_iter_get_line_offset(&ins);
10902 if (gtk_text_iter_backward_line(&ins)) {
10903 gtk_text_iter_set_line_offset(&ins, offset);
10904 gtk_text_buffer_place_cursor(buffer, &ins);
10908 static void textview_delete_forward_character (GtkTextView *text)
10910 GtkTextBuffer *buffer;
10912 GtkTextIter ins, end_iter;
10914 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10916 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10917 mark = gtk_text_buffer_get_insert(buffer);
10918 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10920 if (gtk_text_iter_forward_char(&end_iter)) {
10921 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10925 static void textview_delete_backward_character (GtkTextView *text)
10927 GtkTextBuffer *buffer;
10929 GtkTextIter ins, end_iter;
10931 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10933 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10934 mark = gtk_text_buffer_get_insert(buffer);
10935 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10937 if (gtk_text_iter_backward_char(&end_iter)) {
10938 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10942 static void textview_delete_forward_word (GtkTextView *text)
10944 GtkTextBuffer *buffer;
10946 GtkTextIter ins, end_iter;
10948 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10950 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10951 mark = gtk_text_buffer_get_insert(buffer);
10952 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10954 if (gtk_text_iter_forward_word_end(&end_iter)) {
10955 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10959 static void textview_delete_backward_word (GtkTextView *text)
10961 GtkTextBuffer *buffer;
10963 GtkTextIter ins, end_iter;
10965 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10967 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10968 mark = gtk_text_buffer_get_insert(buffer);
10969 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10971 if (gtk_text_iter_backward_word_start(&end_iter)) {
10972 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10976 static void textview_delete_line (GtkTextView *text)
10978 GtkTextBuffer *buffer;
10980 GtkTextIter ins, start_iter, end_iter;
10982 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10984 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10985 mark = gtk_text_buffer_get_insert(buffer);
10986 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10989 gtk_text_iter_set_line_offset(&start_iter, 0);
10992 if (gtk_text_iter_ends_line(&end_iter)){
10993 if (!gtk_text_iter_forward_char(&end_iter))
10994 gtk_text_iter_backward_char(&start_iter);
10997 gtk_text_iter_forward_to_line_end(&end_iter);
10998 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
11001 static void textview_delete_to_line_end (GtkTextView *text)
11003 GtkTextBuffer *buffer;
11005 GtkTextIter ins, end_iter;
11007 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
11009 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
11010 mark = gtk_text_buffer_get_insert(buffer);
11011 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
11013 if (gtk_text_iter_ends_line(&end_iter))
11014 gtk_text_iter_forward_char(&end_iter);
11016 gtk_text_iter_forward_to_line_end(&end_iter);
11017 gtk_text_buffer_delete(buffer, &ins, &end_iter);
11020 #define DO_ACTION(name, act) { \
11021 if(!strcmp(name, a_name)) { \
11025 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
11027 const gchar *a_name = gtk_action_get_name(action);
11028 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
11029 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
11030 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
11031 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
11032 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
11033 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
11034 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
11035 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
11036 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
11037 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
11038 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
11039 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
11040 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
11041 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
11045 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
11047 Compose *compose = (Compose *)data;
11048 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11049 ComposeCallAdvancedAction action = -1;
11051 action = compose_call_advanced_action_from_path(gaction);
11054 void (*do_action) (GtkTextView *text);
11055 } action_table[] = {
11056 {textview_move_beginning_of_line},
11057 {textview_move_forward_character},
11058 {textview_move_backward_character},
11059 {textview_move_forward_word},
11060 {textview_move_backward_word},
11061 {textview_move_end_of_line},
11062 {textview_move_next_line},
11063 {textview_move_previous_line},
11064 {textview_delete_forward_character},
11065 {textview_delete_backward_character},
11066 {textview_delete_forward_word},
11067 {textview_delete_backward_word},
11068 {textview_delete_line},
11069 {textview_delete_to_line_end}
11072 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
11074 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
11075 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
11076 if (action_table[action].do_action)
11077 action_table[action].do_action(text);
11079 g_warning("Not implemented yet.");
11083 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
11085 GtkAllocation allocation;
11089 if (GTK_IS_EDITABLE(widget)) {
11090 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
11091 gtk_editable_set_position(GTK_EDITABLE(widget),
11094 if ((parent = gtk_widget_get_parent(widget))
11095 && (parent = gtk_widget_get_parent(parent))
11096 && (parent = gtk_widget_get_parent(parent))) {
11097 if (GTK_IS_SCROLLED_WINDOW(parent)) {
11098 gtk_widget_get_allocation(widget, &allocation);
11099 gint y = allocation.y;
11100 gint height = allocation.height;
11101 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
11102 (GTK_SCROLLED_WINDOW(parent));
11104 gfloat value = gtk_adjustment_get_value(shown);
11105 gfloat upper = gtk_adjustment_get_upper(shown);
11106 gfloat page_size = gtk_adjustment_get_page_size(shown);
11107 if (y < (int)value) {
11108 gtk_adjustment_set_value(shown, y - 1);
11110 if ((y + height) > ((int)value + (int)page_size)) {
11111 if ((y - height - 1) < ((int)upper - (int)page_size)) {
11112 gtk_adjustment_set_value(shown,
11113 y + height - (int)page_size - 1);
11115 gtk_adjustment_set_value(shown,
11116 (int)upper - (int)page_size - 1);
11123 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
11124 compose->focused_editable = widget;
11126 #ifdef GENERIC_UMPC
11127 if (GTK_IS_TEXT_VIEW(widget)
11128 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
11129 g_object_ref(compose->notebook);
11130 g_object_ref(compose->edit_vbox);
11131 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11132 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11133 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
11134 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
11135 g_object_unref(compose->notebook);
11136 g_object_unref(compose->edit_vbox);
11137 g_signal_handlers_block_by_func(G_OBJECT(widget),
11138 G_CALLBACK(compose_grab_focus_cb),
11140 gtk_widget_grab_focus(widget);
11141 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11142 G_CALLBACK(compose_grab_focus_cb),
11144 } else if (!GTK_IS_TEXT_VIEW(widget)
11145 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
11146 g_object_ref(compose->notebook);
11147 g_object_ref(compose->edit_vbox);
11148 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
11149 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
11150 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
11151 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
11152 g_object_unref(compose->notebook);
11153 g_object_unref(compose->edit_vbox);
11154 g_signal_handlers_block_by_func(G_OBJECT(widget),
11155 G_CALLBACK(compose_grab_focus_cb),
11157 gtk_widget_grab_focus(widget);
11158 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
11159 G_CALLBACK(compose_grab_focus_cb),
11165 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
11167 compose->modified = TRUE;
11168 // compose_beautify_paragraph(compose, NULL, TRUE);
11169 #ifndef GENERIC_UMPC
11170 compose_set_title(compose);
11174 static void compose_wrap_cb(GtkAction *action, gpointer data)
11176 Compose *compose = (Compose *)data;
11177 compose_beautify_paragraph(compose, NULL, TRUE);
11180 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
11182 Compose *compose = (Compose *)data;
11183 compose_wrap_all_full(compose, TRUE);
11186 static void compose_find_cb(GtkAction *action, gpointer data)
11188 Compose *compose = (Compose *)data;
11190 message_search_compose(compose);
11193 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
11196 Compose *compose = (Compose *)data;
11197 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11198 if (compose->autowrap)
11199 compose_wrap_all_full(compose, TRUE);
11200 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11203 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
11206 Compose *compose = (Compose *)data;
11207 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11210 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
11212 Compose *compose = (Compose *)data;
11214 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11217 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
11219 Compose *compose = (Compose *)data;
11221 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11224 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11226 g_free(compose->privacy_system);
11227 g_free(compose->encdata);
11229 compose->privacy_system = g_strdup(account->default_privacy_system);
11230 compose_update_privacy_system_menu_item(compose, warn);
11233 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11235 Compose *compose = (Compose *)data;
11237 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11238 gtk_widget_show(compose->ruler_hbox);
11239 prefs_common.show_ruler = TRUE;
11241 gtk_widget_hide(compose->ruler_hbox);
11242 gtk_widget_queue_resize(compose->edit_vbox);
11243 prefs_common.show_ruler = FALSE;
11247 static void compose_attach_drag_received_cb (GtkWidget *widget,
11248 GdkDragContext *context,
11251 GtkSelectionData *data,
11254 gpointer user_data)
11256 Compose *compose = (Compose *)user_data;
11260 type = gtk_selection_data_get_data_type(data);
11261 if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11262 && gtk_drag_get_source_widget(context) !=
11263 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11264 list = uri_list_extract_filenames(
11265 (const gchar *)gtk_selection_data_get_data(data));
11266 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11267 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11268 compose_attach_append
11269 (compose, (const gchar *)tmp->data,
11270 utf8_filename, NULL, NULL);
11271 g_free(utf8_filename);
11273 if (list) compose_changed_cb(NULL, compose);
11274 list_free_strings(list);
11276 } else if (gtk_drag_get_source_widget(context)
11277 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11278 /* comes from our summaryview */
11279 SummaryView * summaryview = NULL;
11280 GSList * list = NULL, *cur = NULL;
11282 if (mainwindow_get_mainwindow())
11283 summaryview = mainwindow_get_mainwindow()->summaryview;
11286 list = summary_get_selected_msg_list(summaryview);
11288 for (cur = list; cur; cur = cur->next) {
11289 MsgInfo *msginfo = (MsgInfo *)cur->data;
11290 gchar *file = NULL;
11292 file = procmsg_get_message_file_full(msginfo,
11295 compose_attach_append(compose, (const gchar *)file,
11296 (const gchar *)file, "message/rfc822", NULL);
11300 g_slist_free(list);
11304 static gboolean compose_drag_drop(GtkWidget *widget,
11305 GdkDragContext *drag_context,
11307 guint time, gpointer user_data)
11309 /* not handling this signal makes compose_insert_drag_received_cb
11314 static gboolean completion_set_focus_to_subject
11315 (GtkWidget *widget,
11316 GdkEventKey *event,
11319 cm_return_val_if_fail(compose != NULL, FALSE);
11321 /* make backtab move to subject field */
11322 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11323 gtk_widget_grab_focus(compose->subject_entry);
11329 static void compose_insert_drag_received_cb (GtkWidget *widget,
11330 GdkDragContext *drag_context,
11333 GtkSelectionData *data,
11336 gpointer user_data)
11338 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 if (list == NULL && strstr(ddata, "://")) {
11351 /* Assume a list of no files, and data has ://, is a remote link */
11352 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11353 gchar *tmpfile = get_tmp_file();
11354 str_write_to_file(tmpdata, tmpfile);
11356 compose_insert_file(compose, tmpfile);
11357 claws_unlink(tmpfile);
11359 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11360 compose_beautify_paragraph(compose, NULL, TRUE);
11363 switch (prefs_common.compose_dnd_mode) {
11364 case COMPOSE_DND_ASK:
11365 val = alertpanel_full(_("Insert or attach?"),
11366 _("Do you want to insert the contents of the file(s) "
11367 "into the message body, or attach it to the email?"),
11368 GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
11369 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11371 case COMPOSE_DND_INSERT:
11372 val = G_ALERTALTERNATE;
11374 case COMPOSE_DND_ATTACH:
11375 val = G_ALERTOTHER;
11378 /* unexpected case */
11379 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11382 if (val & G_ALERTDISABLE) {
11383 val &= ~G_ALERTDISABLE;
11384 /* remember what action to perform by default, only if we don't click Cancel */
11385 if (val == G_ALERTALTERNATE)
11386 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11387 else if (val == G_ALERTOTHER)
11388 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11391 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11392 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11393 list_free_strings(list);
11396 } else if (val == G_ALERTOTHER) {
11397 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11398 list_free_strings(list);
11403 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11404 compose_insert_file(compose, (const gchar *)tmp->data);
11406 list_free_strings(list);
11408 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11413 static void compose_header_drag_received_cb (GtkWidget *widget,
11414 GdkDragContext *drag_context,
11417 GtkSelectionData *data,
11420 gpointer user_data)
11422 GtkEditable *entry = (GtkEditable *)user_data;
11423 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11425 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11428 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11429 gchar *decoded=g_new(gchar, strlen(email));
11432 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11433 gtk_editable_delete_text(entry, 0, -1);
11434 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11435 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11439 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11442 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11444 Compose *compose = (Compose *)data;
11446 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11447 compose->return_receipt = TRUE;
11449 compose->return_receipt = FALSE;
11452 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11454 Compose *compose = (Compose *)data;
11456 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11457 compose->remove_references = TRUE;
11459 compose->remove_references = FALSE;
11462 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11463 ComposeHeaderEntry *headerentry)
11465 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11469 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11470 GdkEventKey *event,
11471 ComposeHeaderEntry *headerentry)
11473 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11474 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11475 !(event->state & GDK_MODIFIER_MASK) &&
11476 (event->keyval == GDK_KEY_BackSpace) &&
11477 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11478 gtk_container_remove
11479 (GTK_CONTAINER(headerentry->compose->header_table),
11480 headerentry->combo);
11481 gtk_container_remove
11482 (GTK_CONTAINER(headerentry->compose->header_table),
11483 headerentry->entry);
11484 headerentry->compose->header_list =
11485 g_slist_remove(headerentry->compose->header_list,
11487 g_free(headerentry);
11488 } else if (event->keyval == GDK_KEY_Tab) {
11489 if (headerentry->compose->header_last == headerentry) {
11490 /* Override default next focus, and give it to subject_entry
11491 * instead of notebook tabs
11493 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11494 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11501 static gboolean scroll_postpone(gpointer data)
11503 Compose *compose = (Compose *)data;
11505 if (compose->batch)
11508 GTK_EVENTS_FLUSH();
11509 compose_show_first_last_header(compose, FALSE);
11513 static void compose_headerentry_changed_cb(GtkWidget *entry,
11514 ComposeHeaderEntry *headerentry)
11516 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11517 compose_create_header_entry(headerentry->compose);
11518 g_signal_handlers_disconnect_matched
11519 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11520 0, 0, NULL, NULL, headerentry);
11522 if (!headerentry->compose->batch)
11523 g_timeout_add(0, scroll_postpone, headerentry->compose);
11527 static gboolean compose_defer_auto_save_draft(Compose *compose)
11529 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11530 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11534 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11536 GtkAdjustment *vadj;
11538 cm_return_if_fail(compose);
11543 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11544 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11545 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11546 gtk_widget_get_parent(compose->header_table)));
11547 gtk_adjustment_set_value(vadj, (show_first ?
11548 gtk_adjustment_get_lower(vadj) :
11549 (gtk_adjustment_get_upper(vadj) -
11550 gtk_adjustment_get_page_size(vadj))));
11551 gtk_adjustment_changed(vadj);
11554 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11555 const gchar *text, gint len, Compose *compose)
11557 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11558 (G_OBJECT(compose->text), "paste_as_quotation"));
11561 cm_return_if_fail(text != NULL);
11563 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11564 G_CALLBACK(text_inserted),
11566 if (paste_as_quotation) {
11568 const gchar *qmark;
11570 GtkTextIter start_iter;
11573 len = strlen(text);
11575 new_text = g_strndup(text, len);
11577 qmark = compose_quote_char_from_context(compose);
11579 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11580 gtk_text_buffer_place_cursor(buffer, iter);
11582 pos = gtk_text_iter_get_offset(iter);
11584 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11585 _("Quote format error at line %d."));
11586 quote_fmt_reset_vartable();
11588 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11589 GINT_TO_POINTER(paste_as_quotation - 1));
11591 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11592 gtk_text_buffer_place_cursor(buffer, iter);
11593 gtk_text_buffer_delete_mark(buffer, mark);
11595 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11596 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11597 compose_beautify_paragraph(compose, &start_iter, FALSE);
11598 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11599 gtk_text_buffer_delete_mark(buffer, mark);
11601 if (strcmp(text, "\n") || compose->automatic_break
11602 || gtk_text_iter_starts_line(iter)) {
11603 GtkTextIter before_ins;
11604 gtk_text_buffer_insert(buffer, iter, text, len);
11605 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11606 before_ins = *iter;
11607 gtk_text_iter_backward_chars(&before_ins, len);
11608 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11611 /* check if the preceding is just whitespace or quote */
11612 GtkTextIter start_line;
11613 gchar *tmp = NULL, *quote = NULL;
11614 gint quote_len = 0, is_normal = 0;
11615 start_line = *iter;
11616 gtk_text_iter_set_line_offset(&start_line, 0);
11617 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11620 if (*tmp == '\0') {
11623 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11631 gtk_text_buffer_insert(buffer, iter, text, len);
11633 gtk_text_buffer_insert_with_tags_by_name(buffer,
11634 iter, text, len, "no_join", NULL);
11639 if (!paste_as_quotation) {
11640 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11641 compose_beautify_paragraph(compose, iter, FALSE);
11642 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11643 gtk_text_buffer_delete_mark(buffer, mark);
11646 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11647 G_CALLBACK(text_inserted),
11649 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11651 if (compose_can_autosave(compose) &&
11652 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11653 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11654 compose->draft_timeout_tag = g_timeout_add
11655 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11659 static void compose_check_all(GtkAction *action, gpointer data)
11661 Compose *compose = (Compose *)data;
11662 if (!compose->gtkaspell)
11665 if (gtk_widget_has_focus(compose->subject_entry))
11666 claws_spell_entry_check_all(
11667 CLAWS_SPELL_ENTRY(compose->subject_entry));
11669 gtkaspell_check_all(compose->gtkaspell);
11672 static void compose_highlight_all(GtkAction *action, gpointer data)
11674 Compose *compose = (Compose *)data;
11675 if (compose->gtkaspell) {
11676 claws_spell_entry_recheck_all(
11677 CLAWS_SPELL_ENTRY(compose->subject_entry));
11678 gtkaspell_highlight_all(compose->gtkaspell);
11682 static void compose_check_backwards(GtkAction *action, gpointer data)
11684 Compose *compose = (Compose *)data;
11685 if (!compose->gtkaspell) {
11686 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11690 if (gtk_widget_has_focus(compose->subject_entry))
11691 claws_spell_entry_check_backwards(
11692 CLAWS_SPELL_ENTRY(compose->subject_entry));
11694 gtkaspell_check_backwards(compose->gtkaspell);
11697 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11699 Compose *compose = (Compose *)data;
11700 if (!compose->gtkaspell) {
11701 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11705 if (gtk_widget_has_focus(compose->subject_entry))
11706 claws_spell_entry_check_forwards_go(
11707 CLAWS_SPELL_ENTRY(compose->subject_entry));
11709 gtkaspell_check_forwards_go(compose->gtkaspell);
11714 *\brief Guess originating forward account from MsgInfo and several
11715 * "common preference" settings. Return NULL if no guess.
11717 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11719 PrefsAccount *account = NULL;
11721 cm_return_val_if_fail(msginfo, NULL);
11722 cm_return_val_if_fail(msginfo->folder, NULL);
11723 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11725 if (msginfo->folder->prefs->enable_default_account)
11726 account = account_find_from_id(msginfo->folder->prefs->default_account);
11729 account = msginfo->folder->folder->account;
11731 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11733 Xstrdup_a(to, msginfo->to, return NULL);
11734 extract_address(to);
11735 account = account_find_from_address(to, FALSE);
11738 if (!account && prefs_common.forward_account_autosel) {
11739 gchar cc[BUFFSIZE];
11740 if (!procheader_get_header_from_msginfo
11741 (msginfo, cc,sizeof cc , "Cc:")) {
11742 gchar *buf = cc + strlen("Cc:");
11743 extract_address(buf);
11744 account = account_find_from_address(buf, FALSE);
11748 if (!account && prefs_common.forward_account_autosel) {
11749 gchar deliveredto[BUFFSIZE];
11750 if (!procheader_get_header_from_msginfo
11751 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11752 gchar *buf = deliveredto + strlen("Delivered-To:");
11753 extract_address(buf);
11754 account = account_find_from_address(buf, FALSE);
11761 gboolean compose_close(Compose *compose)
11765 cm_return_val_if_fail(compose, FALSE);
11767 if (!g_mutex_trylock(compose->mutex)) {
11768 /* we have to wait for the (possibly deferred by auto-save)
11769 * drafting to be done, before destroying the compose under
11771 debug_print("waiting for drafting to finish...\n");
11772 compose_allow_user_actions(compose, FALSE);
11773 if (compose->close_timeout_tag == 0) {
11774 compose->close_timeout_tag =
11775 g_timeout_add (500, (GSourceFunc) compose_close,
11781 if (compose->draft_timeout_tag >= 0) {
11782 g_source_remove(compose->draft_timeout_tag);
11783 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11786 gtkut_widget_get_uposition(compose->window, &x, &y);
11787 if (!compose->batch) {
11788 prefs_common.compose_x = x;
11789 prefs_common.compose_y = y;
11791 g_mutex_unlock(compose->mutex);
11792 compose_destroy(compose);
11797 * Add entry field for each address in list.
11798 * \param compose E-Mail composition object.
11799 * \param listAddress List of (formatted) E-Mail addresses.
11801 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11804 node = listAddress;
11806 addr = ( gchar * ) node->data;
11807 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11808 node = g_list_next( node );
11812 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11813 guint action, gboolean opening_multiple)
11815 gchar *body = NULL;
11816 GSList *new_msglist = NULL;
11817 MsgInfo *tmp_msginfo = NULL;
11818 gboolean originally_enc = FALSE;
11819 gboolean originally_sig = FALSE;
11820 Compose *compose = NULL;
11821 gchar *s_system = NULL;
11823 cm_return_if_fail(msgview != NULL);
11825 cm_return_if_fail(msginfo_list != NULL);
11827 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11828 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11829 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11831 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11832 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11833 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11834 orig_msginfo, mimeinfo);
11835 if (tmp_msginfo != NULL) {
11836 new_msglist = g_slist_append(NULL, tmp_msginfo);
11838 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11839 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11840 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11842 tmp_msginfo->folder = orig_msginfo->folder;
11843 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11844 if (orig_msginfo->tags) {
11845 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11846 tmp_msginfo->folder->tags_dirty = TRUE;
11852 if (!opening_multiple)
11853 body = messageview_get_selection(msgview);
11856 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11857 procmsg_msginfo_free(tmp_msginfo);
11858 g_slist_free(new_msglist);
11860 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11862 if (compose && originally_enc) {
11863 compose_force_encryption(compose, compose->account, FALSE, s_system);
11866 if (compose && originally_sig && compose->account->default_sign_reply) {
11867 compose_force_signing(compose, compose->account, s_system);
11871 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11874 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11877 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11878 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11879 GSList *cur = msginfo_list;
11880 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11881 "messages. Opening the windows "
11882 "could take some time. Do you "
11883 "want to continue?"),
11884 g_slist_length(msginfo_list));
11885 if (g_slist_length(msginfo_list) > 9
11886 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11887 != G_ALERTALTERNATE) {
11892 /* We'll open multiple compose windows */
11893 /* let the WM place the next windows */
11894 compose_force_window_origin = FALSE;
11895 for (; cur; cur = cur->next) {
11897 tmplist.data = cur->data;
11898 tmplist.next = NULL;
11899 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11901 compose_force_window_origin = TRUE;
11903 /* forwarding multiple mails as attachments is done via a
11904 * single compose window */
11905 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11909 void compose_check_for_email_account(Compose *compose)
11911 PrefsAccount *ac = NULL, *curr = NULL;
11917 if (compose->account && compose->account->protocol == A_NNTP) {
11918 ac = account_get_cur_account();
11919 if (ac->protocol == A_NNTP) {
11920 list = account_get_list();
11922 for( ; list != NULL ; list = g_list_next(list)) {
11923 curr = (PrefsAccount *) list->data;
11924 if (curr->protocol != A_NNTP) {
11930 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11935 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11936 const gchar *address)
11938 GSList *msginfo_list = NULL;
11939 gchar *body = messageview_get_selection(msgview);
11942 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11944 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11945 compose_check_for_email_account(compose);
11946 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11947 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11948 compose_reply_set_subject(compose, msginfo);
11951 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11954 void compose_set_position(Compose *compose, gint pos)
11956 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11958 gtkut_text_view_set_position(text, pos);
11961 gboolean compose_search_string(Compose *compose,
11962 const gchar *str, gboolean case_sens)
11964 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11966 return gtkut_text_view_search_string(text, str, case_sens);
11969 gboolean compose_search_string_backward(Compose *compose,
11970 const gchar *str, gboolean case_sens)
11972 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11974 return gtkut_text_view_search_string_backward(text, str, case_sens);
11977 /* allocate a msginfo structure and populate its data from a compose data structure */
11978 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11980 MsgInfo *newmsginfo;
11982 gchar buf[BUFFSIZE];
11984 cm_return_val_if_fail( compose != NULL, NULL );
11986 newmsginfo = procmsg_msginfo_new();
11989 get_rfc822_date(buf, sizeof(buf));
11990 newmsginfo->date = g_strdup(buf);
11993 if (compose->from_name) {
11994 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11995 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11999 if (compose->subject_entry)
12000 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
12002 /* to, cc, reply-to, newsgroups */
12003 for (list = compose->header_list; list; list = list->next) {
12004 gchar *header = gtk_editable_get_chars(
12006 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
12007 gchar *entry = gtk_editable_get_chars(
12008 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
12010 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
12011 if ( newmsginfo->to == NULL ) {
12012 newmsginfo->to = g_strdup(entry);
12013 } else if (entry && *entry) {
12014 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
12015 g_free(newmsginfo->to);
12016 newmsginfo->to = tmp;
12019 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
12020 if ( newmsginfo->cc == NULL ) {
12021 newmsginfo->cc = g_strdup(entry);
12022 } else if (entry && *entry) {
12023 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
12024 g_free(newmsginfo->cc);
12025 newmsginfo->cc = tmp;
12028 if ( strcasecmp(header,
12029 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12030 if ( newmsginfo->newsgroups == NULL ) {
12031 newmsginfo->newsgroups = g_strdup(entry);
12032 } else if (entry && *entry) {
12033 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
12034 g_free(newmsginfo->newsgroups);
12035 newmsginfo->newsgroups = tmp;
12043 /* other data is unset */
12049 /* update compose's dictionaries from folder dict settings */
12050 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
12051 FolderItem *folder_item)
12053 cm_return_if_fail(compose != NULL);
12055 if (compose->gtkaspell && folder_item && folder_item->prefs) {
12056 FolderItemPrefs *prefs = folder_item->prefs;
12058 if (prefs->enable_default_dictionary)
12059 gtkaspell_change_dict(compose->gtkaspell,
12060 prefs->default_dictionary, FALSE);
12061 if (folder_item->prefs->enable_default_alt_dictionary)
12062 gtkaspell_change_alt_dict(compose->gtkaspell,
12063 prefs->default_alt_dictionary);
12064 if (prefs->enable_default_dictionary
12065 || prefs->enable_default_alt_dictionary)
12066 compose_spell_menu_changed(compose);
12071 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
12073 Compose *compose = (Compose *)data;
12075 cm_return_if_fail(compose != NULL);
12077 gtk_widget_grab_focus(compose->text);
12080 static void from_name_activate_cb(GtkWidget *widget, gpointer data)
12082 gtk_combo_box_popup(GTK_COMBO_BOX(data));