2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 static GList *compose_list = NULL;
186 static GSList *extra_headers = NULL;
188 static Compose *compose_generic_new (PrefsAccount *account,
192 GList *listAddress );
194 static Compose *compose_create (PrefsAccount *account,
199 static void compose_entry_mark_default_to (Compose *compose,
200 const gchar *address);
201 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
202 ComposeQuoteMode quote_mode,
206 static Compose *compose_forward_multiple (PrefsAccount *account,
207 GSList *msginfo_list);
208 static Compose *compose_reply (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
214 static Compose *compose_reply_mode (ComposeMode mode,
215 GSList *msginfo_list,
217 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
218 static void compose_update_privacy_systems_menu(Compose *compose);
220 static GtkWidget *compose_account_option_menu_create
222 static void compose_set_out_encoding (Compose *compose);
223 static void compose_set_template_menu (Compose *compose);
224 static void compose_destroy (Compose *compose);
226 static MailField compose_entries_set (Compose *compose,
228 ComposeEntryType to_type);
229 static gint compose_parse_header (Compose *compose,
231 static gint compose_parse_manual_headers (Compose *compose,
233 HeaderEntry *entries);
234 static gchar *compose_parse_references (const gchar *ref,
237 static gchar *compose_quote_fmt (Compose *compose,
243 gboolean need_unescape,
244 const gchar *err_msg);
246 static void compose_reply_set_entry (Compose *compose,
252 followup_and_reply_to);
253 static void compose_reedit_set_entry (Compose *compose,
256 static void compose_insert_sig (Compose *compose,
258 static ComposeInsertResult compose_insert_file (Compose *compose,
261 static gboolean compose_attach_append (Compose *compose,
264 const gchar *content_type,
265 const gchar *charset);
266 static void compose_attach_parts (Compose *compose,
269 static gboolean compose_beautify_paragraph (Compose *compose,
270 GtkTextIter *par_iter,
272 static void compose_wrap_all (Compose *compose);
273 static void compose_wrap_all_full (Compose *compose,
276 static void compose_set_title (Compose *compose);
277 static void compose_select_account (Compose *compose,
278 PrefsAccount *account,
281 static PrefsAccount *compose_current_mail_account(void);
282 /* static gint compose_send (Compose *compose); */
283 static gboolean compose_check_for_valid_recipient
285 static gboolean compose_check_entries (Compose *compose,
286 gboolean check_everything);
287 static gint compose_write_to_file (Compose *compose,
290 gboolean attach_parts);
291 static gint compose_write_body_to_file (Compose *compose,
293 static gint compose_remove_reedit_target (Compose *compose,
295 static void compose_remove_draft (Compose *compose);
296 static gint compose_queue_sub (Compose *compose,
300 gboolean check_subject,
301 gboolean remove_reedit_target);
302 static int compose_add_attachments (Compose *compose,
304 static gchar *compose_get_header (Compose *compose);
305 static gchar *compose_get_manual_headers_info (Compose *compose);
307 static void compose_convert_header (Compose *compose,
312 gboolean addr_field);
314 static void compose_attach_info_free (AttachInfo *ainfo);
315 static void compose_attach_remove_selected (GtkAction *action,
318 static void compose_template_apply (Compose *compose,
321 static void compose_attach_property (GtkAction *action,
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
342 static void compose_set_ext_editor_sensitive (Compose *compose,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header,
353 gchar *text, ComposePrefType pref_type);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
359 static void compose_dict_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static void compose_notebook_size_alloc (GtkNotebook *notebook,
367 GtkAllocation *allocation,
369 static gboolean compose_edit_size_alloc (GtkEditable *widget,
370 GtkAllocation *allocation,
371 GtkSHRuler *shruler);
372 static void account_activated (GtkComboBox *optmenu,
374 static void attach_selected (GtkTreeView *tree_view,
375 GtkTreePath *tree_path,
376 GtkTreeViewColumn *column,
378 static gboolean attach_button_pressed (GtkWidget *widget,
379 GdkEventButton *event,
381 static gboolean attach_key_pressed (GtkWidget *widget,
384 static void compose_send_cb (GtkAction *action, gpointer data);
385 static void compose_send_later_cb (GtkAction *action, gpointer data);
387 static void compose_save_cb (GtkAction *action,
390 static void compose_attach_cb (GtkAction *action,
392 static void compose_insert_file_cb (GtkAction *action,
394 static void compose_insert_sig_cb (GtkAction *action,
396 static void compose_replace_sig_cb (GtkAction *action,
399 static void compose_close_cb (GtkAction *action,
401 static void compose_print_cb (GtkAction *action,
404 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
406 static void compose_address_cb (GtkAction *action,
408 static void about_show_cb (GtkAction *action,
410 static void compose_template_activate_cb(GtkWidget *widget,
413 static void compose_ext_editor_cb (GtkAction *action,
416 static gint compose_delete_cb (GtkWidget *widget,
420 static void compose_undo_cb (GtkAction *action,
422 static void compose_redo_cb (GtkAction *action,
424 static void compose_cut_cb (GtkAction *action,
426 static void compose_copy_cb (GtkAction *action,
428 static void compose_paste_cb (GtkAction *action,
430 static void compose_paste_as_quote_cb (GtkAction *action,
432 static void compose_paste_no_wrap_cb (GtkAction *action,
434 static void compose_paste_wrap_cb (GtkAction *action,
436 static void compose_allsel_cb (GtkAction *action,
439 static void compose_advanced_action_cb (GtkAction *action,
442 static void compose_grab_focus_cb (GtkWidget *widget,
445 static void compose_changed_cb (GtkTextBuffer *textbuf,
448 static void compose_wrap_cb (GtkAction *action,
450 static void compose_wrap_all_cb (GtkAction *action,
452 static void compose_find_cb (GtkAction *action,
454 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
456 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
459 static void compose_toggle_ruler_cb (GtkToggleAction *action,
461 static void compose_toggle_sign_cb (GtkToggleAction *action,
463 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
465 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
466 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
467 static void activate_privacy_system (Compose *compose,
468 PrefsAccount *account,
470 static void compose_use_signing(Compose *compose, gboolean use_signing);
471 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
472 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
474 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
476 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
477 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
478 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
509 static gboolean completion_set_focus_to_subject
514 static void text_inserted (GtkTextBuffer *buffer,
519 static Compose *compose_generic_reply(MsgInfo *msginfo,
520 ComposeQuoteMode quote_mode,
524 gboolean followup_and_reply_to,
527 static void compose_headerentry_changed_cb (GtkWidget *entry,
528 ComposeHeaderEntry *headerentry);
529 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
531 ComposeHeaderEntry *headerentry);
532 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
533 ComposeHeaderEntry *headerentry);
535 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
537 static void compose_allow_user_actions (Compose *compose, gboolean allow);
539 static void compose_nothing_cb (GtkAction *action, gpointer data)
545 static void compose_check_all (GtkAction *action, gpointer data);
546 static void compose_highlight_all (GtkAction *action, gpointer data);
547 static void compose_check_backwards (GtkAction *action, gpointer data);
548 static void compose_check_forwards_go (GtkAction *action, gpointer data);
551 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
553 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
556 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
557 FolderItem *folder_item);
559 static void compose_attach_update_label(Compose *compose);
560 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
561 gboolean respect_default_to);
562 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
564 static GtkActionEntry compose_popup_entries[] =
566 {"Compose", NULL, "Compose" },
567 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
568 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
569 {"Compose/---", NULL, "---", NULL, NULL, NULL },
570 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
573 static GtkActionEntry compose_entries[] =
575 {"Menu", NULL, "Menu" },
577 {"Message", NULL, N_("_Message") },
578 {"Edit", NULL, N_("_Edit") },
580 {"Spelling", NULL, N_("_Spelling") },
582 {"Options", NULL, N_("_Options") },
583 {"Tools", NULL, N_("_Tools") },
584 {"Help", NULL, N_("_Help") },
586 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
587 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
588 {"Message/---", NULL, "---" },
590 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
591 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
592 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
593 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
594 /* {"Message/---", NULL, "---" }, */
595 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
596 /* {"Message/---", NULL, "---" }, */
597 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
598 /* {"Message/---", NULL, "---" }, */
599 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
602 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
603 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
604 {"Edit/---", NULL, "---" },
606 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
607 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
608 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
610 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
611 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
612 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
613 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
615 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
617 {"Edit/Advanced", NULL, N_("A_dvanced") },
618 {"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*/
619 {"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*/
620 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
621 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
622 {"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*/
623 {"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*/
624 {"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*/
625 {"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*/
626 {"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*/
627 {"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*/
628 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
629 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
630 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
631 {"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*/
633 /* {"Edit/---", NULL, "---" }, */
634 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
638 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
643 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
644 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
645 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
646 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
648 {"Spelling/---", NULL, "---" },
649 {"Spelling/Options", NULL, N_("_Options") },
654 {"Options/ReplyMode", NULL, N_("Reply _mode") },
655 {"Options/---", NULL, "---" },
656 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
657 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
659 /* {"Options/---", NULL, "---" }, */
661 {"Options/Priority", NULL, N_("_Priority") },
663 {"Options/Encoding", NULL, N_("Character _encoding") },
664 {"Options/Encoding/---", NULL, "---" },
665 #define ENC_ACTION(cs_char,c_char,string) \
666 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
668 {"Options/Encoding/Western", NULL, N_("Western European") },
669 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
670 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
671 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
672 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
673 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
674 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
675 {"Options/Encoding/Korean", NULL, N_("Korean") },
676 {"Options/Encoding/Thai", NULL, N_("Thai") },
679 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
681 {"Tools/Template", NULL, N_("_Template") },
682 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
683 {"Tools/Actions", NULL, N_("Actio_ns") },
684 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
687 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
690 static GtkToggleActionEntry compose_toggle_entries[] =
692 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
693 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
694 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
695 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
696 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
697 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
698 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
701 static GtkRadioActionEntry compose_radio_rm_entries[] =
703 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
704 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
705 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
706 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
709 static GtkRadioActionEntry compose_radio_prio_entries[] =
711 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
712 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
713 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
714 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
718 static GtkRadioActionEntry compose_radio_enc_entries[] =
720 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
754 static GtkTargetEntry compose_mime_types[] =
756 {"text/uri-list", 0, 0},
757 {"UTF8_STRING", 0, 0},
761 static gboolean compose_put_existing_to_front(MsgInfo *info)
763 GList *compose_list = compose_get_compose_list();
767 for (elem = compose_list; elem != NULL && elem->data != NULL;
769 Compose *c = (Compose*)elem->data;
771 if (!c->targetinfo || !c->targetinfo->msgid ||
775 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
776 gtkut_window_popup(c->window);
784 static GdkColor quote_color1 =
785 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
786 static GdkColor quote_color2 =
787 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
788 static GdkColor quote_color3 =
789 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_bgcolor1 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
793 static GdkColor quote_bgcolor2 =
794 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor quote_bgcolor3 =
796 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor signature_color = {
805 static GdkColor uri_color = {
812 static void compose_create_tags(GtkTextView *text, Compose *compose)
814 GtkTextBuffer *buffer;
815 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 #if !GTK_CHECK_VERSION(2, 24, 0)
823 buffer = gtk_text_view_get_buffer(text);
825 if (prefs_common.enable_color) {
826 /* grab the quote colors, converting from an int to a GdkColor */
827 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
829 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
831 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
833 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
835 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
837 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
839 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
841 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
844 signature_color = quote_color1 = quote_color2 = quote_color3 =
845 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
848 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
849 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
850 "foreground-gdk", "e_color1,
851 "paragraph-background-gdk", "e_bgcolor1,
853 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
854 "foreground-gdk", "e_color2,
855 "paragraph-background-gdk", "e_bgcolor2,
857 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
858 "foreground-gdk", "e_color3,
859 "paragraph-background-gdk", "e_bgcolor3,
862 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
863 "foreground-gdk", "e_color1,
865 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
866 "foreground-gdk", "e_color2,
868 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
869 "foreground-gdk", "e_color3,
873 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
874 "foreground-gdk", &signature_color,
877 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
878 "foreground-gdk", &uri_color,
880 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
881 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
883 #if !GTK_CHECK_VERSION(2, 24, 0)
884 color[0] = quote_color1;
885 color[1] = quote_color2;
886 color[2] = quote_color3;
887 color[3] = quote_bgcolor1;
888 color[4] = quote_bgcolor2;
889 color[5] = quote_bgcolor3;
890 color[6] = signature_color;
891 color[7] = uri_color;
893 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
894 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
896 for (i = 0; i < 8; i++) {
897 if (success[i] == FALSE) {
898 g_warning("Compose: color allocation failed.\n");
899 quote_color1 = quote_color2 = quote_color3 =
900 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
901 signature_color = uri_color = black;
907 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
910 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
913 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
915 return compose_generic_new(account, mailto, item, NULL, NULL);
918 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
920 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
923 #define SCROLL_TO_CURSOR(compose) { \
924 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
925 gtk_text_view_get_buffer( \
926 GTK_TEXT_VIEW(compose->text))); \
927 gtk_text_view_scroll_mark_onscreen( \
928 GTK_TEXT_VIEW(compose->text), \
932 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
935 if (folderidentifier) {
936 #if !GTK_CHECK_VERSION(2, 24, 0)
937 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
939 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
941 prefs_common.compose_save_to_history = add_history(
942 prefs_common.compose_save_to_history, folderidentifier);
943 #if !GTK_CHECK_VERSION(2, 24, 0)
944 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
945 prefs_common.compose_save_to_history);
947 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
948 prefs_common.compose_save_to_history);
952 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
953 if (folderidentifier)
954 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
956 gtk_entry_set_text(GTK_ENTRY(entry), "");
959 static gchar *compose_get_save_to(Compose *compose)
962 gchar *result = NULL;
963 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
964 result = gtk_editable_get_chars(entry, 0, -1);
967 #if !GTK_CHECK_VERSION(2, 24, 0)
968 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
970 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
972 prefs_common.compose_save_to_history = add_history(
973 prefs_common.compose_save_to_history, result);
974 #if !GTK_CHECK_VERSION(2, 24, 0)
975 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
976 prefs_common.compose_save_to_history);
978 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
979 prefs_common.compose_save_to_history);
985 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
986 GList *attach_files, GList *listAddress )
989 GtkTextView *textview;
990 GtkTextBuffer *textbuf;
992 const gchar *subject_format = NULL;
993 const gchar *body_format = NULL;
994 gchar *mailto_from = NULL;
995 PrefsAccount *mailto_account = NULL;
996 MsgInfo* dummyinfo = NULL;
997 gint cursor_pos = -1;
998 MailField mfield = NO_FIELD_PRESENT;
1002 /* check if mailto defines a from */
1003 if (mailto && *mailto != '\0') {
1004 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1005 /* mailto defines a from, check if we can get account prefs from it,
1006 if not, the account prefs will be guessed using other ways, but we'll keep
1009 mailto_account = account_find_from_address(mailto_from, TRUE);
1010 if (mailto_account == NULL) {
1012 Xstrdup_a(tmp_from, mailto_from, return NULL);
1013 extract_address(tmp_from);
1014 mailto_account = account_find_from_address(tmp_from, TRUE);
1018 account = mailto_account;
1021 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1022 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1023 account = account_find_from_id(item->prefs->default_account);
1025 /* if no account prefs set, fallback to the current one */
1026 if (!account) account = cur_account;
1027 cm_return_val_if_fail(account != NULL, NULL);
1029 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1031 /* override from name if mailto asked for it */
1033 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1034 g_free(mailto_from);
1036 /* override from name according to folder properties */
1037 if (item && item->prefs &&
1038 item->prefs->compose_with_format &&
1039 item->prefs->compose_override_from_format &&
1040 *item->prefs->compose_override_from_format != '\0') {
1045 dummyinfo = compose_msginfo_new_from_compose(compose);
1047 /* decode \-escape sequences in the internal representation of the quote format */
1048 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1049 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1052 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1053 compose->gtkaspell);
1055 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1057 quote_fmt_scan_string(tmp);
1060 buf = quote_fmt_get_buffer();
1062 alertpanel_error(_("New message From format error."));
1064 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1065 quote_fmt_reset_vartable();
1070 compose->replyinfo = NULL;
1071 compose->fwdinfo = NULL;
1073 textview = GTK_TEXT_VIEW(compose->text);
1074 textbuf = gtk_text_view_get_buffer(textview);
1075 compose_create_tags(textview, compose);
1077 undo_block(compose->undostruct);
1079 compose_set_dictionaries_from_folder_prefs(compose, item);
1082 if (account->auto_sig)
1083 compose_insert_sig(compose, FALSE);
1084 gtk_text_buffer_get_start_iter(textbuf, &iter);
1085 gtk_text_buffer_place_cursor(textbuf, &iter);
1087 if (account->protocol != A_NNTP) {
1088 if (mailto && *mailto != '\0') {
1089 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1092 compose_set_folder_prefs(compose, item, TRUE);
1094 if (item && item->ret_rcpt) {
1095 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1098 if (mailto && *mailto != '\0') {
1099 if (!strchr(mailto, '@'))
1100 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1102 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1103 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1104 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1105 mfield = TO_FIELD_PRESENT;
1108 * CLAWS: just don't allow return receipt request, even if the user
1109 * may want to send an email. simple but foolproof.
1111 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1113 compose_add_field_list( compose, listAddress );
1115 if (item && item->prefs && item->prefs->compose_with_format) {
1116 subject_format = item->prefs->compose_subject_format;
1117 body_format = item->prefs->compose_body_format;
1118 } else if (account->compose_with_format) {
1119 subject_format = account->compose_subject_format;
1120 body_format = account->compose_body_format;
1121 } else if (prefs_common.compose_with_format) {
1122 subject_format = prefs_common.compose_subject_format;
1123 body_format = prefs_common.compose_body_format;
1126 if (subject_format || body_format) {
1129 && *subject_format != '\0' )
1131 gchar *subject = NULL;
1136 dummyinfo = compose_msginfo_new_from_compose(compose);
1138 /* decode \-escape sequences in the internal representation of the quote format */
1139 tmp = g_malloc(strlen(subject_format)+1);
1140 pref_get_unescaped_pref(tmp, subject_format);
1142 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1144 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1145 compose->gtkaspell);
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1149 quote_fmt_scan_string(tmp);
1152 buf = quote_fmt_get_buffer();
1154 alertpanel_error(_("New message subject format error."));
1156 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1157 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1158 quote_fmt_reset_vartable();
1162 mfield = SUBJECT_FIELD_PRESENT;
1166 && *body_format != '\0' )
1169 GtkTextBuffer *buffer;
1170 GtkTextIter start, end;
1174 dummyinfo = compose_msginfo_new_from_compose(compose);
1176 text = GTK_TEXT_VIEW(compose->text);
1177 buffer = gtk_text_view_get_buffer(text);
1178 gtk_text_buffer_get_start_iter(buffer, &start);
1179 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1180 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1182 compose_quote_fmt(compose, dummyinfo,
1184 NULL, tmp, FALSE, TRUE,
1185 _("The body of the \"New message\" template has an error at line %d."));
1186 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1187 quote_fmt_reset_vartable();
1191 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1192 gtkaspell_highlight_all(compose->gtkaspell);
1194 mfield = BODY_FIELD_PRESENT;
1198 procmsg_msginfo_free( dummyinfo );
1204 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1205 ainfo = (AttachInfo *) curr->data;
1206 compose_attach_append(compose, ainfo->file, ainfo->name,
1207 ainfo->content_type, ainfo->charset);
1211 compose_show_first_last_header(compose, TRUE);
1213 /* Set save folder */
1214 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1215 gchar *folderidentifier;
1217 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1218 folderidentifier = folder_item_get_identifier(item);
1219 compose_set_save_to(compose, folderidentifier);
1220 g_free(folderidentifier);
1223 /* Place cursor according to provided input (mfield) */
1225 case NO_FIELD_PRESENT:
1226 if (compose->header_last)
1227 gtk_widget_grab_focus(compose->header_last->entry);
1229 case TO_FIELD_PRESENT:
1230 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1232 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1235 gtk_widget_grab_focus(compose->subject_entry);
1237 case SUBJECT_FIELD_PRESENT:
1238 textview = GTK_TEXT_VIEW(compose->text);
1241 textbuf = gtk_text_view_get_buffer(textview);
1244 mark = gtk_text_buffer_get_insert(textbuf);
1245 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1246 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1248 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1249 * only defers where it comes to the variable body
1250 * is not null. If no body is present compose->text
1251 * will be null in which case you cannot place the
1252 * cursor inside the component so. An empty component
1253 * is therefore created before placing the cursor
1255 case BODY_FIELD_PRESENT:
1256 cursor_pos = quote_fmt_get_cursor_pos();
1257 if (cursor_pos == -1)
1258 gtk_widget_grab_focus(compose->header_last->entry);
1260 gtk_widget_grab_focus(compose->text);
1264 undo_unblock(compose->undostruct);
1266 if (prefs_common.auto_exteditor)
1267 compose_exec_ext_editor(compose);
1269 compose->draft_timeout_tag = -1;
1270 SCROLL_TO_CURSOR(compose);
1272 compose->modified = FALSE;
1273 compose_set_title(compose);
1275 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1280 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1281 gboolean override_pref, const gchar *system)
1283 const gchar *privacy = NULL;
1285 cm_return_if_fail(compose != NULL);
1286 cm_return_if_fail(account != NULL);
1288 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1291 if (account->default_privacy_system && strlen(account->default_privacy_system))
1292 privacy = account->default_privacy_system;
1296 GSList *privacy_avail = privacy_get_system_ids();
1297 if (privacy_avail && g_slist_length(privacy_avail)) {
1298 privacy = (gchar *)(privacy_avail->data);
1301 if (privacy != NULL) {
1303 g_free(compose->privacy_system);
1304 compose->privacy_system = NULL;
1306 if (compose->privacy_system == NULL)
1307 compose->privacy_system = g_strdup(privacy);
1308 else if (*(compose->privacy_system) == '\0') {
1309 g_free(compose->privacy_system);
1310 compose->privacy_system = g_strdup(privacy);
1312 compose_update_privacy_system_menu_item(compose, FALSE);
1313 compose_use_encryption(compose, TRUE);
1317 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1319 const gchar *privacy = NULL;
1321 if (account->default_privacy_system && strlen(account->default_privacy_system))
1322 privacy = account->default_privacy_system;
1326 GSList *privacy_avail = privacy_get_system_ids();
1327 if (privacy_avail && g_slist_length(privacy_avail)) {
1328 privacy = (gchar *)(privacy_avail->data);
1332 if (privacy != NULL) {
1334 g_free(compose->privacy_system);
1335 compose->privacy_system = NULL;
1337 if (compose->privacy_system == NULL)
1338 compose->privacy_system = g_strdup(privacy);
1339 compose_update_privacy_system_menu_item(compose, FALSE);
1340 compose_use_signing(compose, TRUE);
1344 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1348 Compose *compose = NULL;
1350 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1352 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1353 cm_return_val_if_fail(msginfo != NULL, NULL);
1355 list_len = g_slist_length(msginfo_list);
1359 case COMPOSE_REPLY_TO_ADDRESS:
1360 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1361 FALSE, prefs_common.default_reply_list, FALSE, body);
1363 case COMPOSE_REPLY_WITH_QUOTE:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1365 FALSE, prefs_common.default_reply_list, FALSE, body);
1367 case COMPOSE_REPLY_WITHOUT_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1369 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1371 case COMPOSE_REPLY_TO_SENDER:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1373 FALSE, FALSE, TRUE, body);
1375 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1376 compose = compose_followup_and_reply_to(msginfo,
1377 COMPOSE_QUOTE_CHECK,
1378 FALSE, FALSE, body);
1380 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1381 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1382 FALSE, FALSE, TRUE, body);
1384 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1386 FALSE, FALSE, TRUE, NULL);
1388 case COMPOSE_REPLY_TO_ALL:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1390 TRUE, FALSE, FALSE, body);
1392 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1394 TRUE, FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1398 TRUE, FALSE, FALSE, NULL);
1400 case COMPOSE_REPLY_TO_LIST:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1402 FALSE, TRUE, FALSE, body);
1404 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1406 FALSE, TRUE, FALSE, body);
1408 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1410 FALSE, TRUE, FALSE, NULL);
1412 case COMPOSE_FORWARD:
1413 if (prefs_common.forward_as_attachment) {
1414 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1417 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1421 case COMPOSE_FORWARD_INLINE:
1422 /* check if we reply to more than one Message */
1423 if (list_len == 1) {
1424 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1427 /* more messages FALL THROUGH */
1428 case COMPOSE_FORWARD_AS_ATTACH:
1429 compose = compose_forward_multiple(NULL, msginfo_list);
1431 case COMPOSE_REDIRECT:
1432 compose = compose_redirect(NULL, msginfo, FALSE);
1435 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1438 if (compose == NULL) {
1439 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1443 compose->rmode = mode;
1444 switch (compose->rmode) {
1446 case COMPOSE_REPLY_WITH_QUOTE:
1447 case COMPOSE_REPLY_WITHOUT_QUOTE:
1448 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1449 debug_print("reply mode Normal\n");
1450 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1451 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1453 case COMPOSE_REPLY_TO_SENDER:
1454 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1455 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1456 debug_print("reply mode Sender\n");
1457 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1459 case COMPOSE_REPLY_TO_ALL:
1460 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1461 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1462 debug_print("reply mode All\n");
1463 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1465 case COMPOSE_REPLY_TO_LIST:
1466 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1467 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1468 debug_print("reply mode List\n");
1469 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1471 case COMPOSE_REPLY_TO_ADDRESS:
1472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1480 static Compose *compose_reply(MsgInfo *msginfo,
1481 ComposeQuoteMode quote_mode,
1487 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1488 to_sender, FALSE, body);
1491 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1492 ComposeQuoteMode quote_mode,
1497 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1498 to_sender, TRUE, body);
1501 static void compose_extract_original_charset(Compose *compose)
1503 MsgInfo *info = NULL;
1504 if (compose->replyinfo) {
1505 info = compose->replyinfo;
1506 } else if (compose->fwdinfo) {
1507 info = compose->fwdinfo;
1508 } else if (compose->targetinfo) {
1509 info = compose->targetinfo;
1512 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1513 MimeInfo *partinfo = mimeinfo;
1514 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1515 partinfo = procmime_mimeinfo_next(partinfo);
1517 compose->orig_charset =
1518 g_strdup(procmime_mimeinfo_get_parameter(
1519 partinfo, "charset"));
1521 procmime_mimeinfo_free_all(mimeinfo);
1525 #define SIGNAL_BLOCK(buffer) { \
1526 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1527 G_CALLBACK(compose_changed_cb), \
1529 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1530 G_CALLBACK(text_inserted), \
1534 #define SIGNAL_UNBLOCK(buffer) { \
1535 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(compose_changed_cb), \
1538 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1539 G_CALLBACK(text_inserted), \
1543 static Compose *compose_generic_reply(MsgInfo *msginfo,
1544 ComposeQuoteMode quote_mode,
1545 gboolean to_all, gboolean to_ml,
1547 gboolean followup_and_reply_to,
1551 PrefsAccount *account = NULL;
1552 GtkTextView *textview;
1553 GtkTextBuffer *textbuf;
1554 gboolean quote = FALSE;
1555 const gchar *qmark = NULL;
1556 const gchar *body_fmt = NULL;
1557 gchar *s_system = NULL;
1559 cm_return_val_if_fail(msginfo != NULL, NULL);
1560 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1562 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1564 cm_return_val_if_fail(account != NULL, NULL);
1566 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1568 compose->updating = TRUE;
1570 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1571 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1573 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1574 if (!compose->replyinfo)
1575 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1577 compose_extract_original_charset(compose);
1579 if (msginfo->folder && msginfo->folder->ret_rcpt)
1580 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1582 /* Set save folder */
1583 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1584 gchar *folderidentifier;
1586 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1587 folderidentifier = folder_item_get_identifier(msginfo->folder);
1588 compose_set_save_to(compose, folderidentifier);
1589 g_free(folderidentifier);
1592 if (compose_parse_header(compose, msginfo) < 0) {
1593 compose->updating = FALSE;
1594 compose_destroy(compose);
1598 /* override from name according to folder properties */
1599 if (msginfo->folder && msginfo->folder->prefs &&
1600 msginfo->folder->prefs->reply_with_format &&
1601 msginfo->folder->prefs->reply_override_from_format &&
1602 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1607 /* decode \-escape sequences in the internal representation of the quote format */
1608 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1609 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1613 compose->gtkaspell);
1615 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1617 quote_fmt_scan_string(tmp);
1620 buf = quote_fmt_get_buffer();
1622 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1624 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1625 quote_fmt_reset_vartable();
1630 textview = (GTK_TEXT_VIEW(compose->text));
1631 textbuf = gtk_text_view_get_buffer(textview);
1632 compose_create_tags(textview, compose);
1634 undo_block(compose->undostruct);
1636 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1637 gtkaspell_block_check(compose->gtkaspell);
1640 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1641 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1642 /* use the reply format of folder (if enabled), or the account's one
1643 (if enabled) or fallback to the global reply format, which is always
1644 enabled (even if empty), and use the relevant quotemark */
1646 if (msginfo->folder && msginfo->folder->prefs &&
1647 msginfo->folder->prefs->reply_with_format) {
1648 qmark = msginfo->folder->prefs->reply_quotemark;
1649 body_fmt = msginfo->folder->prefs->reply_body_format;
1651 } else if (account->reply_with_format) {
1652 qmark = account->reply_quotemark;
1653 body_fmt = account->reply_body_format;
1656 qmark = prefs_common.quotemark;
1657 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1658 body_fmt = gettext(prefs_common.quotefmt);
1665 /* empty quotemark is not allowed */
1666 if (qmark == NULL || *qmark == '\0')
1668 compose_quote_fmt(compose, compose->replyinfo,
1669 body_fmt, qmark, body, FALSE, TRUE,
1670 _("The body of the \"Reply\" template has an error at line %d."));
1671 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1672 quote_fmt_reset_vartable();
1675 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1676 compose_force_encryption(compose, account, FALSE, s_system);
1679 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1680 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1681 compose_force_signing(compose, account, s_system);
1685 SIGNAL_BLOCK(textbuf);
1687 if (account->auto_sig)
1688 compose_insert_sig(compose, FALSE);
1690 compose_wrap_all(compose);
1693 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1694 gtkaspell_highlight_all(compose->gtkaspell);
1695 gtkaspell_unblock_check(compose->gtkaspell);
1697 SIGNAL_UNBLOCK(textbuf);
1699 gtk_widget_grab_focus(compose->text);
1701 undo_unblock(compose->undostruct);
1703 if (prefs_common.auto_exteditor)
1704 compose_exec_ext_editor(compose);
1706 compose->modified = FALSE;
1707 compose_set_title(compose);
1709 compose->updating = FALSE;
1710 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1711 SCROLL_TO_CURSOR(compose);
1713 if (compose->deferred_destroy) {
1714 compose_destroy(compose);
1722 #define INSERT_FW_HEADER(var, hdr) \
1723 if (msginfo->var && *msginfo->var) { \
1724 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1725 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1726 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1729 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1730 gboolean as_attach, const gchar *body,
1731 gboolean no_extedit,
1735 GtkTextView *textview;
1736 GtkTextBuffer *textbuf;
1737 gint cursor_pos = -1;
1740 cm_return_val_if_fail(msginfo != NULL, NULL);
1741 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1744 !(account = compose_guess_forward_account_from_msginfo
1746 account = cur_account;
1748 if (!prefs_common.forward_as_attachment)
1749 mode = COMPOSE_FORWARD_INLINE;
1751 mode = COMPOSE_FORWARD;
1752 compose = compose_create(account, msginfo->folder, mode, batch);
1754 compose->updating = TRUE;
1755 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1756 if (!compose->fwdinfo)
1757 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1759 compose_extract_original_charset(compose);
1761 if (msginfo->subject && *msginfo->subject) {
1762 gchar *buf, *buf2, *p;
1764 buf = p = g_strdup(msginfo->subject);
1765 p += subject_get_prefix_length(p);
1766 memmove(buf, p, strlen(p) + 1);
1768 buf2 = g_strdup_printf("Fw: %s", buf);
1769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1775 /* override from name according to folder properties */
1776 if (msginfo->folder && msginfo->folder->prefs &&
1777 msginfo->folder->prefs->forward_with_format &&
1778 msginfo->folder->prefs->forward_override_from_format &&
1779 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1783 MsgInfo *full_msginfo = NULL;
1786 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1788 full_msginfo = procmsg_msginfo_copy(msginfo);
1790 /* decode \-escape sequences in the internal representation of the quote format */
1791 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1792 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1795 gtkaspell_block_check(compose->gtkaspell);
1796 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1797 compose->gtkaspell);
1799 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1801 quote_fmt_scan_string(tmp);
1804 buf = quote_fmt_get_buffer();
1806 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1808 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1809 quote_fmt_reset_vartable();
1812 procmsg_msginfo_free(full_msginfo);
1815 textview = GTK_TEXT_VIEW(compose->text);
1816 textbuf = gtk_text_view_get_buffer(textview);
1817 compose_create_tags(textview, compose);
1819 undo_block(compose->undostruct);
1823 msgfile = procmsg_get_message_file(msginfo);
1824 if (!is_file_exist(msgfile))
1825 g_warning("%s: file not exist\n", msgfile);
1827 compose_attach_append(compose, msgfile, msgfile,
1828 "message/rfc822", NULL);
1832 const gchar *qmark = NULL;
1833 const gchar *body_fmt = NULL;
1834 MsgInfo *full_msginfo;
1836 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1838 full_msginfo = procmsg_msginfo_copy(msginfo);
1840 /* use the forward format of folder (if enabled), or the account's one
1841 (if enabled) or fallback to the global forward format, which is always
1842 enabled (even if empty), and use the relevant quotemark */
1843 if (msginfo->folder && msginfo->folder->prefs &&
1844 msginfo->folder->prefs->forward_with_format) {
1845 qmark = msginfo->folder->prefs->forward_quotemark;
1846 body_fmt = msginfo->folder->prefs->forward_body_format;
1848 } else if (account->forward_with_format) {
1849 qmark = account->forward_quotemark;
1850 body_fmt = account->forward_body_format;
1853 qmark = prefs_common.fw_quotemark;
1854 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1855 body_fmt = gettext(prefs_common.fw_quotefmt);
1860 /* empty quotemark is not allowed */
1861 if (qmark == NULL || *qmark == '\0')
1864 compose_quote_fmt(compose, full_msginfo,
1865 body_fmt, qmark, body, FALSE, TRUE,
1866 _("The body of the \"Forward\" template has an error at line %d."));
1867 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1868 quote_fmt_reset_vartable();
1869 compose_attach_parts(compose, msginfo);
1871 procmsg_msginfo_free(full_msginfo);
1874 SIGNAL_BLOCK(textbuf);
1876 if (account->auto_sig)
1877 compose_insert_sig(compose, FALSE);
1879 compose_wrap_all(compose);
1882 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1883 gtkaspell_highlight_all(compose->gtkaspell);
1884 gtkaspell_unblock_check(compose->gtkaspell);
1886 SIGNAL_UNBLOCK(textbuf);
1888 cursor_pos = quote_fmt_get_cursor_pos();
1889 if (cursor_pos == -1)
1890 gtk_widget_grab_focus(compose->header_last->entry);
1892 gtk_widget_grab_focus(compose->text);
1894 if (!no_extedit && prefs_common.auto_exteditor)
1895 compose_exec_ext_editor(compose);
1898 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1899 gchar *folderidentifier;
1901 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1902 folderidentifier = folder_item_get_identifier(msginfo->folder);
1903 compose_set_save_to(compose, folderidentifier);
1904 g_free(folderidentifier);
1907 undo_unblock(compose->undostruct);
1909 compose->modified = FALSE;
1910 compose_set_title(compose);
1912 compose->updating = FALSE;
1913 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1914 SCROLL_TO_CURSOR(compose);
1916 if (compose->deferred_destroy) {
1917 compose_destroy(compose);
1921 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1926 #undef INSERT_FW_HEADER
1928 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1931 GtkTextView *textview;
1932 GtkTextBuffer *textbuf;
1936 gboolean single_mail = TRUE;
1938 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1940 if (g_slist_length(msginfo_list) > 1)
1941 single_mail = FALSE;
1943 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1944 if (((MsgInfo *)msginfo->data)->folder == NULL)
1947 /* guess account from first selected message */
1949 !(account = compose_guess_forward_account_from_msginfo
1950 (msginfo_list->data)))
1951 account = cur_account;
1953 cm_return_val_if_fail(account != NULL, NULL);
1955 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1956 if (msginfo->data) {
1957 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1958 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1962 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1963 g_warning("no msginfo_list");
1967 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1969 compose->updating = TRUE;
1971 /* override from name according to folder properties */
1972 if (msginfo_list->data) {
1973 MsgInfo *msginfo = msginfo_list->data;
1975 if (msginfo->folder && msginfo->folder->prefs &&
1976 msginfo->folder->prefs->forward_with_format &&
1977 msginfo->folder->prefs->forward_override_from_format &&
1978 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1983 /* decode \-escape sequences in the internal representation of the quote format */
1984 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1985 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1988 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1989 compose->gtkaspell);
1991 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1993 quote_fmt_scan_string(tmp);
1996 buf = quote_fmt_get_buffer();
1998 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2000 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2001 quote_fmt_reset_vartable();
2007 textview = GTK_TEXT_VIEW(compose->text);
2008 textbuf = gtk_text_view_get_buffer(textview);
2009 compose_create_tags(textview, compose);
2011 undo_block(compose->undostruct);
2012 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2013 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2015 if (!is_file_exist(msgfile))
2016 g_warning("%s: file not exist\n", msgfile);
2018 compose_attach_append(compose, msgfile, msgfile,
2019 "message/rfc822", NULL);
2024 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2025 if (info->subject && *info->subject) {
2026 gchar *buf, *buf2, *p;
2028 buf = p = g_strdup(info->subject);
2029 p += subject_get_prefix_length(p);
2030 memmove(buf, p, strlen(p) + 1);
2032 buf2 = g_strdup_printf("Fw: %s", buf);
2033 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2039 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2040 _("Fw: multiple emails"));
2043 SIGNAL_BLOCK(textbuf);
2045 if (account->auto_sig)
2046 compose_insert_sig(compose, FALSE);
2048 compose_wrap_all(compose);
2050 SIGNAL_UNBLOCK(textbuf);
2052 gtk_text_buffer_get_start_iter(textbuf, &iter);
2053 gtk_text_buffer_place_cursor(textbuf, &iter);
2055 gtk_widget_grab_focus(compose->header_last->entry);
2056 undo_unblock(compose->undostruct);
2057 compose->modified = FALSE;
2058 compose_set_title(compose);
2060 compose->updating = FALSE;
2061 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2062 SCROLL_TO_CURSOR(compose);
2064 if (compose->deferred_destroy) {
2065 compose_destroy(compose);
2069 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2074 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2076 GtkTextIter start = *iter;
2077 GtkTextIter end_iter;
2078 int start_pos = gtk_text_iter_get_offset(&start);
2080 if (!compose->account->sig_sep)
2083 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2084 start_pos+strlen(compose->account->sig_sep));
2086 /* check sig separator */
2087 str = gtk_text_iter_get_text(&start, &end_iter);
2088 if (!strcmp(str, compose->account->sig_sep)) {
2090 /* check end of line (\n) */
2091 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2092 start_pos+strlen(compose->account->sig_sep));
2093 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2094 start_pos+strlen(compose->account->sig_sep)+1);
2095 tmp = gtk_text_iter_get_text(&start, &end_iter);
2096 if (!strcmp(tmp,"\n")) {
2108 static void compose_colorize_signature(Compose *compose)
2110 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2112 GtkTextIter end_iter;
2113 gtk_text_buffer_get_start_iter(buffer, &iter);
2114 while (gtk_text_iter_forward_line(&iter))
2115 if (compose_is_sig_separator(compose, buffer, &iter)) {
2116 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2117 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2121 #define BLOCK_WRAP() { \
2122 prev_autowrap = compose->autowrap; \
2123 buffer = gtk_text_view_get_buffer( \
2124 GTK_TEXT_VIEW(compose->text)); \
2125 compose->autowrap = FALSE; \
2127 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2128 G_CALLBACK(compose_changed_cb), \
2130 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2131 G_CALLBACK(text_inserted), \
2134 #define UNBLOCK_WRAP() { \
2135 compose->autowrap = prev_autowrap; \
2136 if (compose->autowrap) { \
2137 gint old = compose->draft_timeout_tag; \
2138 compose->draft_timeout_tag = -2; \
2139 compose_wrap_all(compose); \
2140 compose->draft_timeout_tag = old; \
2143 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2144 G_CALLBACK(compose_changed_cb), \
2146 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2147 G_CALLBACK(text_inserted), \
2151 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2153 Compose *compose = NULL;
2154 PrefsAccount *account = NULL;
2155 GtkTextView *textview;
2156 GtkTextBuffer *textbuf;
2160 gchar buf[BUFFSIZE];
2161 gboolean use_signing = FALSE;
2162 gboolean use_encryption = FALSE;
2163 gchar *privacy_system = NULL;
2164 int priority = PRIORITY_NORMAL;
2165 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2166 gboolean autowrap = prefs_common.autowrap;
2167 gboolean autoindent = prefs_common.auto_indent;
2168 HeaderEntry *manual_headers = NULL;
2170 cm_return_val_if_fail(msginfo != NULL, NULL);
2171 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2173 if (compose_put_existing_to_front(msginfo)) {
2177 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2178 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2179 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2180 gchar queueheader_buf[BUFFSIZE];
2183 /* Select Account from queue headers */
2184 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2185 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2186 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2187 account = account_find_from_id(id);
2189 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2190 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2191 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2192 account = account_find_from_id(id);
2194 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2195 sizeof(queueheader_buf), "NAID:")) {
2196 id = atoi(&queueheader_buf[strlen("NAID:")]);
2197 account = account_find_from_id(id);
2199 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2200 sizeof(queueheader_buf), "MAID:")) {
2201 id = atoi(&queueheader_buf[strlen("MAID:")]);
2202 account = account_find_from_id(id);
2204 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2205 sizeof(queueheader_buf), "S:")) {
2206 account = account_find_from_address(queueheader_buf, FALSE);
2208 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2209 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2210 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2211 use_signing = param;
2214 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2215 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2216 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2217 use_signing = param;
2220 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2221 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2222 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2223 use_encryption = param;
2225 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2226 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2227 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2228 use_encryption = param;
2230 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2231 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2232 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2235 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2236 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2237 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2240 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2241 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2242 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2244 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2245 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2246 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2248 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2249 sizeof(queueheader_buf), "X-Priority: ")) {
2250 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2253 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2254 sizeof(queueheader_buf), "RMID:")) {
2255 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2256 if (tokens[0] && tokens[1] && tokens[2]) {
2257 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2258 if (orig_item != NULL) {
2259 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2264 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2265 sizeof(queueheader_buf), "FMID:")) {
2266 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2267 if (tokens[0] && tokens[1] && tokens[2]) {
2268 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2269 if (orig_item != NULL) {
2270 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2275 /* Get manual headers */
2276 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2277 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2278 if (*listmh != '\0') {
2279 debug_print("Got manual headers: %s\n", listmh);
2280 manual_headers = procheader_entries_from_str(listmh);
2285 account = msginfo->folder->folder->account;
2288 if (!account && prefs_common.reedit_account_autosel) {
2289 gchar from[BUFFSIZE];
2290 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2291 extract_address(from);
2292 account = account_find_from_address(from, FALSE);
2296 account = cur_account;
2298 cm_return_val_if_fail(account != NULL, NULL);
2300 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2302 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2303 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2304 compose->autowrap = autowrap;
2305 compose->replyinfo = replyinfo;
2306 compose->fwdinfo = fwdinfo;
2308 compose->updating = TRUE;
2309 compose->priority = priority;
2311 if (privacy_system != NULL) {
2312 compose->privacy_system = privacy_system;
2313 compose_use_signing(compose, use_signing);
2314 compose_use_encryption(compose, use_encryption);
2315 compose_update_privacy_system_menu_item(compose, FALSE);
2317 activate_privacy_system(compose, account, FALSE);
2320 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2322 compose_extract_original_charset(compose);
2324 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2325 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2326 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2327 gchar queueheader_buf[BUFFSIZE];
2329 /* Set message save folder */
2330 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2331 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2332 compose_set_save_to(compose, &queueheader_buf[4]);
2334 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2335 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2337 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2342 if (compose_parse_header(compose, msginfo) < 0) {
2343 compose->updating = FALSE;
2344 compose_destroy(compose);
2347 compose_reedit_set_entry(compose, msginfo);
2349 textview = GTK_TEXT_VIEW(compose->text);
2350 textbuf = gtk_text_view_get_buffer(textview);
2351 compose_create_tags(textview, compose);
2353 mark = gtk_text_buffer_get_insert(textbuf);
2354 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2356 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2357 G_CALLBACK(compose_changed_cb),
2360 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2361 fp = procmime_get_first_encrypted_text_content(msginfo);
2363 compose_force_encryption(compose, account, TRUE, NULL);
2366 fp = procmime_get_first_text_content(msginfo);
2369 g_warning("Can't get text part\n");
2373 gboolean prev_autowrap;
2374 GtkTextBuffer *buffer;
2376 while (fgets(buf, sizeof(buf), fp) != NULL) {
2378 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2384 compose_attach_parts(compose, msginfo);
2386 compose_colorize_signature(compose);
2388 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2389 G_CALLBACK(compose_changed_cb),
2392 if (manual_headers != NULL) {
2393 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2394 procheader_entries_free(manual_headers);
2395 compose->updating = FALSE;
2396 compose_destroy(compose);
2399 procheader_entries_free(manual_headers);
2402 gtk_widget_grab_focus(compose->text);
2404 if (prefs_common.auto_exteditor) {
2405 compose_exec_ext_editor(compose);
2407 compose->modified = FALSE;
2408 compose_set_title(compose);
2410 compose->updating = FALSE;
2411 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2412 SCROLL_TO_CURSOR(compose);
2414 if (compose->deferred_destroy) {
2415 compose_destroy(compose);
2419 compose->sig_str = account_get_signature_str(compose->account);
2421 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2426 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2433 cm_return_val_if_fail(msginfo != NULL, NULL);
2436 account = account_get_reply_account(msginfo,
2437 prefs_common.reply_account_autosel);
2438 cm_return_val_if_fail(account != NULL, NULL);
2440 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2442 compose->updating = TRUE;
2444 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2445 compose->replyinfo = NULL;
2446 compose->fwdinfo = NULL;
2448 compose_show_first_last_header(compose, TRUE);
2450 gtk_widget_grab_focus(compose->header_last->entry);
2452 filename = procmsg_get_message_file(msginfo);
2454 if (filename == NULL) {
2455 compose->updating = FALSE;
2456 compose_destroy(compose);
2461 compose->redirect_filename = filename;
2463 /* Set save folder */
2464 item = msginfo->folder;
2465 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2466 gchar *folderidentifier;
2468 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2469 folderidentifier = folder_item_get_identifier(item);
2470 compose_set_save_to(compose, folderidentifier);
2471 g_free(folderidentifier);
2474 compose_attach_parts(compose, msginfo);
2476 if (msginfo->subject)
2477 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2479 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2481 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2482 _("The body of the \"Redirect\" template has an error at line %d."));
2483 quote_fmt_reset_vartable();
2484 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2486 compose_colorize_signature(compose);
2489 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2490 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2491 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2493 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2494 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2495 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2496 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2497 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2498 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2499 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2500 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2501 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2503 if (compose->toolbar->draft_btn)
2504 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2505 if (compose->toolbar->insert_btn)
2506 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2507 if (compose->toolbar->attach_btn)
2508 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2509 if (compose->toolbar->sig_btn)
2510 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2511 if (compose->toolbar->exteditor_btn)
2512 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2513 if (compose->toolbar->linewrap_current_btn)
2514 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2515 if (compose->toolbar->linewrap_all_btn)
2516 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2518 compose->modified = FALSE;
2519 compose_set_title(compose);
2520 compose->updating = FALSE;
2521 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2522 SCROLL_TO_CURSOR(compose);
2524 if (compose->deferred_destroy) {
2525 compose_destroy(compose);
2529 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2534 GList *compose_get_compose_list(void)
2536 return compose_list;
2539 void compose_entry_append(Compose *compose, const gchar *address,
2540 ComposeEntryType type, ComposePrefType pref_type)
2542 const gchar *header;
2544 gboolean in_quote = FALSE;
2545 if (!address || *address == '\0') return;
2552 header = N_("Bcc:");
2554 case COMPOSE_REPLYTO:
2555 header = N_("Reply-To:");
2557 case COMPOSE_NEWSGROUPS:
2558 header = N_("Newsgroups:");
2560 case COMPOSE_FOLLOWUPTO:
2561 header = N_( "Followup-To:");
2563 case COMPOSE_INREPLYTO:
2564 header = N_( "In-Reply-To:");
2571 header = prefs_common_translated_header_name(header);
2573 cur = begin = (gchar *)address;
2575 /* we separate the line by commas, but not if we're inside a quoted
2577 while (*cur != '\0') {
2579 in_quote = !in_quote;
2580 if (*cur == ',' && !in_quote) {
2581 gchar *tmp = g_strdup(begin);
2583 tmp[cur-begin]='\0';
2586 while (*tmp == ' ' || *tmp == '\t')
2588 compose_add_header_entry(compose, header, tmp, pref_type);
2595 gchar *tmp = g_strdup(begin);
2597 tmp[cur-begin]='\0';
2598 while (*tmp == ' ' || *tmp == '\t')
2600 compose_add_header_entry(compose, header, tmp, pref_type);
2605 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2607 #if !GTK_CHECK_VERSION(3, 0, 0)
2608 static GdkColor yellow;
2609 static GdkColor black;
2610 static gboolean yellow_initialised = FALSE;
2612 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2613 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2618 #if !GTK_CHECK_VERSION(3, 0, 0)
2619 if (!yellow_initialised) {
2620 gdk_color_parse("#f5f6be", &yellow);
2621 gdk_color_parse("#000000", &black);
2622 yellow_initialised = gdk_colormap_alloc_color(
2623 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2624 yellow_initialised &= gdk_colormap_alloc_color(
2625 gdk_colormap_get_system(), &black, FALSE, TRUE);
2629 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2630 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2631 if (gtk_entry_get_text(entry) &&
2632 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2633 #if !GTK_CHECK_VERSION(3, 0, 0)
2634 if (yellow_initialised) {
2636 gtk_widget_modify_base(
2637 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2638 GTK_STATE_NORMAL, &yellow);
2639 gtk_widget_modify_text(
2640 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2641 GTK_STATE_NORMAL, &black);
2642 #if !GTK_CHECK_VERSION(3, 0, 0)
2649 void compose_toolbar_cb(gint action, gpointer data)
2651 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2652 Compose *compose = (Compose*)toolbar_item->parent;
2654 cm_return_if_fail(compose != NULL);
2658 compose_send_cb(NULL, compose);
2661 compose_send_later_cb(NULL, compose);
2664 compose_draft(compose, COMPOSE_QUIT_EDITING);
2667 compose_insert_file_cb(NULL, compose);
2670 compose_attach_cb(NULL, compose);
2673 compose_insert_sig(compose, FALSE);
2676 compose_insert_sig(compose, TRUE);
2679 compose_ext_editor_cb(NULL, compose);
2681 case A_LINEWRAP_CURRENT:
2682 compose_beautify_paragraph(compose, NULL, TRUE);
2684 case A_LINEWRAP_ALL:
2685 compose_wrap_all_full(compose, TRUE);
2688 compose_address_cb(NULL, compose);
2691 case A_CHECK_SPELLING:
2692 compose_check_all(NULL, compose);
2700 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2705 gchar *subject = NULL;
2709 gchar **attach = NULL;
2710 gchar *inreplyto = NULL;
2711 MailField mfield = NO_FIELD_PRESENT;
2713 /* get mailto parts but skip from */
2714 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2717 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2718 mfield = TO_FIELD_PRESENT;
2721 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2723 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2725 if (!g_utf8_validate (subject, -1, NULL)) {
2726 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2727 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2730 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2732 mfield = SUBJECT_FIELD_PRESENT;
2735 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2736 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2739 gboolean prev_autowrap = compose->autowrap;
2741 compose->autowrap = FALSE;
2743 mark = gtk_text_buffer_get_insert(buffer);
2744 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2746 if (!g_utf8_validate (body, -1, NULL)) {
2747 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2748 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2751 gtk_text_buffer_insert(buffer, &iter, body, -1);
2753 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2755 compose->autowrap = prev_autowrap;
2756 if (compose->autowrap)
2757 compose_wrap_all(compose);
2758 mfield = BODY_FIELD_PRESENT;
2762 gint i = 0, att = 0;
2763 gchar *warn_files = NULL;
2764 while (attach[i] != NULL) {
2765 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2766 if (utf8_filename) {
2767 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2768 gchar *tmp = g_strdup_printf("%s%s\n",
2769 warn_files?warn_files:"",
2775 g_free(utf8_filename);
2777 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2782 alertpanel_notice(ngettext(
2783 "The following file has been attached: \n%s",
2784 "The following files have been attached: \n%s", att), warn_files);
2789 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2802 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2804 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2805 {"Cc:", NULL, TRUE},
2806 {"References:", NULL, FALSE},
2807 {"Bcc:", NULL, TRUE},
2808 {"Newsgroups:", NULL, TRUE},
2809 {"Followup-To:", NULL, TRUE},
2810 {"List-Post:", NULL, FALSE},
2811 {"X-Priority:", NULL, FALSE},
2812 {NULL, NULL, FALSE}};
2828 cm_return_val_if_fail(msginfo != NULL, -1);
2830 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2831 procheader_get_header_fields(fp, hentry);
2834 if (hentry[H_REPLY_TO].body != NULL) {
2835 if (hentry[H_REPLY_TO].body[0] != '\0') {
2837 conv_unmime_header(hentry[H_REPLY_TO].body,
2840 g_free(hentry[H_REPLY_TO].body);
2841 hentry[H_REPLY_TO].body = NULL;
2843 if (hentry[H_CC].body != NULL) {
2844 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2845 g_free(hentry[H_CC].body);
2846 hentry[H_CC].body = NULL;
2848 if (hentry[H_REFERENCES].body != NULL) {
2849 if (compose->mode == COMPOSE_REEDIT)
2850 compose->references = hentry[H_REFERENCES].body;
2852 compose->references = compose_parse_references
2853 (hentry[H_REFERENCES].body, msginfo->msgid);
2854 g_free(hentry[H_REFERENCES].body);
2856 hentry[H_REFERENCES].body = NULL;
2858 if (hentry[H_BCC].body != NULL) {
2859 if (compose->mode == COMPOSE_REEDIT)
2861 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2862 g_free(hentry[H_BCC].body);
2863 hentry[H_BCC].body = NULL;
2865 if (hentry[H_NEWSGROUPS].body != NULL) {
2866 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2867 hentry[H_NEWSGROUPS].body = NULL;
2869 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2870 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2871 compose->followup_to =
2872 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2875 g_free(hentry[H_FOLLOWUP_TO].body);
2876 hentry[H_FOLLOWUP_TO].body = NULL;
2878 if (hentry[H_LIST_POST].body != NULL) {
2879 gchar *to = NULL, *start = NULL;
2881 extract_address(hentry[H_LIST_POST].body);
2882 if (hentry[H_LIST_POST].body[0] != '\0') {
2883 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2885 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2886 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2889 g_free(compose->ml_post);
2890 compose->ml_post = to;
2893 g_free(hentry[H_LIST_POST].body);
2894 hentry[H_LIST_POST].body = NULL;
2897 /* CLAWS - X-Priority */
2898 if (compose->mode == COMPOSE_REEDIT)
2899 if (hentry[H_X_PRIORITY].body != NULL) {
2902 priority = atoi(hentry[H_X_PRIORITY].body);
2903 g_free(hentry[H_X_PRIORITY].body);
2905 hentry[H_X_PRIORITY].body = NULL;
2907 if (priority < PRIORITY_HIGHEST ||
2908 priority > PRIORITY_LOWEST)
2909 priority = PRIORITY_NORMAL;
2911 compose->priority = priority;
2914 if (compose->mode == COMPOSE_REEDIT) {
2915 if (msginfo->inreplyto && *msginfo->inreplyto)
2916 compose->inreplyto = g_strdup(msginfo->inreplyto);
2920 if (msginfo->msgid && *msginfo->msgid)
2921 compose->inreplyto = g_strdup(msginfo->msgid);
2923 if (!compose->references) {
2924 if (msginfo->msgid && *msginfo->msgid) {
2925 if (msginfo->inreplyto && *msginfo->inreplyto)
2926 compose->references =
2927 g_strdup_printf("<%s>\n\t<%s>",
2931 compose->references =
2932 g_strconcat("<", msginfo->msgid, ">",
2934 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2935 compose->references =
2936 g_strconcat("<", msginfo->inreplyto, ">",
2944 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2949 cm_return_val_if_fail(msginfo != NULL, -1);
2951 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2952 procheader_get_header_fields(fp, entries);
2956 while (he != NULL && he->name != NULL) {
2958 GtkListStore *model = NULL;
2960 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2961 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2962 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2963 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2964 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2971 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2973 GSList *ref_id_list, *cur;
2977 ref_id_list = references_list_append(NULL, ref);
2978 if (!ref_id_list) return NULL;
2979 if (msgid && *msgid)
2980 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2985 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2986 /* "<" + Message-ID + ">" + CR+LF+TAB */
2987 len += strlen((gchar *)cur->data) + 5;
2989 if (len > MAX_REFERENCES_LEN) {
2990 /* remove second message-ID */
2991 if (ref_id_list && ref_id_list->next &&
2992 ref_id_list->next->next) {
2993 g_free(ref_id_list->next->data);
2994 ref_id_list = g_slist_remove
2995 (ref_id_list, ref_id_list->next->data);
2997 slist_free_strings_full(ref_id_list);
3004 new_ref = g_string_new("");
3005 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3006 if (new_ref->len > 0)
3007 g_string_append(new_ref, "\n\t");
3008 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3011 slist_free_strings_full(ref_id_list);
3013 new_ref_str = new_ref->str;
3014 g_string_free(new_ref, FALSE);
3019 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3020 const gchar *fmt, const gchar *qmark,
3021 const gchar *body, gboolean rewrap,
3022 gboolean need_unescape,
3023 const gchar *err_msg)
3025 MsgInfo* dummyinfo = NULL;
3026 gchar *quote_str = NULL;
3028 gboolean prev_autowrap;
3029 const gchar *trimmed_body = body;
3030 gint cursor_pos = -1;
3031 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3032 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3037 SIGNAL_BLOCK(buffer);
3040 dummyinfo = compose_msginfo_new_from_compose(compose);
3041 msginfo = dummyinfo;
3044 if (qmark != NULL) {
3046 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3047 compose->gtkaspell);
3049 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3051 quote_fmt_scan_string(qmark);
3054 buf = quote_fmt_get_buffer();
3056 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3058 Xstrdup_a(quote_str, buf, goto error)
3061 if (fmt && *fmt != '\0') {
3064 while (*trimmed_body == '\n')
3068 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3069 compose->gtkaspell);
3071 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3073 if (need_unescape) {
3076 /* decode \-escape sequences in the internal representation of the quote format */
3077 tmp = g_malloc(strlen(fmt)+1);
3078 pref_get_unescaped_pref(tmp, fmt);
3079 quote_fmt_scan_string(tmp);
3083 quote_fmt_scan_string(fmt);
3087 buf = quote_fmt_get_buffer();
3089 gint line = quote_fmt_get_line();
3090 alertpanel_error(err_msg, line);
3096 prev_autowrap = compose->autowrap;
3097 compose->autowrap = FALSE;
3099 mark = gtk_text_buffer_get_insert(buffer);
3100 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3101 if (g_utf8_validate(buf, -1, NULL)) {
3102 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3104 gchar *tmpout = NULL;
3105 tmpout = conv_codeset_strdup
3106 (buf, conv_get_locale_charset_str_no_utf8(),
3108 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3110 tmpout = g_malloc(strlen(buf)*2+1);
3111 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3113 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3117 cursor_pos = quote_fmt_get_cursor_pos();
3118 if (cursor_pos == -1)
3119 cursor_pos = gtk_text_iter_get_offset(&iter);
3120 compose->set_cursor_pos = cursor_pos;
3122 gtk_text_buffer_get_start_iter(buffer, &iter);
3123 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3124 gtk_text_buffer_place_cursor(buffer, &iter);
3126 compose->autowrap = prev_autowrap;
3127 if (compose->autowrap && rewrap)
3128 compose_wrap_all(compose);
3135 SIGNAL_UNBLOCK(buffer);
3137 procmsg_msginfo_free( dummyinfo );
3142 /* if ml_post is of type addr@host and from is of type
3143 * addr-anything@host, return TRUE
3145 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3147 gchar *left_ml = NULL;
3148 gchar *right_ml = NULL;
3149 gchar *left_from = NULL;
3150 gchar *right_from = NULL;
3151 gboolean result = FALSE;
3153 if (!ml_post || !from)
3156 left_ml = g_strdup(ml_post);
3157 if (strstr(left_ml, "@")) {
3158 right_ml = strstr(left_ml, "@")+1;
3159 *(strstr(left_ml, "@")) = '\0';
3162 left_from = g_strdup(from);
3163 if (strstr(left_from, "@")) {
3164 right_from = strstr(left_from, "@")+1;
3165 *(strstr(left_from, "@")) = '\0';
3168 if (left_ml && left_from && right_ml && right_from
3169 && !strncmp(left_from, left_ml, strlen(left_ml))
3170 && !strcmp(right_from, right_ml)) {
3179 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3180 gboolean respect_default_to)
3184 if (!folder || !folder->prefs)
3187 if (respect_default_to && folder->prefs->enable_default_to) {
3188 compose_entry_append(compose, folder->prefs->default_to,
3189 COMPOSE_TO, PREF_FOLDER);
3190 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3192 if (folder->prefs->enable_default_cc)
3193 compose_entry_append(compose, folder->prefs->default_cc,
3194 COMPOSE_CC, PREF_FOLDER);
3195 if (folder->prefs->enable_default_bcc)
3196 compose_entry_append(compose, folder->prefs->default_bcc,
3197 COMPOSE_BCC, PREF_FOLDER);
3198 if (folder->prefs->enable_default_replyto)
3199 compose_entry_append(compose, folder->prefs->default_replyto,
3200 COMPOSE_REPLYTO, PREF_FOLDER);
3203 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3208 if (!compose || !msginfo)
3211 if (msginfo->subject && *msginfo->subject) {
3212 buf = p = g_strdup(msginfo->subject);
3213 p += subject_get_prefix_length(p);
3214 memmove(buf, p, strlen(p) + 1);
3216 buf2 = g_strdup_printf("Re: %s", buf);
3217 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3222 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3225 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3226 gboolean to_all, gboolean to_ml,
3228 gboolean followup_and_reply_to)
3230 GSList *cc_list = NULL;
3233 gchar *replyto = NULL;
3234 gchar *ac_email = NULL;
3236 gboolean reply_to_ml = FALSE;
3237 gboolean default_reply_to = FALSE;
3239 cm_return_if_fail(compose->account != NULL);
3240 cm_return_if_fail(msginfo != NULL);
3242 reply_to_ml = to_ml && compose->ml_post;
3244 default_reply_to = msginfo->folder &&
3245 msginfo->folder->prefs->enable_default_reply_to;
3247 if (compose->account->protocol != A_NNTP) {
3248 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3250 if (reply_to_ml && !default_reply_to) {
3252 gboolean is_subscr = is_subscription(compose->ml_post,
3255 /* normal answer to ml post with a reply-to */
3256 compose_entry_append(compose,
3258 COMPOSE_TO, PREF_ML);
3259 if (compose->replyto)
3260 compose_entry_append(compose,
3262 COMPOSE_CC, PREF_ML);
3264 /* answer to subscription confirmation */
3265 if (compose->replyto)
3266 compose_entry_append(compose,
3268 COMPOSE_TO, PREF_ML);
3269 else if (msginfo->from)
3270 compose_entry_append(compose,
3272 COMPOSE_TO, PREF_ML);
3275 else if (!(to_all || to_sender) && default_reply_to) {
3276 compose_entry_append(compose,
3277 msginfo->folder->prefs->default_reply_to,
3278 COMPOSE_TO, PREF_FOLDER);
3279 compose_entry_mark_default_to(compose,
3280 msginfo->folder->prefs->default_reply_to);
3285 Xstrdup_a(tmp1, msginfo->from, return);
3286 extract_address(tmp1);
3287 if (to_all || to_sender ||
3288 !account_find_from_address(tmp1, FALSE))
3289 compose_entry_append(compose,
3290 (compose->replyto && !to_sender)
3291 ? compose->replyto :
3292 msginfo->from ? msginfo->from : "",
3293 COMPOSE_TO, PREF_NONE);
3294 else if (!to_all && !to_sender) {
3295 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3296 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3297 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3298 if (compose->replyto) {
3299 compose_entry_append(compose,
3301 COMPOSE_TO, PREF_NONE);
3303 compose_entry_append(compose,
3304 msginfo->from ? msginfo->from : "",
3305 COMPOSE_TO, PREF_NONE);
3308 /* replying to own mail, use original recp */
3309 compose_entry_append(compose,
3310 msginfo->to ? msginfo->to : "",
3311 COMPOSE_TO, PREF_NONE);
3312 compose_entry_append(compose,
3313 msginfo->cc ? msginfo->cc : "",
3314 COMPOSE_CC, PREF_NONE);
3319 if (to_sender || (compose->followup_to &&
3320 !strncmp(compose->followup_to, "poster", 6)))
3321 compose_entry_append
3323 (compose->replyto ? compose->replyto :
3324 msginfo->from ? msginfo->from : ""),
3325 COMPOSE_TO, PREF_NONE);
3327 else if (followup_and_reply_to || to_all) {
3328 compose_entry_append
3330 (compose->replyto ? compose->replyto :
3331 msginfo->from ? msginfo->from : ""),
3332 COMPOSE_TO, PREF_NONE);
3334 compose_entry_append
3336 compose->followup_to ? compose->followup_to :
3337 compose->newsgroups ? compose->newsgroups : "",
3338 COMPOSE_NEWSGROUPS, PREF_NONE);
3341 compose_entry_append
3343 compose->followup_to ? compose->followup_to :
3344 compose->newsgroups ? compose->newsgroups : "",
3345 COMPOSE_NEWSGROUPS, PREF_NONE);
3347 compose_reply_set_subject(compose, msginfo);
3349 if (to_ml && compose->ml_post) return;
3350 if (!to_all || compose->account->protocol == A_NNTP) return;
3352 if (compose->replyto) {
3353 Xstrdup_a(replyto, compose->replyto, return);
3354 extract_address(replyto);
3356 if (msginfo->from) {
3357 Xstrdup_a(from, msginfo->from, return);
3358 extract_address(from);
3361 if (replyto && from)
3362 cc_list = address_list_append_with_comments(cc_list, from);
3363 if (to_all && msginfo->folder &&
3364 msginfo->folder->prefs->enable_default_reply_to)
3365 cc_list = address_list_append_with_comments(cc_list,
3366 msginfo->folder->prefs->default_reply_to);
3367 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3368 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3370 ac_email = g_utf8_strdown(compose->account->address, -1);
3373 for (cur = cc_list; cur != NULL; cur = cur->next) {
3374 gchar *addr = g_utf8_strdown(cur->data, -1);
3375 extract_address(addr);
3377 if (strcmp(ac_email, addr))
3378 compose_entry_append(compose, (gchar *)cur->data,
3379 COMPOSE_CC, PREF_NONE);
3381 debug_print("Cc address same as compose account's, ignoring\n");
3386 slist_free_strings_full(cc_list);
3392 #define SET_ENTRY(entry, str) \
3395 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3398 #define SET_ADDRESS(type, str) \
3401 compose_entry_append(compose, str, type, PREF_NONE); \
3404 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3406 cm_return_if_fail(msginfo != NULL);
3408 SET_ENTRY(subject_entry, msginfo->subject);
3409 SET_ENTRY(from_name, msginfo->from);
3410 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3411 SET_ADDRESS(COMPOSE_CC, compose->cc);
3412 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3413 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3414 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3415 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3417 compose_update_priority_menu_item(compose);
3418 compose_update_privacy_system_menu_item(compose, FALSE);
3419 compose_show_first_last_header(compose, TRUE);
3425 static void compose_insert_sig(Compose *compose, gboolean replace)
3427 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3428 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3430 GtkTextIter iter, iter_end;
3431 gint cur_pos, ins_pos;
3432 gboolean prev_autowrap;
3433 gboolean found = FALSE;
3434 gboolean exists = FALSE;
3436 cm_return_if_fail(compose->account != NULL);
3440 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3441 G_CALLBACK(compose_changed_cb),
3444 mark = gtk_text_buffer_get_insert(buffer);
3445 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3446 cur_pos = gtk_text_iter_get_offset (&iter);
3449 gtk_text_buffer_get_end_iter(buffer, &iter);
3451 exists = (compose->sig_str != NULL);
3454 GtkTextIter first_iter, start_iter, end_iter;
3456 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3458 if (!exists || compose->sig_str[0] == '\0')
3461 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3462 compose->signature_tag);
3465 /* include previous \n\n */
3466 gtk_text_iter_backward_chars(&first_iter, 1);
3467 start_iter = first_iter;
3468 end_iter = first_iter;
3470 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3471 compose->signature_tag);
3472 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3473 compose->signature_tag);
3475 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3481 g_free(compose->sig_str);
3482 compose->sig_str = account_get_signature_str(compose->account);
3484 cur_pos = gtk_text_iter_get_offset(&iter);
3486 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3487 g_free(compose->sig_str);
3488 compose->sig_str = NULL;
3490 if (compose->sig_inserted == FALSE)
3491 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3492 compose->sig_inserted = TRUE;
3494 cur_pos = gtk_text_iter_get_offset(&iter);
3495 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3497 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3498 gtk_text_iter_forward_chars(&iter, 1);
3499 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3500 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3502 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3503 cur_pos = gtk_text_buffer_get_char_count (buffer);
3506 /* put the cursor where it should be
3507 * either where the quote_fmt says, either where it was */
3508 if (compose->set_cursor_pos < 0)
3509 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3511 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3512 compose->set_cursor_pos);
3514 compose->set_cursor_pos = -1;
3515 gtk_text_buffer_place_cursor(buffer, &iter);
3516 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3517 G_CALLBACK(compose_changed_cb),
3523 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3526 GtkTextBuffer *buffer;
3529 const gchar *cur_encoding;
3530 gchar buf[BUFFSIZE];
3533 gboolean prev_autowrap;
3534 gboolean badtxt = FALSE;
3535 struct stat file_stat;
3537 GString *file_contents = NULL;
3539 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3541 /* get the size of the file we are about to insert */
3542 ret = g_stat(file, &file_stat);
3544 gchar *shortfile = g_path_get_basename(file);
3545 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3547 return COMPOSE_INSERT_NO_FILE;
3548 } else if (prefs_common.warn_large_insert == TRUE) {
3550 /* ask user for confirmation if the file is large */
3551 if (prefs_common.warn_large_insert_size < 0 ||
3552 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3556 msg = g_strdup_printf(_("You are about to insert a file of %s "
3557 "in the message body. Are you sure you want to do that?"),
3558 to_human_readable(file_stat.st_size));
3559 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3560 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3563 /* do we ask for confirmation next time? */
3564 if (aval & G_ALERTDISABLE) {
3565 /* no confirmation next time, disable feature in preferences */
3566 aval &= ~G_ALERTDISABLE;
3567 prefs_common.warn_large_insert = FALSE;
3570 /* abort file insertion if user canceled action */
3571 if (aval != G_ALERTALTERNATE) {
3572 return COMPOSE_INSERT_NO_FILE;
3578 if ((fp = g_fopen(file, "rb")) == NULL) {
3579 FILE_OP_ERROR(file, "fopen");
3580 return COMPOSE_INSERT_READ_ERROR;
3583 prev_autowrap = compose->autowrap;
3584 compose->autowrap = FALSE;
3586 text = GTK_TEXT_VIEW(compose->text);
3587 buffer = gtk_text_view_get_buffer(text);
3588 mark = gtk_text_buffer_get_insert(buffer);
3589 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3591 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3592 G_CALLBACK(text_inserted),
3595 cur_encoding = conv_get_locale_charset_str_no_utf8();
3597 file_contents = g_string_new("");
3598 while (fgets(buf, sizeof(buf), fp) != NULL) {
3601 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3602 str = g_strdup(buf);
3604 str = conv_codeset_strdup
3605 (buf, cur_encoding, CS_INTERNAL);
3608 /* strip <CR> if DOS/Windows file,
3609 replace <CR> with <LF> if Macintosh file. */
3612 if (len > 0 && str[len - 1] != '\n') {
3614 if (str[len] == '\r') str[len] = '\n';
3617 file_contents = g_string_append(file_contents, str);
3621 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3622 g_string_free(file_contents, TRUE);
3624 compose_changed_cb(NULL, compose);
3625 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3626 G_CALLBACK(text_inserted),
3628 compose->autowrap = prev_autowrap;
3629 if (compose->autowrap)
3630 compose_wrap_all(compose);
3635 return COMPOSE_INSERT_INVALID_CHARACTER;
3637 return COMPOSE_INSERT_SUCCESS;
3640 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3641 const gchar *filename,
3642 const gchar *content_type,
3643 const gchar *charset)
3651 GtkListStore *store;
3653 gboolean has_binary = FALSE;
3655 if (!is_file_exist(file)) {
3656 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3657 gboolean result = FALSE;
3658 if (file_from_uri && is_file_exist(file_from_uri)) {
3659 result = compose_attach_append(
3660 compose, file_from_uri,
3661 filename, content_type,
3664 g_free(file_from_uri);
3667 alertpanel_error("File %s doesn't exist\n", filename);
3670 if ((size = get_file_size(file)) < 0) {
3671 alertpanel_error("Can't get file size of %s\n", filename);
3675 alertpanel_error(_("File %s is empty."), filename);
3678 if ((fp = g_fopen(file, "rb")) == NULL) {
3679 alertpanel_error(_("Can't read %s."), filename);
3684 ainfo = g_new0(AttachInfo, 1);
3685 auto_ainfo = g_auto_pointer_new_with_free
3686 (ainfo, (GFreeFunc) compose_attach_info_free);
3687 ainfo->file = g_strdup(file);
3690 ainfo->content_type = g_strdup(content_type);
3691 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3693 MsgFlags flags = {0, 0};
3695 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3696 ainfo->encoding = ENC_7BIT;
3698 ainfo->encoding = ENC_8BIT;
3700 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3701 if (msginfo && msginfo->subject)
3702 name = g_strdup(msginfo->subject);
3704 name = g_path_get_basename(filename ? filename : file);
3706 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3708 procmsg_msginfo_free(msginfo);
3710 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3711 ainfo->charset = g_strdup(charset);
3712 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3714 ainfo->encoding = ENC_BASE64;
3716 name = g_path_get_basename(filename ? filename : file);
3717 ainfo->name = g_strdup(name);
3721 ainfo->content_type = procmime_get_mime_type(file);
3722 if (!ainfo->content_type) {
3723 ainfo->content_type =
3724 g_strdup("application/octet-stream");
3725 ainfo->encoding = ENC_BASE64;
3726 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3728 procmime_get_encoding_for_text_file(file, &has_binary);
3730 ainfo->encoding = ENC_BASE64;
3731 name = g_path_get_basename(filename ? filename : file);
3732 ainfo->name = g_strdup(name);
3736 if (ainfo->name != NULL
3737 && !strcmp(ainfo->name, ".")) {
3738 g_free(ainfo->name);
3742 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3743 g_free(ainfo->content_type);
3744 ainfo->content_type = g_strdup("application/octet-stream");
3745 g_free(ainfo->charset);
3746 ainfo->charset = NULL;
3749 ainfo->size = (goffset)size;
3750 size_text = to_human_readable((goffset)size);
3752 store = GTK_LIST_STORE(gtk_tree_view_get_model
3753 (GTK_TREE_VIEW(compose->attach_clist)));
3755 gtk_list_store_append(store, &iter);
3756 gtk_list_store_set(store, &iter,
3757 COL_MIMETYPE, ainfo->content_type,
3758 COL_SIZE, size_text,
3759 COL_NAME, ainfo->name,
3760 COL_CHARSET, ainfo->charset,
3762 COL_AUTODATA, auto_ainfo,
3765 g_auto_pointer_free(auto_ainfo);
3766 compose_attach_update_label(compose);
3770 static void compose_use_signing(Compose *compose, gboolean use_signing)
3772 compose->use_signing = use_signing;
3773 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3776 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3778 compose->use_encryption = use_encryption;
3779 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3782 #define NEXT_PART_NOT_CHILD(info) \
3784 node = info->node; \
3785 while (node->children) \
3786 node = g_node_last_child(node); \
3787 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3790 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3794 MimeInfo *firsttext = NULL;
3795 MimeInfo *encrypted = NULL;
3798 const gchar *partname = NULL;
3800 mimeinfo = procmime_scan_message(msginfo);
3801 if (!mimeinfo) return;
3803 if (mimeinfo->node->children == NULL) {
3804 procmime_mimeinfo_free_all(mimeinfo);
3808 /* find first content part */
3809 child = (MimeInfo *) mimeinfo->node->children->data;
3810 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3811 child = (MimeInfo *)child->node->children->data;
3814 if (child->type == MIMETYPE_TEXT) {
3816 debug_print("First text part found\n");
3817 } else if (compose->mode == COMPOSE_REEDIT &&
3818 child->type == MIMETYPE_APPLICATION &&
3819 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3820 encrypted = (MimeInfo *)child->node->parent->data;
3823 child = (MimeInfo *) mimeinfo->node->children->data;
3824 while (child != NULL) {
3827 if (child == encrypted) {
3828 /* skip this part of tree */
3829 NEXT_PART_NOT_CHILD(child);
3833 if (child->type == MIMETYPE_MULTIPART) {
3834 /* get the actual content */
3835 child = procmime_mimeinfo_next(child);
3839 if (child == firsttext) {
3840 child = procmime_mimeinfo_next(child);
3844 outfile = procmime_get_tmp_file_name(child);
3845 if ((err = procmime_get_part(outfile, child)) < 0)
3846 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3848 gchar *content_type;
3850 content_type = procmime_get_content_type_str(child->type, child->subtype);
3852 /* if we meet a pgp signature, we don't attach it, but
3853 * we force signing. */
3854 if ((strcmp(content_type, "application/pgp-signature") &&
3855 strcmp(content_type, "application/pkcs7-signature") &&
3856 strcmp(content_type, "application/x-pkcs7-signature"))
3857 || compose->mode == COMPOSE_REDIRECT) {
3858 partname = procmime_mimeinfo_get_parameter(child, "filename");
3859 if (partname == NULL)
3860 partname = procmime_mimeinfo_get_parameter(child, "name");
3861 if (partname == NULL)
3863 compose_attach_append(compose, outfile,
3864 partname, content_type,
3865 procmime_mimeinfo_get_parameter(child, "charset"));
3867 compose_force_signing(compose, compose->account, NULL);
3869 g_free(content_type);
3872 NEXT_PART_NOT_CHILD(child);
3874 procmime_mimeinfo_free_all(mimeinfo);
3877 #undef NEXT_PART_NOT_CHILD
3882 WAIT_FOR_INDENT_CHAR,
3883 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3886 /* return indent length, we allow:
3887 indent characters followed by indent characters or spaces/tabs,
3888 alphabets and numbers immediately followed by indent characters,
3889 and the repeating sequences of the above
3890 If quote ends with multiple spaces, only the first one is included. */
3891 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3892 const GtkTextIter *start, gint *len)
3894 GtkTextIter iter = *start;
3898 IndentState state = WAIT_FOR_INDENT_CHAR;
3901 gint alnum_count = 0;
3902 gint space_count = 0;
3905 if (prefs_common.quote_chars == NULL) {
3909 while (!gtk_text_iter_ends_line(&iter)) {
3910 wc = gtk_text_iter_get_char(&iter);
3911 if (g_unichar_iswide(wc))
3913 clen = g_unichar_to_utf8(wc, ch);
3917 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3918 is_space = g_unichar_isspace(wc);
3920 if (state == WAIT_FOR_INDENT_CHAR) {
3921 if (!is_indent && !g_unichar_isalnum(wc))
3924 quote_len += alnum_count + space_count + 1;
3925 alnum_count = space_count = 0;
3926 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3929 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3930 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3934 else if (is_indent) {
3935 quote_len += alnum_count + space_count + 1;
3936 alnum_count = space_count = 0;
3939 state = WAIT_FOR_INDENT_CHAR;
3943 gtk_text_iter_forward_char(&iter);
3946 if (quote_len > 0 && space_count > 0)
3952 if (quote_len > 0) {
3954 gtk_text_iter_forward_chars(&iter, quote_len);
3955 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3961 /* return >0 if the line is itemized */
3962 static int compose_itemized_length(GtkTextBuffer *buffer,
3963 const GtkTextIter *start)
3965 GtkTextIter iter = *start;
3970 if (gtk_text_iter_ends_line(&iter))
3975 wc = gtk_text_iter_get_char(&iter);
3976 if (!g_unichar_isspace(wc))
3978 gtk_text_iter_forward_char(&iter);
3979 if (gtk_text_iter_ends_line(&iter))
3983 clen = g_unichar_to_utf8(wc, ch);
3987 if (!strchr("*-+", ch[0]))
3990 gtk_text_iter_forward_char(&iter);
3991 if (gtk_text_iter_ends_line(&iter))
3993 wc = gtk_text_iter_get_char(&iter);
3994 if (g_unichar_isspace(wc)) {
4000 /* return the string at the start of the itemization */
4001 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4002 const GtkTextIter *start)
4004 GtkTextIter iter = *start;
4007 GString *item_chars = g_string_new("");
4010 if (gtk_text_iter_ends_line(&iter))
4015 wc = gtk_text_iter_get_char(&iter);
4016 if (!g_unichar_isspace(wc))
4018 gtk_text_iter_forward_char(&iter);
4019 if (gtk_text_iter_ends_line(&iter))
4021 g_string_append_unichar(item_chars, wc);
4024 str = item_chars->str;
4025 g_string_free(item_chars, FALSE);
4029 /* return the number of spaces at a line's start */
4030 static int compose_left_offset_length(GtkTextBuffer *buffer,
4031 const GtkTextIter *start)
4033 GtkTextIter iter = *start;
4036 if (gtk_text_iter_ends_line(&iter))
4040 wc = gtk_text_iter_get_char(&iter);
4041 if (!g_unichar_isspace(wc))
4044 gtk_text_iter_forward_char(&iter);
4045 if (gtk_text_iter_ends_line(&iter))
4049 gtk_text_iter_forward_char(&iter);
4050 if (gtk_text_iter_ends_line(&iter))
4055 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4056 const GtkTextIter *start,
4057 GtkTextIter *break_pos,
4061 GtkTextIter iter = *start, line_end = *start;
4062 PangoLogAttr *attrs;
4069 gboolean can_break = FALSE;
4070 gboolean do_break = FALSE;
4071 gboolean was_white = FALSE;
4072 gboolean prev_dont_break = FALSE;
4074 gtk_text_iter_forward_to_line_end(&line_end);
4075 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4076 len = g_utf8_strlen(str, -1);
4080 g_warning("compose_get_line_break_pos: len = 0!\n");
4084 /* g_print("breaking line: %d: %s (len = %d)\n",
4085 gtk_text_iter_get_line(&iter), str, len); */
4087 attrs = g_new(PangoLogAttr, len + 1);
4089 pango_default_break(str, -1, NULL, attrs, len + 1);
4093 /* skip quote and leading spaces */
4094 for (i = 0; *p != '\0' && i < len; i++) {
4097 wc = g_utf8_get_char(p);
4098 if (i >= quote_len && !g_unichar_isspace(wc))
4100 if (g_unichar_iswide(wc))
4102 else if (*p == '\t')
4106 p = g_utf8_next_char(p);
4109 for (; *p != '\0' && i < len; i++) {
4110 PangoLogAttr *attr = attrs + i;
4114 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4117 was_white = attr->is_white;
4119 /* don't wrap URI */
4120 if ((uri_len = get_uri_len(p)) > 0) {
4122 if (pos > 0 && col > max_col) {
4132 wc = g_utf8_get_char(p);
4133 if (g_unichar_iswide(wc)) {
4135 if (prev_dont_break && can_break && attr->is_line_break)
4137 } else if (*p == '\t')
4141 if (pos > 0 && col > max_col) {
4146 if (*p == '-' || *p == '/')
4147 prev_dont_break = TRUE;
4149 prev_dont_break = FALSE;
4151 p = g_utf8_next_char(p);
4155 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4160 *break_pos = *start;
4161 gtk_text_iter_set_line_offset(break_pos, pos);
4166 static gboolean compose_join_next_line(Compose *compose,
4167 GtkTextBuffer *buffer,
4169 const gchar *quote_str)
4171 GtkTextIter iter_ = *iter, cur, prev, next, end;
4172 PangoLogAttr attrs[3];
4174 gchar *next_quote_str;
4177 gboolean keep_cursor = FALSE;
4179 if (!gtk_text_iter_forward_line(&iter_) ||
4180 gtk_text_iter_ends_line(&iter_)) {
4183 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4185 if ((quote_str || next_quote_str) &&
4186 strcmp2(quote_str, next_quote_str) != 0) {
4187 g_free(next_quote_str);
4190 g_free(next_quote_str);
4193 if (quote_len > 0) {
4194 gtk_text_iter_forward_chars(&end, quote_len);
4195 if (gtk_text_iter_ends_line(&end)) {
4200 /* don't join itemized lines */
4201 if (compose_itemized_length(buffer, &end) > 0) {
4205 /* don't join signature separator */
4206 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4209 /* delete quote str */
4211 gtk_text_buffer_delete(buffer, &iter_, &end);
4213 /* don't join line breaks put by the user */
4215 gtk_text_iter_backward_char(&cur);
4216 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4217 gtk_text_iter_forward_char(&cur);
4221 gtk_text_iter_forward_char(&cur);
4222 /* delete linebreak and extra spaces */
4223 while (gtk_text_iter_backward_char(&cur)) {
4224 wc1 = gtk_text_iter_get_char(&cur);
4225 if (!g_unichar_isspace(wc1))
4230 while (!gtk_text_iter_ends_line(&cur)) {
4231 wc1 = gtk_text_iter_get_char(&cur);
4232 if (!g_unichar_isspace(wc1))
4234 gtk_text_iter_forward_char(&cur);
4237 if (!gtk_text_iter_equal(&prev, &next)) {
4240 mark = gtk_text_buffer_get_insert(buffer);
4241 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4242 if (gtk_text_iter_equal(&prev, &cur))
4244 gtk_text_buffer_delete(buffer, &prev, &next);
4248 /* insert space if required */
4249 gtk_text_iter_backward_char(&prev);
4250 wc1 = gtk_text_iter_get_char(&prev);
4251 wc2 = gtk_text_iter_get_char(&next);
4252 gtk_text_iter_forward_char(&next);
4253 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4254 pango_default_break(str, -1, NULL, attrs, 3);
4255 if (!attrs[1].is_line_break ||
4256 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4257 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4259 gtk_text_iter_backward_char(&iter_);
4260 gtk_text_buffer_place_cursor(buffer, &iter_);
4269 #define ADD_TXT_POS(bp_, ep_, pti_) \
4270 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4271 last = last->next; \
4272 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4273 last->next = NULL; \
4275 g_warning("alloc error scanning URIs\n"); \
4278 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4280 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4281 GtkTextBuffer *buffer;
4282 GtkTextIter iter, break_pos, end_of_line;
4283 gchar *quote_str = NULL;
4285 gboolean wrap_quote = prefs_common.linewrap_quote;
4286 gboolean prev_autowrap = compose->autowrap;
4287 gint startq_offset = -1, noq_offset = -1;
4288 gint uri_start = -1, uri_stop = -1;
4289 gint nouri_start = -1, nouri_stop = -1;
4290 gint num_blocks = 0;
4291 gint quotelevel = -1;
4292 gboolean modified = force;
4293 gboolean removed = FALSE;
4294 gboolean modified_before_remove = FALSE;
4296 gboolean start = TRUE;
4297 gint itemized_len = 0, rem_item_len = 0;
4298 gchar *itemized_chars = NULL;
4299 gboolean item_continuation = FALSE;
4304 if (compose->draft_timeout_tag == -2) {
4308 compose->autowrap = FALSE;
4310 buffer = gtk_text_view_get_buffer(text);
4311 undo_wrapping(compose->undostruct, TRUE);
4316 mark = gtk_text_buffer_get_insert(buffer);
4317 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4321 if (compose->draft_timeout_tag == -2) {
4322 if (gtk_text_iter_ends_line(&iter)) {
4323 while (gtk_text_iter_ends_line(&iter) &&
4324 gtk_text_iter_forward_line(&iter))
4327 while (gtk_text_iter_backward_line(&iter)) {
4328 if (gtk_text_iter_ends_line(&iter)) {
4329 gtk_text_iter_forward_line(&iter);
4335 /* move to line start */
4336 gtk_text_iter_set_line_offset(&iter, 0);
4339 itemized_len = compose_itemized_length(buffer, &iter);
4341 if (!itemized_len) {
4342 itemized_len = compose_left_offset_length(buffer, &iter);
4343 item_continuation = TRUE;
4347 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4349 /* go until paragraph end (empty line) */
4350 while (start || !gtk_text_iter_ends_line(&iter)) {
4351 gchar *scanpos = NULL;
4352 /* parse table - in order of priority */
4354 const gchar *needle; /* token */
4356 /* token search function */
4357 gchar *(*search) (const gchar *haystack,
4358 const gchar *needle);
4359 /* part parsing function */
4360 gboolean (*parse) (const gchar *start,
4361 const gchar *scanpos,
4365 /* part to URI function */
4366 gchar *(*build_uri) (const gchar *bp,
4370 static struct table parser[] = {
4371 {"http://", strcasestr, get_uri_part, make_uri_string},
4372 {"https://", strcasestr, get_uri_part, make_uri_string},
4373 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4374 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4375 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4376 {"www.", strcasestr, get_uri_part, make_http_string},
4377 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4378 {"@", strcasestr, get_email_part, make_email_string}
4380 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4381 gint last_index = PARSE_ELEMS;
4383 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4387 if (!prev_autowrap && num_blocks == 0) {
4389 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4390 G_CALLBACK(text_inserted),
4393 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4396 uri_start = uri_stop = -1;
4398 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4401 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4402 if (startq_offset == -1)
4403 startq_offset = gtk_text_iter_get_offset(&iter);
4404 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4405 if (quotelevel > 2) {
4406 /* recycle colors */
4407 if (prefs_common.recycle_quote_colors)
4416 if (startq_offset == -1)
4417 noq_offset = gtk_text_iter_get_offset(&iter);
4421 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4424 if (gtk_text_iter_ends_line(&iter)) {
4426 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4427 prefs_common.linewrap_len,
4429 GtkTextIter prev, next, cur;
4430 if (prev_autowrap != FALSE || force) {
4431 compose->automatic_break = TRUE;
4433 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4434 compose->automatic_break = FALSE;
4435 if (itemized_len && compose->autoindent) {
4436 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4437 if (!item_continuation)
4438 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4440 } else if (quote_str && wrap_quote) {
4441 compose->automatic_break = TRUE;
4443 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4444 compose->automatic_break = FALSE;
4445 if (itemized_len && compose->autoindent) {
4446 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4447 if (!item_continuation)
4448 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4452 /* remove trailing spaces */
4454 rem_item_len = itemized_len;
4455 while (compose->autoindent && rem_item_len-- > 0)
4456 gtk_text_iter_backward_char(&cur);
4457 gtk_text_iter_backward_char(&cur);
4460 while (!gtk_text_iter_starts_line(&cur)) {
4463 gtk_text_iter_backward_char(&cur);
4464 wc = gtk_text_iter_get_char(&cur);
4465 if (!g_unichar_isspace(wc))
4469 if (!gtk_text_iter_equal(&prev, &next)) {
4470 gtk_text_buffer_delete(buffer, &prev, &next);
4472 gtk_text_iter_forward_char(&break_pos);
4476 gtk_text_buffer_insert(buffer, &break_pos,
4480 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4482 /* move iter to current line start */
4483 gtk_text_iter_set_line_offset(&iter, 0);
4490 /* move iter to next line start */
4496 if (!prev_autowrap && num_blocks > 0) {
4498 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4499 G_CALLBACK(text_inserted),
4503 while (!gtk_text_iter_ends_line(&end_of_line)) {
4504 gtk_text_iter_forward_char(&end_of_line);
4506 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4508 nouri_start = gtk_text_iter_get_offset(&iter);
4509 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4511 walk_pos = gtk_text_iter_get_offset(&iter);
4512 /* FIXME: this looks phony. scanning for anything in the parse table */
4513 for (n = 0; n < PARSE_ELEMS; n++) {
4516 tmp = parser[n].search(walk, parser[n].needle);
4518 if (scanpos == NULL || tmp < scanpos) {
4527 /* check if URI can be parsed */
4528 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4529 (const gchar **)&ep, FALSE)
4530 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4534 strlen(parser[last_index].needle);
4537 uri_start = walk_pos + (bp - o_walk);
4538 uri_stop = walk_pos + (ep - o_walk);
4542 gtk_text_iter_forward_line(&iter);
4545 if (startq_offset != -1) {
4546 GtkTextIter startquote, endquote;
4547 gtk_text_buffer_get_iter_at_offset(
4548 buffer, &startquote, startq_offset);
4551 switch (quotelevel) {
4553 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4554 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4555 gtk_text_buffer_apply_tag_by_name(
4556 buffer, "quote0", &startquote, &endquote);
4557 gtk_text_buffer_remove_tag_by_name(
4558 buffer, "quote1", &startquote, &endquote);
4559 gtk_text_buffer_remove_tag_by_name(
4560 buffer, "quote2", &startquote, &endquote);
4565 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4566 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4567 gtk_text_buffer_apply_tag_by_name(
4568 buffer, "quote1", &startquote, &endquote);
4569 gtk_text_buffer_remove_tag_by_name(
4570 buffer, "quote0", &startquote, &endquote);
4571 gtk_text_buffer_remove_tag_by_name(
4572 buffer, "quote2", &startquote, &endquote);
4577 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4578 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4579 gtk_text_buffer_apply_tag_by_name(
4580 buffer, "quote2", &startquote, &endquote);
4581 gtk_text_buffer_remove_tag_by_name(
4582 buffer, "quote0", &startquote, &endquote);
4583 gtk_text_buffer_remove_tag_by_name(
4584 buffer, "quote1", &startquote, &endquote);
4590 } else if (noq_offset != -1) {
4591 GtkTextIter startnoquote, endnoquote;
4592 gtk_text_buffer_get_iter_at_offset(
4593 buffer, &startnoquote, noq_offset);
4596 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4597 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4598 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4599 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4600 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4601 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4602 gtk_text_buffer_remove_tag_by_name(
4603 buffer, "quote0", &startnoquote, &endnoquote);
4604 gtk_text_buffer_remove_tag_by_name(
4605 buffer, "quote1", &startnoquote, &endnoquote);
4606 gtk_text_buffer_remove_tag_by_name(
4607 buffer, "quote2", &startnoquote, &endnoquote);
4613 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4614 GtkTextIter nouri_start_iter, nouri_end_iter;
4615 gtk_text_buffer_get_iter_at_offset(
4616 buffer, &nouri_start_iter, nouri_start);
4617 gtk_text_buffer_get_iter_at_offset(
4618 buffer, &nouri_end_iter, nouri_stop);
4619 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4620 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4621 gtk_text_buffer_remove_tag_by_name(
4622 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4623 modified_before_remove = modified;
4628 if (uri_start >= 0 && uri_stop > 0) {
4629 GtkTextIter uri_start_iter, uri_end_iter, back;
4630 gtk_text_buffer_get_iter_at_offset(
4631 buffer, &uri_start_iter, uri_start);
4632 gtk_text_buffer_get_iter_at_offset(
4633 buffer, &uri_end_iter, uri_stop);
4634 back = uri_end_iter;
4635 gtk_text_iter_backward_char(&back);
4636 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4637 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4638 gtk_text_buffer_apply_tag_by_name(
4639 buffer, "link", &uri_start_iter, &uri_end_iter);
4641 if (removed && !modified_before_remove) {
4647 // debug_print("not modified, out after %d lines\n", lines);
4651 // debug_print("modified, out after %d lines\n", lines);
4653 g_free(itemized_chars);
4656 undo_wrapping(compose->undostruct, FALSE);
4657 compose->autowrap = prev_autowrap;
4662 void compose_action_cb(void *data)
4664 Compose *compose = (Compose *)data;
4665 compose_wrap_all(compose);
4668 static void compose_wrap_all(Compose *compose)
4670 compose_wrap_all_full(compose, FALSE);
4673 static void compose_wrap_all_full(Compose *compose, gboolean force)
4675 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4676 GtkTextBuffer *buffer;
4678 gboolean modified = TRUE;
4680 buffer = gtk_text_view_get_buffer(text);
4682 gtk_text_buffer_get_start_iter(buffer, &iter);
4683 while (!gtk_text_iter_is_end(&iter) && modified)
4684 modified = compose_beautify_paragraph(compose, &iter, force);
4688 static void compose_set_title(Compose *compose)
4694 edited = compose->modified ? _(" [Edited]") : "";
4696 subject = gtk_editable_get_chars(
4697 GTK_EDITABLE(compose->subject_entry), 0, -1);
4699 #ifndef GENERIC_UMPC
4700 if (subject && strlen(subject))
4701 str = g_strdup_printf(_("%s - Compose message%s"),
4704 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4706 str = g_strdup(_("Compose message"));
4709 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4715 * compose_current_mail_account:
4717 * Find a current mail account (the currently selected account, or the
4718 * default account, if a news account is currently selected). If a
4719 * mail account cannot be found, display an error message.
4721 * Return value: Mail account, or NULL if not found.
4723 static PrefsAccount *
4724 compose_current_mail_account(void)
4728 if (cur_account && cur_account->protocol != A_NNTP)
4731 ac = account_get_default();
4732 if (!ac || ac->protocol == A_NNTP) {
4733 alertpanel_error(_("Account for sending mail is not specified.\n"
4734 "Please select a mail account before sending."));
4741 #define QUOTE_IF_REQUIRED(out, str) \
4743 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4747 len = strlen(str) + 3; \
4748 if ((__tmp = alloca(len)) == NULL) { \
4749 g_warning("can't allocate memory\n"); \
4750 g_string_free(header, TRUE); \
4753 g_snprintf(__tmp, len, "\"%s\"", str); \
4758 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4759 g_warning("can't allocate memory\n"); \
4760 g_string_free(header, TRUE); \
4763 strcpy(__tmp, str); \
4769 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4771 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4775 len = strlen(str) + 3; \
4776 if ((__tmp = alloca(len)) == NULL) { \
4777 g_warning("can't allocate memory\n"); \
4780 g_snprintf(__tmp, len, "\"%s\"", str); \
4785 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4786 g_warning("can't allocate memory\n"); \
4789 strcpy(__tmp, str); \
4795 static void compose_select_account(Compose *compose, PrefsAccount *account,
4798 gchar *from = NULL, *header = NULL;
4799 ComposeHeaderEntry *header_entry;
4800 #if GTK_CHECK_VERSION(2, 24, 0)
4804 cm_return_if_fail(account != NULL);
4806 compose->account = account;
4807 if (account->name && *account->name) {
4809 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4810 qbuf = escape_internal_quotes(buf, '"');
4811 from = g_strdup_printf("%s <%s>",
4812 qbuf, account->address);
4815 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4817 from = g_strdup_printf("<%s>",
4819 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4824 compose_set_title(compose);
4826 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4827 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4829 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4830 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4831 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4833 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4835 activate_privacy_system(compose, account, FALSE);
4837 if (!init && compose->mode != COMPOSE_REDIRECT) {
4838 undo_block(compose->undostruct);
4839 compose_insert_sig(compose, TRUE);
4840 undo_unblock(compose->undostruct);
4843 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4844 #if !GTK_CHECK_VERSION(2, 24, 0)
4845 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4847 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4848 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4849 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4852 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4853 if (account->protocol == A_NNTP) {
4854 if (!strcmp(header, _("To:")))
4855 combobox_select_by_text(
4856 GTK_COMBO_BOX(header_entry->combo),
4859 if (!strcmp(header, _("Newsgroups:")))
4860 combobox_select_by_text(
4861 GTK_COMBO_BOX(header_entry->combo),
4869 /* use account's dict info if set */
4870 if (compose->gtkaspell) {
4871 if (account->enable_default_dictionary)
4872 gtkaspell_change_dict(compose->gtkaspell,
4873 account->default_dictionary, FALSE);
4874 if (account->enable_default_alt_dictionary)
4875 gtkaspell_change_alt_dict(compose->gtkaspell,
4876 account->default_alt_dictionary);
4877 if (account->enable_default_dictionary
4878 || account->enable_default_alt_dictionary)
4879 compose_spell_menu_changed(compose);
4884 gboolean compose_check_for_valid_recipient(Compose *compose) {
4885 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4886 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4887 gboolean recipient_found = FALSE;
4891 /* free to and newsgroup list */
4892 slist_free_strings_full(compose->to_list);
4893 compose->to_list = NULL;
4895 slist_free_strings_full(compose->newsgroup_list);
4896 compose->newsgroup_list = NULL;
4898 /* search header entries for to and newsgroup entries */
4899 for (list = compose->header_list; list; list = list->next) {
4902 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4903 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4906 if (entry[0] != '\0') {
4907 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4908 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4909 compose->to_list = address_list_append(compose->to_list, entry);
4910 recipient_found = TRUE;
4913 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4914 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4915 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4916 recipient_found = TRUE;
4923 return recipient_found;
4926 static gboolean compose_check_for_set_recipients(Compose *compose)
4928 if (compose->account->set_autocc && compose->account->auto_cc) {
4929 gboolean found_other = FALSE;
4931 /* search header entries for to and newsgroup entries */
4932 for (list = compose->header_list; list; list = list->next) {
4935 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4936 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4939 if (strcmp(entry, compose->account->auto_cc)
4940 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4950 if (compose->batch) {
4951 gtk_widget_show_all(compose->window);
4953 aval = alertpanel(_("Send"),
4954 _("The only recipient is the default CC address. Send anyway?"),
4955 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4956 if (aval != G_ALERTALTERNATE)
4960 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4961 gboolean found_other = FALSE;
4963 /* search header entries for to and newsgroup entries */
4964 for (list = compose->header_list; list; list = list->next) {
4967 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4968 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4971 if (strcmp(entry, compose->account->auto_bcc)
4972 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4982 if (compose->batch) {
4983 gtk_widget_show_all(compose->window);
4985 aval = alertpanel(_("Send"),
4986 _("The only recipient is the default BCC address. Send anyway?"),
4987 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4988 if (aval != G_ALERTALTERNATE)
4995 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4999 if (compose_check_for_valid_recipient(compose) == FALSE) {
5000 if (compose->batch) {
5001 gtk_widget_show_all(compose->window);
5003 alertpanel_error(_("Recipient is not specified."));
5007 if (compose_check_for_set_recipients(compose) == FALSE) {
5011 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5012 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5013 if (*str == '\0' && check_everything == TRUE &&
5014 compose->mode != COMPOSE_REDIRECT) {
5016 gchar *button_label;
5019 if (compose->sending)
5020 button_label = _("+_Send");
5022 button_label = _("+_Queue");
5023 message = g_strdup_printf(_("Subject is empty. %s"),
5024 compose->sending?_("Send it anyway?"):
5025 _("Queue it anyway?"));
5027 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5028 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5029 ALERT_QUESTION, G_ALERTDEFAULT);
5031 if (aval & G_ALERTDISABLE) {
5032 aval &= ~G_ALERTDISABLE;
5033 prefs_common.warn_empty_subj = FALSE;
5035 if (aval != G_ALERTALTERNATE)
5040 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5046 gint compose_send(Compose *compose)
5049 FolderItem *folder = NULL;
5051 gchar *msgpath = NULL;
5052 gboolean discard_window = FALSE;
5053 gchar *errstr = NULL;
5054 gchar *tmsgid = NULL;
5055 MainWindow *mainwin = mainwindow_get_mainwindow();
5056 gboolean queued_removed = FALSE;
5058 if (prefs_common.send_dialog_invisible
5059 || compose->batch == TRUE)
5060 discard_window = TRUE;
5062 compose_allow_user_actions (compose, FALSE);
5063 compose->sending = TRUE;
5065 if (compose_check_entries(compose, TRUE) == FALSE) {
5066 if (compose->batch) {
5067 gtk_widget_show_all(compose->window);
5073 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5076 if (compose->batch) {
5077 gtk_widget_show_all(compose->window);
5080 alertpanel_error(_("Could not queue message for sending:\n\n"
5081 "Charset conversion failed."));
5082 } else if (val == -5) {
5083 alertpanel_error(_("Could not queue message for sending:\n\n"
5084 "Couldn't get recipient encryption key."));
5085 } else if (val == -6) {
5087 } else if (val == -3) {
5088 if (privacy_peek_error())
5089 alertpanel_error(_("Could not queue message for sending:\n\n"
5090 "Signature failed: %s"), privacy_get_error());
5091 } else if (val == -2 && errno != 0) {
5092 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5094 alertpanel_error(_("Could not queue message for sending."));
5099 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5100 if (discard_window) {
5101 compose->sending = FALSE;
5102 compose_close(compose);
5103 /* No more compose access in the normal codepath
5104 * after this point! */
5109 alertpanel_error(_("The message was queued but could not be "
5110 "sent.\nUse \"Send queued messages\" from "
5111 "the main window to retry."));
5112 if (!discard_window) {
5119 if (msgpath == NULL) {
5120 msgpath = folder_item_fetch_msg(folder, msgnum);
5121 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5124 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5125 claws_unlink(msgpath);
5128 if (!discard_window) {
5130 if (!queued_removed)
5131 folder_item_remove_msg(folder, msgnum);
5132 folder_item_scan(folder);
5134 /* make sure we delete that */
5135 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5137 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5138 folder_item_remove_msg(folder, tmp->msgnum);
5139 procmsg_msginfo_free(tmp);
5146 if (!queued_removed)
5147 folder_item_remove_msg(folder, msgnum);
5148 folder_item_scan(folder);
5150 /* make sure we delete that */
5151 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5153 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5154 folder_item_remove_msg(folder, tmp->msgnum);
5155 procmsg_msginfo_free(tmp);
5158 if (!discard_window) {
5159 compose->sending = FALSE;
5160 compose_allow_user_actions (compose, TRUE);
5161 compose_close(compose);
5165 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5166 "the main window to retry."), errstr);
5169 alertpanel_error_log(_("The message was queued but could not be "
5170 "sent.\nUse \"Send queued messages\" from "
5171 "the main window to retry."));
5173 if (!discard_window) {
5182 toolbar_main_set_sensitive(mainwin);
5183 main_window_set_menu_sensitive(mainwin);
5189 compose_allow_user_actions (compose, TRUE);
5190 compose->sending = FALSE;
5191 compose->modified = TRUE;
5192 toolbar_main_set_sensitive(mainwin);
5193 main_window_set_menu_sensitive(mainwin);
5198 static gboolean compose_use_attach(Compose *compose)
5200 GtkTreeModel *model = gtk_tree_view_get_model
5201 (GTK_TREE_VIEW(compose->attach_clist));
5202 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5205 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5208 gchar buf[BUFFSIZE];
5210 gboolean first_to_address;
5211 gboolean first_cc_address;
5213 ComposeHeaderEntry *headerentry;
5214 const gchar *headerentryname;
5215 const gchar *cc_hdr;
5216 const gchar *to_hdr;
5217 gboolean err = FALSE;
5219 debug_print("Writing redirect header\n");
5221 cc_hdr = prefs_common_translated_header_name("Cc:");
5222 to_hdr = prefs_common_translated_header_name("To:");
5224 first_to_address = TRUE;
5225 for (list = compose->header_list; list; list = list->next) {
5226 headerentry = ((ComposeHeaderEntry *)list->data);
5227 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5229 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5230 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5231 Xstrdup_a(str, entstr, return -1);
5233 if (str[0] != '\0') {
5234 compose_convert_header
5235 (compose, buf, sizeof(buf), str,
5236 strlen("Resent-To") + 2, TRUE);
5238 if (first_to_address) {
5239 err |= (fprintf(fp, "Resent-To: ") < 0);
5240 first_to_address = FALSE;
5242 err |= (fprintf(fp, ",") < 0);
5244 err |= (fprintf(fp, "%s", buf) < 0);
5248 if (!first_to_address) {
5249 err |= (fprintf(fp, "\n") < 0);
5252 first_cc_address = TRUE;
5253 for (list = compose->header_list; list; list = list->next) {
5254 headerentry = ((ComposeHeaderEntry *)list->data);
5255 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5257 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5258 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5259 Xstrdup_a(str, strg, return -1);
5261 if (str[0] != '\0') {
5262 compose_convert_header
5263 (compose, buf, sizeof(buf), str,
5264 strlen("Resent-Cc") + 2, TRUE);
5266 if (first_cc_address) {
5267 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5268 first_cc_address = FALSE;
5270 err |= (fprintf(fp, ",") < 0);
5272 err |= (fprintf(fp, "%s", buf) < 0);
5276 if (!first_cc_address) {
5277 err |= (fprintf(fp, "\n") < 0);
5280 return (err ? -1:0);
5283 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5285 gchar buf[BUFFSIZE];
5287 const gchar *entstr;
5288 /* struct utsname utsbuf; */
5289 gboolean err = FALSE;
5291 cm_return_val_if_fail(fp != NULL, -1);
5292 cm_return_val_if_fail(compose->account != NULL, -1);
5293 cm_return_val_if_fail(compose->account->address != NULL, -1);
5296 get_rfc822_date(buf, sizeof(buf));
5297 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5300 if (compose->account->name && *compose->account->name) {
5301 compose_convert_header
5302 (compose, buf, sizeof(buf), compose->account->name,
5303 strlen("From: "), TRUE);
5304 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5305 buf, compose->account->address) < 0);
5307 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5310 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5311 if (*entstr != '\0') {
5312 Xstrdup_a(str, entstr, return -1);
5315 compose_convert_header(compose, buf, sizeof(buf), str,
5316 strlen("Subject: "), FALSE);
5317 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5321 /* Resent-Message-ID */
5322 if (compose->account->set_domain && compose->account->domain) {
5323 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5324 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5325 g_snprintf(buf, sizeof(buf), "%s",
5326 strchr(compose->account->address, '@') ?
5327 strchr(compose->account->address, '@')+1 :
5328 compose->account->address);
5330 g_snprintf(buf, sizeof(buf), "%s", "");
5333 if (compose->account->gen_msgid) {
5335 if (compose->account->msgid_with_addr) {
5336 addr = compose->account->address;
5338 generate_msgid(buf, sizeof(buf), addr);
5339 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5340 compose->msgid = g_strdup(buf);
5342 compose->msgid = NULL;
5345 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5348 /* separator between header and body */
5349 err |= (fputs("\n", fp) == EOF);
5351 return (err ? -1:0);
5354 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5358 gchar buf[BUFFSIZE];
5360 gboolean skip = FALSE;
5361 gboolean err = FALSE;
5362 gchar *not_included[]={
5363 "Return-Path:", "Delivered-To:", "Received:",
5364 "Subject:", "X-UIDL:", "AF:",
5365 "NF:", "PS:", "SRH:",
5366 "SFN:", "DSR:", "MID:",
5367 "CFG:", "PT:", "S:",
5368 "RQ:", "SSV:", "NSV:",
5369 "SSH:", "R:", "MAID:",
5370 "NAID:", "RMID:", "FMID:",
5371 "SCF:", "RRCPT:", "NG:",
5372 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5373 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5374 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5375 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5376 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5379 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5380 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5384 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5386 for (i = 0; not_included[i] != NULL; i++) {
5387 if (g_ascii_strncasecmp(buf, not_included[i],
5388 strlen(not_included[i])) == 0) {
5395 if (fputs(buf, fdest) == -1)
5398 if (!prefs_common.redirect_keep_from) {
5399 if (g_ascii_strncasecmp(buf, "From:",
5400 strlen("From:")) == 0) {
5401 err |= (fputs(" (by way of ", fdest) == EOF);
5402 if (compose->account->name
5403 && *compose->account->name) {
5404 compose_convert_header
5405 (compose, buf, sizeof(buf),
5406 compose->account->name,
5409 err |= (fprintf(fdest, "%s <%s>",
5411 compose->account->address) < 0);
5413 err |= (fprintf(fdest, "%s",
5414 compose->account->address) < 0);
5415 err |= (fputs(")", fdest) == EOF);
5419 if (fputs("\n", fdest) == -1)
5426 if (compose_redirect_write_headers(compose, fdest))
5429 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5430 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5443 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5445 GtkTextBuffer *buffer;
5446 GtkTextIter start, end;
5449 const gchar *out_codeset;
5450 EncodingType encoding = ENC_UNKNOWN;
5451 MimeInfo *mimemsg, *mimetext;
5453 const gchar *src_codeset = CS_INTERNAL;
5454 gchar *from_addr = NULL;
5455 gchar *from_name = NULL;
5457 if (action == COMPOSE_WRITE_FOR_SEND)
5458 attach_parts = TRUE;
5460 /* create message MimeInfo */
5461 mimemsg = procmime_mimeinfo_new();
5462 mimemsg->type = MIMETYPE_MESSAGE;
5463 mimemsg->subtype = g_strdup("rfc822");
5464 mimemsg->content = MIMECONTENT_MEM;
5465 mimemsg->tmp = TRUE; /* must free content later */
5466 mimemsg->data.mem = compose_get_header(compose);
5468 /* Create text part MimeInfo */
5469 /* get all composed text */
5470 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5471 gtk_text_buffer_get_start_iter(buffer, &start);
5472 gtk_text_buffer_get_end_iter(buffer, &end);
5473 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5475 out_codeset = conv_get_charset_str(compose->out_encoding);
5477 if (!out_codeset && is_ascii_str(chars)) {
5478 out_codeset = CS_US_ASCII;
5479 } else if (prefs_common.outgoing_fallback_to_ascii &&
5480 is_ascii_str(chars)) {
5481 out_codeset = CS_US_ASCII;
5482 encoding = ENC_7BIT;
5486 gchar *test_conv_global_out = NULL;
5487 gchar *test_conv_reply = NULL;
5489 /* automatic mode. be automatic. */
5490 codeconv_set_strict(TRUE);
5492 out_codeset = conv_get_outgoing_charset_str();
5494 debug_print("trying to convert to %s\n", out_codeset);
5495 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5498 if (!test_conv_global_out && compose->orig_charset
5499 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5500 out_codeset = compose->orig_charset;
5501 debug_print("failure; trying to convert to %s\n", out_codeset);
5502 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5505 if (!test_conv_global_out && !test_conv_reply) {
5507 out_codeset = CS_INTERNAL;
5508 debug_print("failure; finally using %s\n", out_codeset);
5510 g_free(test_conv_global_out);
5511 g_free(test_conv_reply);
5512 codeconv_set_strict(FALSE);
5515 if (encoding == ENC_UNKNOWN) {
5516 if (prefs_common.encoding_method == CTE_BASE64)
5517 encoding = ENC_BASE64;
5518 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5519 encoding = ENC_QUOTED_PRINTABLE;
5520 else if (prefs_common.encoding_method == CTE_8BIT)
5521 encoding = ENC_8BIT;
5523 encoding = procmime_get_encoding_for_charset(out_codeset);
5526 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5527 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5529 if (action == COMPOSE_WRITE_FOR_SEND) {
5530 codeconv_set_strict(TRUE);
5531 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5532 codeconv_set_strict(FALSE);
5538 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5539 "to the specified %s charset.\n"
5540 "Send it as %s?"), out_codeset, src_codeset);
5541 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5542 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5545 if (aval != G_ALERTALTERNATE) {
5550 out_codeset = src_codeset;
5556 out_codeset = src_codeset;
5561 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5562 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5563 strstr(buf, "\nFrom ") != NULL) {
5564 encoding = ENC_QUOTED_PRINTABLE;
5568 mimetext = procmime_mimeinfo_new();
5569 mimetext->content = MIMECONTENT_MEM;
5570 mimetext->tmp = TRUE; /* must free content later */
5571 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5572 * and free the data, which we need later. */
5573 mimetext->data.mem = g_strdup(buf);
5574 mimetext->type = MIMETYPE_TEXT;
5575 mimetext->subtype = g_strdup("plain");
5576 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5577 g_strdup(out_codeset));
5579 /* protect trailing spaces when signing message */
5580 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5581 privacy_system_can_sign(compose->privacy_system)) {
5582 encoding = ENC_QUOTED_PRINTABLE;
5585 debug_print("main text: %zd bytes encoded as %s in %d\n",
5586 strlen(buf), out_codeset, encoding);
5588 /* check for line length limit */
5589 if (action == COMPOSE_WRITE_FOR_SEND &&
5590 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5591 check_line_length(buf, 1000, &line) < 0) {
5595 msg = g_strdup_printf
5596 (_("Line %d exceeds the line length limit (998 bytes).\n"
5597 "The contents of the message might be broken on the way to the delivery.\n"
5599 "Send it anyway?"), line + 1);
5600 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5602 if (aval != G_ALERTALTERNATE) {
5608 if (encoding != ENC_UNKNOWN)
5609 procmime_encode_content(mimetext, encoding);
5611 /* append attachment parts */
5612 if (compose_use_attach(compose) && attach_parts) {
5613 MimeInfo *mimempart;
5614 gchar *boundary = NULL;
5615 mimempart = procmime_mimeinfo_new();
5616 mimempart->content = MIMECONTENT_EMPTY;
5617 mimempart->type = MIMETYPE_MULTIPART;
5618 mimempart->subtype = g_strdup("mixed");
5622 boundary = generate_mime_boundary(NULL);
5623 } while (strstr(buf, boundary) != NULL);
5625 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5628 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5630 g_node_append(mimempart->node, mimetext->node);
5631 g_node_append(mimemsg->node, mimempart->node);
5633 if (compose_add_attachments(compose, mimempart) < 0)
5636 g_node_append(mimemsg->node, mimetext->node);
5640 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5641 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5642 /* extract name and address */
5643 if (strstr(spec, " <") && strstr(spec, ">")) {
5644 from_addr = g_strdup(strrchr(spec, '<')+1);
5645 *(strrchr(from_addr, '>')) = '\0';
5646 from_name = g_strdup(spec);
5647 *(strrchr(from_name, '<')) = '\0';
5654 /* sign message if sending */
5655 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5656 privacy_system_can_sign(compose->privacy_system))
5657 if (!privacy_sign(compose->privacy_system, mimemsg,
5658 compose->account, from_addr)) {
5665 procmime_write_mimeinfo(mimemsg, fp);
5667 procmime_mimeinfo_free_all(mimemsg);
5672 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5674 GtkTextBuffer *buffer;
5675 GtkTextIter start, end;
5680 if ((fp = g_fopen(file, "wb")) == NULL) {
5681 FILE_OP_ERROR(file, "fopen");
5685 /* chmod for security */
5686 if (change_file_mode_rw(fp, file) < 0) {
5687 FILE_OP_ERROR(file, "chmod");
5688 g_warning("can't change file mode\n");
5691 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5692 gtk_text_buffer_get_start_iter(buffer, &start);
5693 gtk_text_buffer_get_end_iter(buffer, &end);
5694 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5696 chars = conv_codeset_strdup
5697 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5700 if (!chars) return -1;
5703 len = strlen(chars);
5704 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5705 FILE_OP_ERROR(file, "fwrite");
5714 if (fclose(fp) == EOF) {
5715 FILE_OP_ERROR(file, "fclose");
5722 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5725 MsgInfo *msginfo = compose->targetinfo;
5727 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5728 if (!msginfo) return -1;
5730 if (!force && MSG_IS_LOCKED(msginfo->flags))
5733 item = msginfo->folder;
5734 cm_return_val_if_fail(item != NULL, -1);
5736 if (procmsg_msg_exist(msginfo) &&
5737 (folder_has_parent_of_type(item, F_QUEUE) ||
5738 folder_has_parent_of_type(item, F_DRAFT)
5739 || msginfo == compose->autosaved_draft)) {
5740 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5741 g_warning("can't remove the old message\n");
5744 debug_print("removed reedit target %d\n", msginfo->msgnum);
5751 static void compose_remove_draft(Compose *compose)
5754 MsgInfo *msginfo = compose->targetinfo;
5755 drafts = account_get_special_folder(compose->account, F_DRAFT);
5757 if (procmsg_msg_exist(msginfo)) {
5758 folder_item_remove_msg(drafts, msginfo->msgnum);
5763 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5764 gboolean remove_reedit_target)
5766 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5769 static gboolean compose_warn_encryption(Compose *compose)
5771 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5772 AlertValue val = G_ALERTALTERNATE;
5774 if (warning == NULL)
5777 val = alertpanel_full(_("Encryption warning"), warning,
5778 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5779 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5780 if (val & G_ALERTDISABLE) {
5781 val &= ~G_ALERTDISABLE;
5782 if (val == G_ALERTALTERNATE)
5783 privacy_inhibit_encrypt_warning(compose->privacy_system,
5787 if (val == G_ALERTALTERNATE) {
5794 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5795 gchar **msgpath, gboolean check_subject,
5796 gboolean remove_reedit_target)
5803 PrefsAccount *mailac = NULL, *newsac = NULL;
5804 gboolean err = FALSE;
5806 debug_print("queueing message...\n");
5807 cm_return_val_if_fail(compose->account != NULL, -1);
5809 if (compose_check_entries(compose, check_subject) == FALSE) {
5810 if (compose->batch) {
5811 gtk_widget_show_all(compose->window);
5816 if (!compose->to_list && !compose->newsgroup_list) {
5817 g_warning("can't get recipient list.");
5821 if (compose->to_list) {
5822 if (compose->account->protocol != A_NNTP)
5823 mailac = compose->account;
5824 else if (cur_account && cur_account->protocol != A_NNTP)
5825 mailac = cur_account;
5826 else if (!(mailac = compose_current_mail_account())) {
5827 alertpanel_error(_("No account for sending mails available!"));
5832 if (compose->newsgroup_list) {
5833 if (compose->account->protocol == A_NNTP)
5834 newsac = compose->account;
5836 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5841 /* write queue header */
5842 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5843 G_DIR_SEPARATOR, compose, (guint) rand());
5844 debug_print("queuing to %s\n", tmp);
5845 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5846 FILE_OP_ERROR(tmp, "fopen");
5851 if (change_file_mode_rw(fp, tmp) < 0) {
5852 FILE_OP_ERROR(tmp, "chmod");
5853 g_warning("can't change file mode\n");
5856 /* queueing variables */
5857 err |= (fprintf(fp, "AF:\n") < 0);
5858 err |= (fprintf(fp, "NF:0\n") < 0);
5859 err |= (fprintf(fp, "PS:10\n") < 0);
5860 err |= (fprintf(fp, "SRH:1\n") < 0);
5861 err |= (fprintf(fp, "SFN:\n") < 0);
5862 err |= (fprintf(fp, "DSR:\n") < 0);
5864 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5866 err |= (fprintf(fp, "MID:\n") < 0);
5867 err |= (fprintf(fp, "CFG:\n") < 0);
5868 err |= (fprintf(fp, "PT:0\n") < 0);
5869 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5870 err |= (fprintf(fp, "RQ:\n") < 0);
5872 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5874 err |= (fprintf(fp, "SSV:\n") < 0);
5876 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5878 err |= (fprintf(fp, "NSV:\n") < 0);
5879 err |= (fprintf(fp, "SSH:\n") < 0);
5880 /* write recepient list */
5881 if (compose->to_list) {
5882 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5883 for (cur = compose->to_list->next; cur != NULL;
5885 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5886 err |= (fprintf(fp, "\n") < 0);
5888 /* write newsgroup list */
5889 if (compose->newsgroup_list) {
5890 err |= (fprintf(fp, "NG:") < 0);
5891 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5892 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5893 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5894 err |= (fprintf(fp, "\n") < 0);
5896 /* Sylpheed account IDs */
5898 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5900 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5903 if (compose->privacy_system != NULL) {
5904 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5905 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5906 if (compose->use_encryption) {
5908 if (!compose_warn_encryption(compose)) {
5914 if (mailac && mailac->encrypt_to_self) {
5915 GSList *tmp_list = g_slist_copy(compose->to_list);
5916 tmp_list = g_slist_append(tmp_list, compose->account->address);
5917 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5918 g_slist_free(tmp_list);
5920 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5922 if (encdata != NULL) {
5923 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5924 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5925 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5927 } /* else we finally dont want to encrypt */
5929 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5930 /* and if encdata was null, it means there's been a problem in
5933 g_warning("failed to write queue message");
5943 /* Save copy folder */
5944 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5945 gchar *savefolderid;
5947 savefolderid = compose_get_save_to(compose);
5948 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5949 g_free(savefolderid);
5951 /* Save copy folder */
5952 if (compose->return_receipt) {
5953 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5955 /* Message-ID of message replying to */
5956 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5959 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5960 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5963 /* Message-ID of message forwarding to */
5964 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5967 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5968 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5972 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5973 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5975 /* end of headers */
5976 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5978 if (compose->redirect_filename != NULL) {
5979 if (compose_redirect_write_to_file(compose, fp) < 0) {
5987 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5991 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5995 g_warning("failed to write queue message\n");
6001 if (fclose(fp) == EOF) {
6002 FILE_OP_ERROR(tmp, "fclose");
6008 if (item && *item) {
6011 queue = account_get_special_folder(compose->account, F_QUEUE);
6014 g_warning("can't find queue folder\n");
6019 folder_item_scan(queue);
6020 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6021 g_warning("can't queue the message\n");
6027 if (msgpath == NULL) {
6033 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6034 compose_remove_reedit_target(compose, FALSE);
6037 if ((msgnum != NULL) && (item != NULL)) {
6045 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6048 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6050 struct stat statbuf;
6051 gchar *type, *subtype;
6052 GtkTreeModel *model;
6055 model = gtk_tree_view_get_model(tree_view);
6057 if (!gtk_tree_model_get_iter_first(model, &iter))
6060 gtk_tree_model_get(model, &iter,
6064 if (!is_file_exist(ainfo->file)) {
6065 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6066 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6067 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6069 if (val == G_ALERTDEFAULT) {
6074 mimepart = procmime_mimeinfo_new();
6075 mimepart->content = MIMECONTENT_FILE;
6076 mimepart->data.filename = g_strdup(ainfo->file);
6077 mimepart->tmp = FALSE; /* or we destroy our attachment */
6078 mimepart->offset = 0;
6080 g_stat(ainfo->file, &statbuf);
6081 mimepart->length = statbuf.st_size;
6083 type = g_strdup(ainfo->content_type);
6085 if (!strchr(type, '/')) {
6087 type = g_strdup("application/octet-stream");
6090 subtype = strchr(type, '/') + 1;
6091 *(subtype - 1) = '\0';
6092 mimepart->type = procmime_get_media_type(type);
6093 mimepart->subtype = g_strdup(subtype);
6096 if (mimepart->type == MIMETYPE_MESSAGE &&
6097 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6098 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6099 } else if (mimepart->type == MIMETYPE_TEXT) {
6100 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6101 /* Text parts with no name come from multipart/alternative
6102 * forwards. Make sure the recipient won't look at the
6103 * original HTML part by mistake. */
6104 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6105 ainfo->name = g_strdup_printf(_("Original %s part"),
6109 g_hash_table_insert(mimepart->typeparameters,
6110 g_strdup("charset"), g_strdup(ainfo->charset));
6112 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6113 if (mimepart->type == MIMETYPE_APPLICATION &&
6114 !strcmp2(mimepart->subtype, "octet-stream"))
6115 g_hash_table_insert(mimepart->typeparameters,
6116 g_strdup("name"), g_strdup(ainfo->name));
6117 g_hash_table_insert(mimepart->dispositionparameters,
6118 g_strdup("filename"), g_strdup(ainfo->name));
6119 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6122 if (mimepart->type == MIMETYPE_MESSAGE
6123 || mimepart->type == MIMETYPE_MULTIPART)
6124 ainfo->encoding = ENC_BINARY;
6125 else if (compose->use_signing) {
6126 if (ainfo->encoding == ENC_7BIT)
6127 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6128 else if (ainfo->encoding == ENC_8BIT)
6129 ainfo->encoding = ENC_BASE64;
6134 procmime_encode_content(mimepart, ainfo->encoding);
6136 g_node_append(parent->node, mimepart->node);
6137 } while (gtk_tree_model_iter_next(model, &iter));
6142 static gchar *compose_quote_list_of_addresses(gchar *str)
6144 GSList *list = NULL, *item = NULL;
6145 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6147 list = address_list_append_with_comments(list, str);
6148 for (item = list; item != NULL; item = item->next) {
6149 gchar *spec = item->data;
6150 gchar *endofname = strstr(spec, " <");
6151 if (endofname != NULL) {
6154 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6155 qqname = escape_internal_quotes(qname, '"');
6157 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6158 gchar *addr = g_strdup(endofname);
6159 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6160 faddr = g_strconcat(name, addr, NULL);
6163 debug_print("new auto-quoted address: '%s'", faddr);
6167 result = g_strdup((faddr != NULL)? faddr: spec);
6169 result = g_strconcat(result,
6171 (faddr != NULL)? faddr: spec,
6174 if (faddr != NULL) {
6179 slist_free_strings_full(list);
6184 #define IS_IN_CUSTOM_HEADER(header) \
6185 (compose->account->add_customhdr && \
6186 custom_header_find(compose->account->customhdr_list, header) != NULL)
6188 static void compose_add_headerfield_from_headerlist(Compose *compose,
6190 const gchar *fieldname,
6191 const gchar *seperator)
6193 gchar *str, *fieldname_w_colon;
6194 gboolean add_field = FALSE;
6196 ComposeHeaderEntry *headerentry;
6197 const gchar *headerentryname;
6198 const gchar *trans_fieldname;
6201 if (IS_IN_CUSTOM_HEADER(fieldname))
6204 debug_print("Adding %s-fields\n", fieldname);
6206 fieldstr = g_string_sized_new(64);
6208 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6209 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6211 for (list = compose->header_list; list; list = list->next) {
6212 headerentry = ((ComposeHeaderEntry *)list->data);
6213 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6215 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6216 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6218 str = compose_quote_list_of_addresses(ustr);
6220 if (str != NULL && str[0] != '\0') {
6222 g_string_append(fieldstr, seperator);
6223 g_string_append(fieldstr, str);
6232 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6233 compose_convert_header
6234 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6235 strlen(fieldname) + 2, TRUE);
6236 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6240 g_free(fieldname_w_colon);
6241 g_string_free(fieldstr, TRUE);
6246 static gchar *compose_get_manual_headers_info(Compose *compose)
6248 GString *sh_header = g_string_new(" ");
6250 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6252 for (list = compose->header_list; list; list = list->next) {
6253 ComposeHeaderEntry *headerentry;
6256 gchar *headername_wcolon;
6257 const gchar *headername_trans;
6259 gboolean standard_header = FALSE;
6261 headerentry = ((ComposeHeaderEntry *)list->data);
6263 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6265 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6270 if (!strstr(tmp, ":")) {
6271 headername_wcolon = g_strconcat(tmp, ":", NULL);
6272 headername = g_strdup(tmp);
6274 headername_wcolon = g_strdup(tmp);
6275 headername = g_strdup(strtok(tmp, ":"));
6279 string = std_headers;
6280 while (*string != NULL) {
6281 headername_trans = prefs_common_translated_header_name(*string);
6282 if (!strcmp(headername_trans, headername_wcolon))
6283 standard_header = TRUE;
6286 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6287 g_string_append_printf(sh_header, "%s ", headername);
6289 g_free(headername_wcolon);
6291 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6292 return g_string_free(sh_header, FALSE);
6295 static gchar *compose_get_header(Compose *compose)
6297 gchar buf[BUFFSIZE];
6298 const gchar *entry_str;
6302 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6304 gchar *from_name = NULL, *from_address = NULL;
6307 cm_return_val_if_fail(compose->account != NULL, NULL);
6308 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6310 header = g_string_sized_new(64);
6313 get_rfc822_date(buf, sizeof(buf));
6314 g_string_append_printf(header, "Date: %s\n", buf);
6318 if (compose->account->name && *compose->account->name) {
6320 QUOTE_IF_REQUIRED(buf, compose->account->name);
6321 tmp = g_strdup_printf("%s <%s>",
6322 buf, compose->account->address);
6324 tmp = g_strdup_printf("%s",
6325 compose->account->address);
6327 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6328 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6330 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6331 from_address = g_strdup(compose->account->address);
6333 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6334 /* extract name and address */
6335 if (strstr(spec, " <") && strstr(spec, ">")) {
6336 from_address = g_strdup(strrchr(spec, '<')+1);
6337 *(strrchr(from_address, '>')) = '\0';
6338 from_name = g_strdup(spec);
6339 *(strrchr(from_name, '<')) = '\0';
6342 from_address = g_strdup(spec);
6349 if (from_name && *from_name) {
6351 compose_convert_header
6352 (compose, buf, sizeof(buf), from_name,
6353 strlen("From: "), TRUE);
6354 QUOTE_IF_REQUIRED(name, buf);
6355 qname = escape_internal_quotes(name, '"');
6357 g_string_append_printf(header, "From: %s <%s>\n",
6358 qname, from_address);
6362 g_string_append_printf(header, "From: %s\n", from_address);
6365 g_free(from_address);
6368 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6371 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6374 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6378 * If this account is a NNTP account remove Bcc header from
6379 * message body since it otherwise will be publicly shown
6381 if (compose->account->protocol != A_NNTP)
6382 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6385 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6387 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6390 compose_convert_header(compose, buf, sizeof(buf), str,
6391 strlen("Subject: "), FALSE);
6392 g_string_append_printf(header, "Subject: %s\n", buf);
6398 if (compose->account->set_domain && compose->account->domain) {
6399 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6400 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6401 g_snprintf(buf, sizeof(buf), "%s",
6402 strchr(compose->account->address, '@') ?
6403 strchr(compose->account->address, '@')+1 :
6404 compose->account->address);
6406 g_snprintf(buf, sizeof(buf), "%s", "");
6409 if (compose->account->gen_msgid) {
6411 if (compose->account->msgid_with_addr) {
6412 addr = compose->account->address;
6414 generate_msgid(buf, sizeof(buf), addr);
6415 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6416 compose->msgid = g_strdup(buf);
6418 compose->msgid = NULL;
6421 if (compose->remove_references == FALSE) {
6423 if (compose->inreplyto && compose->to_list)
6424 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6427 if (compose->references)
6428 g_string_append_printf(header, "References: %s\n", compose->references);
6432 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6435 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6438 if (compose->account->organization &&
6439 strlen(compose->account->organization) &&
6440 !IS_IN_CUSTOM_HEADER("Organization")) {
6441 compose_convert_header(compose, buf, sizeof(buf),
6442 compose->account->organization,
6443 strlen("Organization: "), FALSE);
6444 g_string_append_printf(header, "Organization: %s\n", buf);
6447 /* Program version and system info */
6448 if (compose->account->gen_xmailer &&
6449 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6450 !compose->newsgroup_list) {
6451 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6453 gtk_major_version, gtk_minor_version, gtk_micro_version,
6456 if (compose->account->gen_xmailer &&
6457 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6458 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6460 gtk_major_version, gtk_minor_version, gtk_micro_version,
6464 /* custom headers */
6465 if (compose->account->add_customhdr) {
6468 for (cur = compose->account->customhdr_list; cur != NULL;
6470 CustomHeader *chdr = (CustomHeader *)cur->data;
6472 if (custom_header_is_allowed(chdr->name)
6473 && chdr->value != NULL
6474 && *(chdr->value) != '\0') {
6475 compose_convert_header
6476 (compose, buf, sizeof(buf),
6478 strlen(chdr->name) + 2, FALSE);
6479 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6484 /* Automatic Faces and X-Faces */
6485 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6486 g_string_append_printf(header, "X-Face: %s\n", buf);
6488 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6489 g_string_append_printf(header, "X-Face: %s\n", buf);
6491 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6492 g_string_append_printf(header, "Face: %s\n", buf);
6494 else if (get_default_face (buf, sizeof(buf)) == 0) {
6495 g_string_append_printf(header, "Face: %s\n", buf);
6499 switch (compose->priority) {
6500 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6501 "X-Priority: 1 (Highest)\n");
6503 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6504 "X-Priority: 2 (High)\n");
6506 case PRIORITY_NORMAL: break;
6507 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6508 "X-Priority: 4 (Low)\n");
6510 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6511 "X-Priority: 5 (Lowest)\n");
6513 default: debug_print("compose: priority unknown : %d\n",
6517 /* Request Return Receipt */
6518 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6519 if (compose->return_receipt) {
6520 if (compose->account->name
6521 && *compose->account->name) {
6522 compose_convert_header(compose, buf, sizeof(buf),
6523 compose->account->name,
6524 strlen("Disposition-Notification-To: "),
6526 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6528 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6532 /* get special headers */
6533 for (list = compose->header_list; list; list = list->next) {
6534 ComposeHeaderEntry *headerentry;
6537 gchar *headername_wcolon;
6538 const gchar *headername_trans;
6541 gboolean standard_header = FALSE;
6543 headerentry = ((ComposeHeaderEntry *)list->data);
6545 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6547 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6552 if (!strstr(tmp, ":")) {
6553 headername_wcolon = g_strconcat(tmp, ":", NULL);
6554 headername = g_strdup(tmp);
6556 headername_wcolon = g_strdup(tmp);
6557 headername = g_strdup(strtok(tmp, ":"));
6561 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6562 Xstrdup_a(headervalue, entry_str, return NULL);
6563 subst_char(headervalue, '\r', ' ');
6564 subst_char(headervalue, '\n', ' ');
6565 string = std_headers;
6566 while (*string != NULL) {
6567 headername_trans = prefs_common_translated_header_name(*string);
6568 if (!strcmp(headername_trans, headername_wcolon))
6569 standard_header = TRUE;
6572 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6573 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6576 g_free(headername_wcolon);
6580 g_string_free(header, FALSE);
6585 #undef IS_IN_CUSTOM_HEADER
6587 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6588 gint header_len, gboolean addr_field)
6590 gchar *tmpstr = NULL;
6591 const gchar *out_codeset = NULL;
6593 cm_return_if_fail(src != NULL);
6594 cm_return_if_fail(dest != NULL);
6596 if (len < 1) return;
6598 tmpstr = g_strdup(src);
6600 subst_char(tmpstr, '\n', ' ');
6601 subst_char(tmpstr, '\r', ' ');
6604 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6605 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6606 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6611 codeconv_set_strict(TRUE);
6612 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6613 conv_get_charset_str(compose->out_encoding));
6614 codeconv_set_strict(FALSE);
6616 if (!dest || *dest == '\0') {
6617 gchar *test_conv_global_out = NULL;
6618 gchar *test_conv_reply = NULL;
6620 /* automatic mode. be automatic. */
6621 codeconv_set_strict(TRUE);
6623 out_codeset = conv_get_outgoing_charset_str();
6625 debug_print("trying to convert to %s\n", out_codeset);
6626 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6629 if (!test_conv_global_out && compose->orig_charset
6630 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6631 out_codeset = compose->orig_charset;
6632 debug_print("failure; trying to convert to %s\n", out_codeset);
6633 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6636 if (!test_conv_global_out && !test_conv_reply) {
6638 out_codeset = CS_INTERNAL;
6639 debug_print("finally using %s\n", out_codeset);
6641 g_free(test_conv_global_out);
6642 g_free(test_conv_reply);
6643 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6645 codeconv_set_strict(FALSE);
6650 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6654 cm_return_if_fail(user_data != NULL);
6656 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6657 g_strstrip(address);
6658 if (*address != '\0') {
6659 gchar *name = procheader_get_fromname(address);
6660 extract_address(address);
6661 #ifndef USE_NEW_ADDRBOOK
6662 addressbook_add_contact(name, address, NULL, NULL);
6664 debug_print("%s: %s\n", name, address);
6665 if (addressadd_selection(name, address, NULL, NULL)) {
6666 debug_print( "addressbook_add_contact - added\n" );
6673 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6675 GtkWidget *menuitem;
6678 cm_return_if_fail(menu != NULL);
6679 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6681 menuitem = gtk_separator_menu_item_new();
6682 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6683 gtk_widget_show(menuitem);
6685 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6686 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6688 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6689 g_strstrip(address);
6690 if (*address == '\0') {
6691 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6694 g_signal_connect(G_OBJECT(menuitem), "activate",
6695 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6696 gtk_widget_show(menuitem);
6699 void compose_add_extra_header(gchar *header, GtkListStore *model)
6702 if (strcmp(header, "")) {
6703 COMBOBOX_ADD(model, header, COMPOSE_TO);
6707 void compose_add_extra_header_entries(GtkListStore *model)
6711 gchar buf[BUFFSIZE];
6714 if (extra_headers == NULL) {
6715 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6716 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6717 debug_print("extra headers file not found\n");
6718 goto extra_headers_done;
6720 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6721 lastc = strlen(buf) - 1; /* remove trailing control chars */
6722 while (lastc >= 0 && buf[lastc] != ':')
6723 buf[lastc--] = '\0';
6724 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6725 buf[lastc] = '\0'; /* remove trailing : for comparison */
6726 if (custom_header_is_allowed(buf)) {
6728 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6731 g_message("disallowed extra header line: %s\n", buf);
6735 g_message("invalid extra header line: %s\n", buf);
6741 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6742 extra_headers = g_slist_reverse(extra_headers);
6744 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6747 static void compose_create_header_entry(Compose *compose)
6749 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6756 const gchar *header = NULL;
6757 ComposeHeaderEntry *headerentry;
6758 gboolean standard_header = FALSE;
6759 GtkListStore *model;
6761 #if !(GTK_CHECK_VERSION(2,12,0))
6762 GtkTooltips *tips = compose->tooltips;
6765 headerentry = g_new0(ComposeHeaderEntry, 1);
6767 /* Combo box model */
6768 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6769 #if !GTK_CHECK_VERSION(2, 24, 0)
6770 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6772 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6774 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6776 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6778 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6779 COMPOSE_NEWSGROUPS);
6780 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6782 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6783 COMPOSE_FOLLOWUPTO);
6784 compose_add_extra_header_entries(model);
6787 #if GTK_CHECK_VERSION(2, 24, 0)
6788 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6789 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6790 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6791 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6792 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6794 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6795 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6796 G_CALLBACK(compose_grab_focus_cb), compose);
6797 gtk_widget_show(combo);
6800 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6801 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6804 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6805 compose->header_nextrow, compose->header_nextrow+1,
6806 GTK_SHRINK, GTK_FILL, 0, 0);
6807 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6808 const gchar *last_header_entry = gtk_entry_get_text(
6809 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6811 while (*string != NULL) {
6812 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6813 standard_header = TRUE;
6816 if (standard_header)
6817 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6819 if (!compose->header_last || !standard_header) {
6820 switch(compose->account->protocol) {
6822 header = prefs_common_translated_header_name("Newsgroups:");
6825 header = prefs_common_translated_header_name("To:");
6830 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6832 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6833 G_CALLBACK(compose_grab_focus_cb), compose);
6835 /* Entry field with cleanup button */
6836 button = gtk_button_new();
6837 gtk_button_set_image(GTK_BUTTON(button),
6838 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6839 gtk_widget_show(button);
6840 CLAWS_SET_TIP(button,
6841 _("Delete entry contents"));
6842 entry = gtk_entry_new();
6843 gtk_widget_show(entry);
6844 CLAWS_SET_TIP(entry,
6845 _("Use <tab> to autocomplete from addressbook"));
6846 hbox = gtk_hbox_new (FALSE, 0);
6847 gtk_widget_show(hbox);
6848 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6849 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6850 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6851 compose->header_nextrow, compose->header_nextrow+1,
6852 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6854 g_signal_connect(G_OBJECT(entry), "key-press-event",
6855 G_CALLBACK(compose_headerentry_key_press_event_cb),
6857 g_signal_connect(G_OBJECT(entry), "changed",
6858 G_CALLBACK(compose_headerentry_changed_cb),
6860 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6861 G_CALLBACK(compose_grab_focus_cb), compose);
6863 g_signal_connect(G_OBJECT(button), "clicked",
6864 G_CALLBACK(compose_headerentry_button_clicked_cb),
6868 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6869 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6870 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6871 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6872 G_CALLBACK(compose_header_drag_received_cb),
6874 g_signal_connect(G_OBJECT(entry), "drag-drop",
6875 G_CALLBACK(compose_drag_drop),
6877 g_signal_connect(G_OBJECT(entry), "populate-popup",
6878 G_CALLBACK(compose_entry_popup_extend),
6881 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6883 headerentry->compose = compose;
6884 headerentry->combo = combo;
6885 headerentry->entry = entry;
6886 headerentry->button = button;
6887 headerentry->hbox = hbox;
6888 headerentry->headernum = compose->header_nextrow;
6889 headerentry->type = PREF_NONE;
6891 compose->header_nextrow++;
6892 compose->header_last = headerentry;
6893 compose->header_list =
6894 g_slist_append(compose->header_list,
6898 static void compose_add_header_entry(Compose *compose, const gchar *header,
6899 gchar *text, ComposePrefType pref_type)
6901 ComposeHeaderEntry *last_header = compose->header_last;
6902 gchar *tmp = g_strdup(text), *email;
6903 gboolean replyto_hdr;
6905 replyto_hdr = (!strcasecmp(header,
6906 prefs_common_translated_header_name("Reply-To:")) ||
6908 prefs_common_translated_header_name("Followup-To:")) ||
6910 prefs_common_translated_header_name("In-Reply-To:")));
6912 extract_address(tmp);
6913 email = g_utf8_strdown(tmp, -1);
6915 if (replyto_hdr == FALSE &&
6916 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6918 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6919 header, text, (gint) pref_type);
6925 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6926 gtk_entry_set_text(GTK_ENTRY(
6927 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6929 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6930 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6931 last_header->type = pref_type;
6933 if (replyto_hdr == FALSE)
6934 g_hash_table_insert(compose->email_hashtable, email,
6935 GUINT_TO_POINTER(1));
6942 static void compose_destroy_headerentry(Compose *compose,
6943 ComposeHeaderEntry *headerentry)
6945 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6948 extract_address(text);
6949 email = g_utf8_strdown(text, -1);
6950 g_hash_table_remove(compose->email_hashtable, email);
6954 gtk_widget_destroy(headerentry->combo);
6955 gtk_widget_destroy(headerentry->entry);
6956 gtk_widget_destroy(headerentry->button);
6957 gtk_widget_destroy(headerentry->hbox);
6958 g_free(headerentry);
6961 static void compose_remove_header_entries(Compose *compose)
6964 for (list = compose->header_list; list; list = list->next)
6965 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6967 compose->header_last = NULL;
6968 g_slist_free(compose->header_list);
6969 compose->header_list = NULL;
6970 compose->header_nextrow = 1;
6971 compose_create_header_entry(compose);
6974 static GtkWidget *compose_create_header(Compose *compose)
6976 GtkWidget *from_optmenu_hbox;
6977 GtkWidget *header_scrolledwin_main;
6978 GtkWidget *header_table_main;
6979 GtkWidget *header_scrolledwin;
6980 GtkWidget *header_table;
6982 /* parent with account selection and from header */
6983 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6984 gtk_widget_show(header_scrolledwin_main);
6985 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6987 header_table_main = gtk_table_new(2, 2, FALSE);
6988 gtk_widget_show(header_table_main);
6989 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6990 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6991 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6993 from_optmenu_hbox = compose_account_option_menu_create(compose);
6994 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6995 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6997 /* child with header labels and entries */
6998 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6999 gtk_widget_show(header_scrolledwin);
7000 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7002 header_table = gtk_table_new(2, 2, FALSE);
7003 gtk_widget_show(header_table);
7004 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7005 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7006 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7008 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7009 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7011 compose->header_table = header_table;
7012 compose->header_list = NULL;
7013 compose->header_nextrow = 0;
7015 compose_create_header_entry(compose);
7017 compose->table = NULL;
7019 return header_scrolledwin_main;
7022 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7024 Compose *compose = (Compose *)data;
7025 GdkEventButton event;
7028 event.time = gtk_get_current_event_time();
7030 return attach_button_pressed(compose->attach_clist, &event, compose);
7033 static GtkWidget *compose_create_attach(Compose *compose)
7035 GtkWidget *attach_scrwin;
7036 GtkWidget *attach_clist;
7038 GtkListStore *store;
7039 GtkCellRenderer *renderer;
7040 GtkTreeViewColumn *column;
7041 GtkTreeSelection *selection;
7043 /* attachment list */
7044 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7045 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7046 GTK_POLICY_AUTOMATIC,
7047 GTK_POLICY_AUTOMATIC);
7048 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7050 store = gtk_list_store_new(N_ATTACH_COLS,
7056 G_TYPE_AUTO_POINTER,
7058 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7059 (GTK_TREE_MODEL(store)));
7060 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7061 g_object_unref(store);
7063 renderer = gtk_cell_renderer_text_new();
7064 column = gtk_tree_view_column_new_with_attributes
7065 (_("Mime type"), renderer, "text",
7066 COL_MIMETYPE, NULL);
7067 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7069 renderer = gtk_cell_renderer_text_new();
7070 column = gtk_tree_view_column_new_with_attributes
7071 (_("Size"), renderer, "text",
7073 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7075 renderer = gtk_cell_renderer_text_new();
7076 column = gtk_tree_view_column_new_with_attributes
7077 (_("Name"), renderer, "text",
7079 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7081 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7082 prefs_common.use_stripes_everywhere);
7083 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7084 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7086 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7087 G_CALLBACK(attach_selected), compose);
7088 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7089 G_CALLBACK(attach_button_pressed), compose);
7090 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7091 G_CALLBACK(popup_attach_button_pressed), compose);
7092 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7093 G_CALLBACK(attach_key_pressed), compose);
7096 gtk_drag_dest_set(attach_clist,
7097 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7098 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7099 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7100 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7101 G_CALLBACK(compose_attach_drag_received_cb),
7103 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7104 G_CALLBACK(compose_drag_drop),
7107 compose->attach_scrwin = attach_scrwin;
7108 compose->attach_clist = attach_clist;
7110 return attach_scrwin;
7113 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7114 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7116 static GtkWidget *compose_create_others(Compose *compose)
7119 GtkWidget *savemsg_checkbtn;
7120 GtkWidget *savemsg_combo;
7121 GtkWidget *savemsg_select;
7124 gchar *folderidentifier;
7126 /* Table for settings */
7127 table = gtk_table_new(3, 1, FALSE);
7128 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7129 gtk_widget_show(table);
7130 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7133 /* Save Message to folder */
7134 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7135 gtk_widget_show(savemsg_checkbtn);
7136 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7137 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7138 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7140 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7141 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7143 #if !GTK_CHECK_VERSION(2, 24, 0)
7144 savemsg_combo = gtk_combo_box_entry_new_text();
7146 savemsg_combo = gtk_combo_box_text_new_with_entry();
7148 compose->savemsg_checkbtn = savemsg_checkbtn;
7149 compose->savemsg_combo = savemsg_combo;
7150 gtk_widget_show(savemsg_combo);
7152 if (prefs_common.compose_save_to_history)
7153 #if !GTK_CHECK_VERSION(2, 24, 0)
7154 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7155 prefs_common.compose_save_to_history);
7157 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7158 prefs_common.compose_save_to_history);
7160 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7161 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7162 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7163 G_CALLBACK(compose_grab_focus_cb), compose);
7164 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7165 folderidentifier = folder_item_get_identifier(account_get_special_folder
7166 (compose->account, F_OUTBOX));
7167 compose_set_save_to(compose, folderidentifier);
7168 g_free(folderidentifier);
7171 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7172 gtk_widget_show(savemsg_select);
7173 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7174 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7175 G_CALLBACK(compose_savemsg_select_cb),
7181 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7183 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7184 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7187 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7192 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7195 path = folder_item_get_identifier(dest);
7197 compose_set_save_to(compose, path);
7201 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7202 GdkAtom clip, GtkTextIter *insert_place);
7205 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7209 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7211 if (event->button == 3) {
7213 GtkTextIter sel_start, sel_end;
7214 gboolean stuff_selected;
7216 /* move the cursor to allow GtkAspell to check the word
7217 * under the mouse */
7218 if (event->x && event->y) {
7219 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7220 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7222 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7225 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7226 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7229 stuff_selected = gtk_text_buffer_get_selection_bounds(
7231 &sel_start, &sel_end);
7233 gtk_text_buffer_place_cursor (buffer, &iter);
7234 /* reselect stuff */
7236 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7237 gtk_text_buffer_select_range(buffer,
7238 &sel_start, &sel_end);
7240 return FALSE; /* pass the event so that the right-click goes through */
7243 if (event->button == 2) {
7248 /* get the middle-click position to paste at the correct place */
7249 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7250 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7252 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7255 entry_paste_clipboard(compose, text,
7256 prefs_common.linewrap_pastes,
7257 GDK_SELECTION_PRIMARY, &iter);
7265 static void compose_spell_menu_changed(void *data)
7267 Compose *compose = (Compose *)data;
7269 GtkWidget *menuitem;
7270 GtkWidget *parent_item;
7271 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7274 if (compose->gtkaspell == NULL)
7277 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7278 "/Menu/Spelling/Options");
7280 /* setting the submenu removes /Spelling/Options from the factory
7281 * so we need to save it */
7283 if (parent_item == NULL) {
7284 parent_item = compose->aspell_options_menu;
7285 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7287 compose->aspell_options_menu = parent_item;
7289 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7291 spell_menu = g_slist_reverse(spell_menu);
7292 for (items = spell_menu;
7293 items; items = items->next) {
7294 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7295 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7296 gtk_widget_show(GTK_WIDGET(menuitem));
7298 g_slist_free(spell_menu);
7300 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7301 gtk_widget_show(parent_item);
7304 static void compose_dict_changed(void *data)
7306 Compose *compose = (Compose *) data;
7308 if(compose->gtkaspell &&
7309 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7312 gtkaspell_highlight_all(compose->gtkaspell);
7313 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7317 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7319 Compose *compose = (Compose *)data;
7320 GdkEventButton event;
7323 event.time = gtk_get_current_event_time();
7327 return text_clicked(compose->text, &event, compose);
7330 static gboolean compose_force_window_origin = TRUE;
7331 static Compose *compose_create(PrefsAccount *account,
7340 GtkWidget *handlebox;
7342 GtkWidget *notebook;
7344 GtkWidget *attach_hbox;
7345 GtkWidget *attach_lab1;
7346 GtkWidget *attach_lab2;
7351 GtkWidget *subject_hbox;
7352 GtkWidget *subject_frame;
7353 GtkWidget *subject_entry;
7357 GtkWidget *edit_vbox;
7358 GtkWidget *ruler_hbox;
7360 GtkWidget *scrolledwin;
7362 GtkTextBuffer *buffer;
7363 GtkClipboard *clipboard;
7365 UndoMain *undostruct;
7367 GtkWidget *popupmenu;
7368 GtkWidget *tmpl_menu;
7369 GtkActionGroup *action_group = NULL;
7372 GtkAspell * gtkaspell = NULL;
7375 static GdkGeometry geometry;
7377 cm_return_val_if_fail(account != NULL, NULL);
7379 debug_print("Creating compose window...\n");
7380 compose = g_new0(Compose, 1);
7382 compose->batch = batch;
7383 compose->account = account;
7384 compose->folder = folder;
7386 compose->mutex = cm_mutex_new();
7387 compose->set_cursor_pos = -1;
7389 #if !(GTK_CHECK_VERSION(2,12,0))
7390 compose->tooltips = tips;
7393 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7395 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7396 gtk_widget_set_size_request(window, prefs_common.compose_width,
7397 prefs_common.compose_height);
7399 if (!geometry.max_width) {
7400 geometry.max_width = gdk_screen_width();
7401 geometry.max_height = gdk_screen_height();
7404 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7405 &geometry, GDK_HINT_MAX_SIZE);
7406 if (!geometry.min_width) {
7407 geometry.min_width = 600;
7408 geometry.min_height = 440;
7410 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7411 &geometry, GDK_HINT_MIN_SIZE);
7413 #ifndef GENERIC_UMPC
7414 if (compose_force_window_origin)
7415 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7416 prefs_common.compose_y);
7418 g_signal_connect(G_OBJECT(window), "delete_event",
7419 G_CALLBACK(compose_delete_cb), compose);
7420 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7421 gtk_widget_realize(window);
7423 gtkut_widget_set_composer_icon(window);
7425 vbox = gtk_vbox_new(FALSE, 0);
7426 gtk_container_add(GTK_CONTAINER(window), vbox);
7428 compose->ui_manager = gtk_ui_manager_new();
7429 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7430 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7431 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7432 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7433 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7434 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7435 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7436 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7437 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7438 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7445 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7447 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7452 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7453 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7461 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7484 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7489 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7492 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7493 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7498 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7500 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7557 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)
7558 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)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7564 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)
7565 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)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7570 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)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7574 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)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7580 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)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7586 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)
7587 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)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7600 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)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7618 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7619 gtk_widget_show_all(menubar);
7621 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7622 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7624 if (prefs_common.toolbar_detachable) {
7625 handlebox = gtk_handle_box_new();
7627 handlebox = gtk_hbox_new(FALSE, 0);
7629 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7631 gtk_widget_realize(handlebox);
7632 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7635 vbox2 = gtk_vbox_new(FALSE, 2);
7636 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7637 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7640 notebook = gtk_notebook_new();
7641 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7642 gtk_widget_show(notebook);
7644 /* header labels and entries */
7645 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7646 compose_create_header(compose),
7647 gtk_label_new_with_mnemonic(_("Hea_der")));
7648 /* attachment list */
7649 attach_hbox = gtk_hbox_new(FALSE, 0);
7650 gtk_widget_show(attach_hbox);
7652 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7653 gtk_widget_show(attach_lab1);
7654 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7656 attach_lab2 = gtk_label_new("");
7657 gtk_widget_show(attach_lab2);
7658 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7660 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7661 compose_create_attach(compose),
7664 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7665 compose_create_others(compose),
7666 gtk_label_new_with_mnemonic(_("Othe_rs")));
7669 subject_hbox = gtk_hbox_new(FALSE, 0);
7670 gtk_widget_show(subject_hbox);
7672 subject_frame = gtk_frame_new(NULL);
7673 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7674 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7675 gtk_widget_show(subject_frame);
7677 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7678 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7679 gtk_widget_show(subject);
7681 label = gtk_label_new(_("Subject:"));
7682 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7683 gtk_widget_show(label);
7686 subject_entry = claws_spell_entry_new();
7688 subject_entry = gtk_entry_new();
7690 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7691 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7692 G_CALLBACK(compose_grab_focus_cb), compose);
7693 gtk_widget_show(subject_entry);
7694 compose->subject_entry = subject_entry;
7695 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7697 edit_vbox = gtk_vbox_new(FALSE, 0);
7699 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7702 ruler_hbox = gtk_hbox_new(FALSE, 0);
7703 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7705 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7706 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7707 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7711 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7712 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7713 GTK_POLICY_AUTOMATIC,
7714 GTK_POLICY_AUTOMATIC);
7715 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7717 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7719 text = gtk_text_view_new();
7720 if (prefs_common.show_compose_margin) {
7721 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7722 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7724 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7725 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7726 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7727 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7728 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7730 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7731 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7732 G_CALLBACK(compose_notebook_size_alloc), compose);
7733 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7734 G_CALLBACK(compose_edit_size_alloc),
7736 g_signal_connect(G_OBJECT(buffer), "changed",
7737 G_CALLBACK(compose_changed_cb), compose);
7738 g_signal_connect(G_OBJECT(text), "grab_focus",
7739 G_CALLBACK(compose_grab_focus_cb), compose);
7740 g_signal_connect(G_OBJECT(buffer), "insert_text",
7741 G_CALLBACK(text_inserted), compose);
7742 g_signal_connect(G_OBJECT(text), "button_press_event",
7743 G_CALLBACK(text_clicked), compose);
7744 g_signal_connect(G_OBJECT(text), "popup-menu",
7745 G_CALLBACK(compose_popup_menu), compose);
7746 g_signal_connect(G_OBJECT(subject_entry), "changed",
7747 G_CALLBACK(compose_changed_cb), compose);
7748 g_signal_connect(G_OBJECT(subject_entry), "activate",
7749 G_CALLBACK(compose_subject_entry_activated), compose);
7752 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7753 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7754 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7755 g_signal_connect(G_OBJECT(text), "drag_data_received",
7756 G_CALLBACK(compose_insert_drag_received_cb),
7758 g_signal_connect(G_OBJECT(text), "drag-drop",
7759 G_CALLBACK(compose_drag_drop),
7761 g_signal_connect(G_OBJECT(text), "key-press-event",
7762 G_CALLBACK(completion_set_focus_to_subject),
7764 gtk_widget_show_all(vbox);
7766 /* pane between attach clist and text */
7767 paned = gtk_vpaned_new();
7768 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7769 gtk_paned_add1(GTK_PANED(paned), notebook);
7770 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7771 gtk_widget_show_all(paned);
7774 if (prefs_common.textfont) {
7775 PangoFontDescription *font_desc;
7777 font_desc = pango_font_description_from_string
7778 (prefs_common.textfont);
7780 gtk_widget_modify_font(text, font_desc);
7781 pango_font_description_free(font_desc);
7785 gtk_action_group_add_actions(action_group, compose_popup_entries,
7786 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7789 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7790 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7791 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7792 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7794 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7796 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7797 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7798 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7800 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7802 undostruct = undo_init(text);
7803 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7806 address_completion_start(window);
7808 compose->window = window;
7809 compose->vbox = vbox;
7810 compose->menubar = menubar;
7811 compose->handlebox = handlebox;
7813 compose->vbox2 = vbox2;
7815 compose->paned = paned;
7817 compose->attach_label = attach_lab2;
7819 compose->notebook = notebook;
7820 compose->edit_vbox = edit_vbox;
7821 compose->ruler_hbox = ruler_hbox;
7822 compose->ruler = ruler;
7823 compose->scrolledwin = scrolledwin;
7824 compose->text = text;
7826 compose->focused_editable = NULL;
7828 compose->popupmenu = popupmenu;
7830 compose->tmpl_menu = tmpl_menu;
7832 compose->mode = mode;
7833 compose->rmode = mode;
7835 compose->targetinfo = NULL;
7836 compose->replyinfo = NULL;
7837 compose->fwdinfo = NULL;
7839 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7840 g_str_equal, (GDestroyNotify) g_free, NULL);
7842 compose->replyto = NULL;
7844 compose->bcc = NULL;
7845 compose->followup_to = NULL;
7847 compose->ml_post = NULL;
7849 compose->inreplyto = NULL;
7850 compose->references = NULL;
7851 compose->msgid = NULL;
7852 compose->boundary = NULL;
7854 compose->autowrap = prefs_common.autowrap;
7855 compose->autoindent = prefs_common.auto_indent;
7856 compose->use_signing = FALSE;
7857 compose->use_encryption = FALSE;
7858 compose->privacy_system = NULL;
7860 compose->modified = FALSE;
7862 compose->return_receipt = FALSE;
7864 compose->to_list = NULL;
7865 compose->newsgroup_list = NULL;
7867 compose->undostruct = undostruct;
7869 compose->sig_str = NULL;
7871 compose->exteditor_file = NULL;
7872 compose->exteditor_pid = -1;
7873 compose->exteditor_tag = -1;
7874 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7877 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7878 if (mode != COMPOSE_REDIRECT) {
7879 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7880 strcmp(prefs_common.dictionary, "")) {
7881 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7882 prefs_common.alt_dictionary,
7883 conv_get_locale_charset_str(),
7884 prefs_common.misspelled_col,
7885 prefs_common.check_while_typing,
7886 prefs_common.recheck_when_changing_dict,
7887 prefs_common.use_alternate,
7888 prefs_common.use_both_dicts,
7889 GTK_TEXT_VIEW(text),
7890 GTK_WINDOW(compose->window),
7891 compose_dict_changed,
7892 compose_spell_menu_changed,
7895 alertpanel_error(_("Spell checker could not "
7897 gtkaspell_checkers_strerror());
7898 gtkaspell_checkers_reset_error();
7900 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7904 compose->gtkaspell = gtkaspell;
7905 compose_spell_menu_changed(compose);
7906 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7909 compose_select_account(compose, account, TRUE);
7911 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7912 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7914 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7915 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7917 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7918 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7920 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7921 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7923 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7924 if (account->protocol != A_NNTP)
7925 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7926 prefs_common_translated_header_name("To:"));
7928 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7929 prefs_common_translated_header_name("Newsgroups:"));
7931 #ifndef USE_NEW_ADDRBOOK
7932 addressbook_set_target_compose(compose);
7934 if (mode != COMPOSE_REDIRECT)
7935 compose_set_template_menu(compose);
7937 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7940 compose_list = g_list_append(compose_list, compose);
7942 if (!prefs_common.show_ruler)
7943 gtk_widget_hide(ruler_hbox);
7945 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7948 compose->priority = PRIORITY_NORMAL;
7949 compose_update_priority_menu_item(compose);
7951 compose_set_out_encoding(compose);
7954 compose_update_actions_menu(compose);
7956 /* Privacy Systems menu */
7957 compose_update_privacy_systems_menu(compose);
7959 activate_privacy_system(compose, account, TRUE);
7960 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7962 gtk_widget_realize(window);
7964 gtk_widget_show(window);
7970 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7975 GtkWidget *optmenubox;
7978 GtkWidget *from_name = NULL;
7979 #if !(GTK_CHECK_VERSION(2,12,0))
7980 GtkTooltips *tips = compose->tooltips;
7983 gint num = 0, def_menu = 0;
7985 accounts = account_get_list();
7986 cm_return_val_if_fail(accounts != NULL, NULL);
7988 optmenubox = gtk_event_box_new();
7989 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7990 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7992 hbox = gtk_hbox_new(FALSE, 6);
7993 from_name = gtk_entry_new();
7995 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7996 G_CALLBACK(compose_grab_focus_cb), compose);
7998 for (; accounts != NULL; accounts = accounts->next, num++) {
7999 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8000 gchar *name, *from = NULL;
8002 if (ac == compose->account) def_menu = num;
8004 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8007 if (ac == compose->account) {
8008 if (ac->name && *ac->name) {
8010 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8011 from = g_strdup_printf("%s <%s>",
8013 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8015 from = g_strdup_printf("%s",
8017 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8020 COMBOBOX_ADD(menu, name, ac->account_id);
8025 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8027 g_signal_connect(G_OBJECT(optmenu), "changed",
8028 G_CALLBACK(account_activated),
8030 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8031 G_CALLBACK(compose_entry_popup_extend),
8034 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8035 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8037 CLAWS_SET_TIP(optmenubox,
8038 _("Account to use for this email"));
8039 CLAWS_SET_TIP(from_name,
8040 _("Sender address to be used"));
8042 compose->account_combo = optmenu;
8043 compose->from_name = from_name;
8048 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8050 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8051 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8052 Compose *compose = (Compose *) data;
8054 compose->priority = value;
8058 static void compose_reply_change_mode(Compose *compose,
8061 gboolean was_modified = compose->modified;
8063 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8065 cm_return_if_fail(compose->replyinfo != NULL);
8067 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8069 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8071 if (action == COMPOSE_REPLY_TO_ALL)
8073 if (action == COMPOSE_REPLY_TO_SENDER)
8075 if (action == COMPOSE_REPLY_TO_LIST)
8078 compose_remove_header_entries(compose);
8079 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8080 if (compose->account->set_autocc && compose->account->auto_cc)
8081 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8083 if (compose->account->set_autobcc && compose->account->auto_bcc)
8084 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8086 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8087 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8088 compose_show_first_last_header(compose, TRUE);
8089 compose->modified = was_modified;
8090 compose_set_title(compose);
8093 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8095 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8096 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8097 Compose *compose = (Compose *) data;
8100 compose_reply_change_mode(compose, value);
8103 static void compose_update_priority_menu_item(Compose * compose)
8105 GtkWidget *menuitem = NULL;
8106 switch (compose->priority) {
8107 case PRIORITY_HIGHEST:
8108 menuitem = gtk_ui_manager_get_widget
8109 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8112 menuitem = gtk_ui_manager_get_widget
8113 (compose->ui_manager, "/Menu/Options/Priority/High");
8115 case PRIORITY_NORMAL:
8116 menuitem = gtk_ui_manager_get_widget
8117 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8120 menuitem = gtk_ui_manager_get_widget
8121 (compose->ui_manager, "/Menu/Options/Priority/Low");
8123 case PRIORITY_LOWEST:
8124 menuitem = gtk_ui_manager_get_widget
8125 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8128 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8131 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8133 Compose *compose = (Compose *) data;
8135 gboolean can_sign = FALSE, can_encrypt = FALSE;
8137 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8139 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8142 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8143 g_free(compose->privacy_system);
8144 compose->privacy_system = NULL;
8145 if (systemid != NULL) {
8146 compose->privacy_system = g_strdup(systemid);
8148 can_sign = privacy_system_can_sign(systemid);
8149 can_encrypt = privacy_system_can_encrypt(systemid);
8152 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8154 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8155 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8158 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8160 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8161 GtkWidget *menuitem = NULL;
8162 GList *children, *amenu;
8163 gboolean can_sign = FALSE, can_encrypt = FALSE;
8164 gboolean found = FALSE;
8166 if (compose->privacy_system != NULL) {
8168 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8169 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8170 cm_return_if_fail(menuitem != NULL);
8172 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8175 while (amenu != NULL) {
8176 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8177 if (systemid != NULL) {
8178 if (strcmp(systemid, compose->privacy_system) == 0 &&
8179 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8180 menuitem = GTK_WIDGET(amenu->data);
8182 can_sign = privacy_system_can_sign(systemid);
8183 can_encrypt = privacy_system_can_encrypt(systemid);
8187 } else if (strlen(compose->privacy_system) == 0 &&
8188 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8189 menuitem = GTK_WIDGET(amenu->data);
8192 can_encrypt = FALSE;
8197 amenu = amenu->next;
8199 g_list_free(children);
8200 if (menuitem != NULL)
8201 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8203 if (warn && !found && strlen(compose->privacy_system)) {
8204 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8205 "will not be able to sign or encrypt this message."),
8206 compose->privacy_system);
8210 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8211 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8214 static void compose_set_out_encoding(Compose *compose)
8216 CharSet out_encoding;
8217 const gchar *branch = NULL;
8218 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8220 switch(out_encoding) {
8221 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8222 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8223 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8224 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8225 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8226 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8227 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8228 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8229 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8230 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8231 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8232 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8233 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8234 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8235 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8236 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8237 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8238 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8239 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8240 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8241 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8242 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8243 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8244 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8245 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8246 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8247 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8248 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8249 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8250 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8251 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8252 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8253 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8255 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8258 static void compose_set_template_menu(Compose *compose)
8260 GSList *tmpl_list, *cur;
8264 tmpl_list = template_get_config();
8266 menu = gtk_menu_new();
8268 gtk_menu_set_accel_group (GTK_MENU (menu),
8269 gtk_ui_manager_get_accel_group(compose->ui_manager));
8270 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8271 Template *tmpl = (Template *)cur->data;
8272 gchar *accel_path = NULL;
8273 item = gtk_menu_item_new_with_label(tmpl->name);
8274 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8275 g_signal_connect(G_OBJECT(item), "activate",
8276 G_CALLBACK(compose_template_activate_cb),
8278 g_object_set_data(G_OBJECT(item), "template", tmpl);
8279 gtk_widget_show(item);
8280 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8281 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8285 gtk_widget_show(menu);
8286 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8289 void compose_update_actions_menu(Compose *compose)
8291 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8294 static void compose_update_privacy_systems_menu(Compose *compose)
8296 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8297 GSList *systems, *cur;
8299 GtkWidget *system_none;
8301 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8302 GtkWidget *privacy_menu = gtk_menu_new();
8304 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8305 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8307 g_signal_connect(G_OBJECT(system_none), "activate",
8308 G_CALLBACK(compose_set_privacy_system_cb), compose);
8310 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8311 gtk_widget_show(system_none);
8313 systems = privacy_get_system_ids();
8314 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8315 gchar *systemid = cur->data;
8317 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8318 widget = gtk_radio_menu_item_new_with_label(group,
8319 privacy_system_get_name(systemid));
8320 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8321 g_strdup(systemid), g_free);
8322 g_signal_connect(G_OBJECT(widget), "activate",
8323 G_CALLBACK(compose_set_privacy_system_cb), compose);
8325 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8326 gtk_widget_show(widget);
8329 g_slist_free(systems);
8330 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8331 gtk_widget_show_all(privacy_menu);
8332 gtk_widget_show_all(privacy_menuitem);
8335 void compose_reflect_prefs_all(void)
8340 for (cur = compose_list; cur != NULL; cur = cur->next) {
8341 compose = (Compose *)cur->data;
8342 compose_set_template_menu(compose);
8346 void compose_reflect_prefs_pixmap_theme(void)
8351 for (cur = compose_list; cur != NULL; cur = cur->next) {
8352 compose = (Compose *)cur->data;
8353 toolbar_update(TOOLBAR_COMPOSE, compose);
8357 static const gchar *compose_quote_char_from_context(Compose *compose)
8359 const gchar *qmark = NULL;
8361 cm_return_val_if_fail(compose != NULL, NULL);
8363 switch (compose->mode) {
8364 /* use forward-specific quote char */
8365 case COMPOSE_FORWARD:
8366 case COMPOSE_FORWARD_AS_ATTACH:
8367 case COMPOSE_FORWARD_INLINE:
8368 if (compose->folder && compose->folder->prefs &&
8369 compose->folder->prefs->forward_with_format)
8370 qmark = compose->folder->prefs->forward_quotemark;
8371 else if (compose->account->forward_with_format)
8372 qmark = compose->account->forward_quotemark;
8374 qmark = prefs_common.fw_quotemark;
8377 /* use reply-specific quote char in all other modes */
8379 if (compose->folder && compose->folder->prefs &&
8380 compose->folder->prefs->reply_with_format)
8381 qmark = compose->folder->prefs->reply_quotemark;
8382 else if (compose->account->reply_with_format)
8383 qmark = compose->account->reply_quotemark;
8385 qmark = prefs_common.quotemark;
8389 if (qmark == NULL || *qmark == '\0')
8395 static void compose_template_apply(Compose *compose, Template *tmpl,
8399 GtkTextBuffer *buffer;
8403 gchar *parsed_str = NULL;
8404 gint cursor_pos = 0;
8405 const gchar *err_msg = _("The body of the template has an error at line %d.");
8408 /* process the body */
8410 text = GTK_TEXT_VIEW(compose->text);
8411 buffer = gtk_text_view_get_buffer(text);
8414 qmark = compose_quote_char_from_context(compose);
8416 if (compose->replyinfo != NULL) {
8419 gtk_text_buffer_set_text(buffer, "", -1);
8420 mark = gtk_text_buffer_get_insert(buffer);
8421 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8423 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8424 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8426 } else if (compose->fwdinfo != NULL) {
8429 gtk_text_buffer_set_text(buffer, "", -1);
8430 mark = gtk_text_buffer_get_insert(buffer);
8431 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8433 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8434 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8437 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8439 GtkTextIter start, end;
8442 gtk_text_buffer_get_start_iter(buffer, &start);
8443 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8444 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8446 /* clear the buffer now */
8448 gtk_text_buffer_set_text(buffer, "", -1);
8450 parsed_str = compose_quote_fmt(compose, dummyinfo,
8451 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8452 procmsg_msginfo_free( dummyinfo );
8458 gtk_text_buffer_set_text(buffer, "", -1);
8459 mark = gtk_text_buffer_get_insert(buffer);
8460 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8463 if (replace && parsed_str && compose->account->auto_sig)
8464 compose_insert_sig(compose, FALSE);
8466 if (replace && parsed_str) {
8467 gtk_text_buffer_get_start_iter(buffer, &iter);
8468 gtk_text_buffer_place_cursor(buffer, &iter);
8472 cursor_pos = quote_fmt_get_cursor_pos();
8473 compose->set_cursor_pos = cursor_pos;
8474 if (cursor_pos == -1)
8476 gtk_text_buffer_get_start_iter(buffer, &iter);
8477 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8478 gtk_text_buffer_place_cursor(buffer, &iter);
8481 /* process the other fields */
8483 compose_template_apply_fields(compose, tmpl);
8484 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8485 quote_fmt_reset_vartable();
8486 compose_changed_cb(NULL, compose);
8489 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8490 gtkaspell_highlight_all(compose->gtkaspell);
8494 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8496 MsgInfo* dummyinfo = NULL;
8497 MsgInfo *msginfo = NULL;
8500 if (compose->replyinfo != NULL)
8501 msginfo = compose->replyinfo;
8502 else if (compose->fwdinfo != NULL)
8503 msginfo = compose->fwdinfo;
8505 dummyinfo = compose_msginfo_new_from_compose(compose);
8506 msginfo = dummyinfo;
8509 if (tmpl->from && *tmpl->from != '\0') {
8511 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8512 compose->gtkaspell);
8514 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8516 quote_fmt_scan_string(tmpl->from);
8519 buf = quote_fmt_get_buffer();
8521 alertpanel_error(_("Template From format error."));
8523 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8527 if (tmpl->to && *tmpl->to != '\0') {
8529 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8530 compose->gtkaspell);
8532 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8534 quote_fmt_scan_string(tmpl->to);
8537 buf = quote_fmt_get_buffer();
8539 alertpanel_error(_("Template To format error."));
8541 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8545 if (tmpl->cc && *tmpl->cc != '\0') {
8547 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8548 compose->gtkaspell);
8550 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8552 quote_fmt_scan_string(tmpl->cc);
8555 buf = quote_fmt_get_buffer();
8557 alertpanel_error(_("Template Cc format error."));
8559 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8563 if (tmpl->bcc && *tmpl->bcc != '\0') {
8565 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8566 compose->gtkaspell);
8568 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8570 quote_fmt_scan_string(tmpl->bcc);
8573 buf = quote_fmt_get_buffer();
8575 alertpanel_error(_("Template Bcc format error."));
8577 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8581 /* process the subject */
8582 if (tmpl->subject && *tmpl->subject != '\0') {
8584 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8585 compose->gtkaspell);
8587 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8589 quote_fmt_scan_string(tmpl->subject);
8592 buf = quote_fmt_get_buffer();
8594 alertpanel_error(_("Template subject format error."));
8596 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8600 procmsg_msginfo_free( dummyinfo );
8603 static void compose_destroy(Compose *compose)
8605 GtkAllocation allocation;
8606 GtkTextBuffer *buffer;
8607 GtkClipboard *clipboard;
8609 compose_list = g_list_remove(compose_list, compose);
8611 if (compose->updating) {
8612 debug_print("danger, not destroying anything now\n");
8613 compose->deferred_destroy = TRUE;
8616 /* NOTE: address_completion_end() does nothing with the window
8617 * however this may change. */
8618 address_completion_end(compose->window);
8620 slist_free_strings_full(compose->to_list);
8621 slist_free_strings_full(compose->newsgroup_list);
8622 slist_free_strings_full(compose->header_list);
8624 slist_free_strings_full(extra_headers);
8625 extra_headers = NULL;
8627 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8629 g_hash_table_destroy(compose->email_hashtable);
8631 procmsg_msginfo_free(compose->targetinfo);
8632 procmsg_msginfo_free(compose->replyinfo);
8633 procmsg_msginfo_free(compose->fwdinfo);
8635 g_free(compose->replyto);
8636 g_free(compose->cc);
8637 g_free(compose->bcc);
8638 g_free(compose->newsgroups);
8639 g_free(compose->followup_to);
8641 g_free(compose->ml_post);
8643 g_free(compose->inreplyto);
8644 g_free(compose->references);
8645 g_free(compose->msgid);
8646 g_free(compose->boundary);
8648 g_free(compose->redirect_filename);
8649 if (compose->undostruct)
8650 undo_destroy(compose->undostruct);
8652 g_free(compose->sig_str);
8654 g_free(compose->exteditor_file);
8656 g_free(compose->orig_charset);
8658 g_free(compose->privacy_system);
8660 #ifndef USE_NEW_ADDRBOOK
8661 if (addressbook_get_target_compose() == compose)
8662 addressbook_set_target_compose(NULL);
8665 if (compose->gtkaspell) {
8666 gtkaspell_delete(compose->gtkaspell);
8667 compose->gtkaspell = NULL;
8671 if (!compose->batch) {
8672 gtk_widget_get_allocation(compose->window, &allocation);
8673 prefs_common.compose_width = allocation.width;
8674 prefs_common.compose_height = allocation.height;
8677 if (!gtk_widget_get_parent(compose->paned))
8678 gtk_widget_destroy(compose->paned);
8679 gtk_widget_destroy(compose->popupmenu);
8681 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8682 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8683 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8685 gtk_widget_destroy(compose->window);
8686 toolbar_destroy(compose->toolbar);
8687 g_free(compose->toolbar);
8688 cm_mutex_free(compose->mutex);
8692 static void compose_attach_info_free(AttachInfo *ainfo)
8694 g_free(ainfo->file);
8695 g_free(ainfo->content_type);
8696 g_free(ainfo->name);
8697 g_free(ainfo->charset);
8701 static void compose_attach_update_label(Compose *compose)
8706 GtkTreeModel *model;
8711 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8712 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8713 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8717 while(gtk_tree_model_iter_next(model, &iter))
8720 text = g_strdup_printf("(%d)", i);
8721 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8725 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8727 Compose *compose = (Compose *)data;
8728 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8729 GtkTreeSelection *selection;
8731 GtkTreeModel *model;
8733 selection = gtk_tree_view_get_selection(tree_view);
8734 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8739 for (cur = sel; cur != NULL; cur = cur->next) {
8740 GtkTreePath *path = cur->data;
8741 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8744 gtk_tree_path_free(path);
8747 for (cur = sel; cur != NULL; cur = cur->next) {
8748 GtkTreeRowReference *ref = cur->data;
8749 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8752 if (gtk_tree_model_get_iter(model, &iter, path))
8753 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8755 gtk_tree_path_free(path);
8756 gtk_tree_row_reference_free(ref);
8760 compose_attach_update_label(compose);
8763 static struct _AttachProperty
8766 GtkWidget *mimetype_entry;
8767 GtkWidget *encoding_optmenu;
8768 GtkWidget *path_entry;
8769 GtkWidget *filename_entry;
8771 GtkWidget *cancel_btn;
8774 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8776 gtk_tree_path_free((GtkTreePath *)ptr);
8779 static void compose_attach_property(GtkAction *action, gpointer data)
8781 Compose *compose = (Compose *)data;
8782 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8784 GtkComboBox *optmenu;
8785 GtkTreeSelection *selection;
8787 GtkTreeModel *model;
8790 static gboolean cancelled;
8792 /* only if one selected */
8793 selection = gtk_tree_view_get_selection(tree_view);
8794 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8797 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8801 path = (GtkTreePath *) sel->data;
8802 gtk_tree_model_get_iter(model, &iter, path);
8803 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8806 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8812 if (!attach_prop.window)
8813 compose_attach_property_create(&cancelled);
8814 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8815 gtk_widget_grab_focus(attach_prop.ok_btn);
8816 gtk_widget_show(attach_prop.window);
8817 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8818 GTK_WINDOW(compose->window));
8820 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8821 if (ainfo->encoding == ENC_UNKNOWN)
8822 combobox_select_by_data(optmenu, ENC_BASE64);
8824 combobox_select_by_data(optmenu, ainfo->encoding);
8826 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8827 ainfo->content_type ? ainfo->content_type : "");
8828 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8829 ainfo->file ? ainfo->file : "");
8830 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8831 ainfo->name ? ainfo->name : "");
8834 const gchar *entry_text;
8836 gchar *cnttype = NULL;
8843 gtk_widget_hide(attach_prop.window);
8844 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8849 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8850 if (*entry_text != '\0') {
8853 text = g_strstrip(g_strdup(entry_text));
8854 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8855 cnttype = g_strdup(text);
8858 alertpanel_error(_("Invalid MIME type."));
8864 ainfo->encoding = combobox_get_active_data(optmenu);
8866 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8867 if (*entry_text != '\0') {
8868 if (is_file_exist(entry_text) &&
8869 (size = get_file_size(entry_text)) > 0)
8870 file = g_strdup(entry_text);
8873 (_("File doesn't exist or is empty."));
8879 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8880 if (*entry_text != '\0') {
8881 g_free(ainfo->name);
8882 ainfo->name = g_strdup(entry_text);
8886 g_free(ainfo->content_type);
8887 ainfo->content_type = cnttype;
8890 g_free(ainfo->file);
8894 ainfo->size = (goffset)size;
8896 /* update tree store */
8897 text = to_human_readable(ainfo->size);
8898 gtk_tree_model_get_iter(model, &iter, path);
8899 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8900 COL_MIMETYPE, ainfo->content_type,
8902 COL_NAME, ainfo->name,
8903 COL_CHARSET, ainfo->charset,
8909 gtk_tree_path_free(path);
8912 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8914 label = gtk_label_new(str); \
8915 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8916 GTK_FILL, 0, 0, 0); \
8917 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8919 entry = gtk_entry_new(); \
8920 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8921 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8924 static void compose_attach_property_create(gboolean *cancelled)
8930 GtkWidget *mimetype_entry;
8933 GtkListStore *optmenu_menu;
8934 GtkWidget *path_entry;
8935 GtkWidget *filename_entry;
8938 GtkWidget *cancel_btn;
8939 GList *mime_type_list, *strlist;
8942 debug_print("Creating attach_property window...\n");
8944 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8945 gtk_widget_set_size_request(window, 480, -1);
8946 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8947 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8948 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8949 g_signal_connect(G_OBJECT(window), "delete_event",
8950 G_CALLBACK(attach_property_delete_event),
8952 g_signal_connect(G_OBJECT(window), "key_press_event",
8953 G_CALLBACK(attach_property_key_pressed),
8956 vbox = gtk_vbox_new(FALSE, 8);
8957 gtk_container_add(GTK_CONTAINER(window), vbox);
8959 table = gtk_table_new(4, 2, FALSE);
8960 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8961 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8962 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8964 label = gtk_label_new(_("MIME type"));
8965 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8967 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8968 #if !GTK_CHECK_VERSION(2, 24, 0)
8969 mimetype_entry = gtk_combo_box_entry_new_text();
8971 mimetype_entry = gtk_combo_box_text_new_with_entry();
8973 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8974 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8976 /* stuff with list */
8977 mime_type_list = procmime_get_mime_type_list();
8979 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8980 MimeType *type = (MimeType *) mime_type_list->data;
8983 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8985 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8988 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8989 (GCompareFunc)strcmp2);
8992 for (mime_type_list = strlist; mime_type_list != NULL;
8993 mime_type_list = mime_type_list->next) {
8994 #if !GTK_CHECK_VERSION(2, 24, 0)
8995 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8997 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8999 g_free(mime_type_list->data);
9001 g_list_free(strlist);
9002 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9003 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9005 label = gtk_label_new(_("Encoding"));
9006 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9008 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9010 hbox = gtk_hbox_new(FALSE, 0);
9011 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9012 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9014 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9015 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9017 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9018 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9019 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9020 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9021 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9023 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9025 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9026 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9028 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9029 &ok_btn, GTK_STOCK_OK,
9031 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9032 gtk_widget_grab_default(ok_btn);
9034 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9035 G_CALLBACK(attach_property_ok),
9037 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9038 G_CALLBACK(attach_property_cancel),
9041 gtk_widget_show_all(vbox);
9043 attach_prop.window = window;
9044 attach_prop.mimetype_entry = mimetype_entry;
9045 attach_prop.encoding_optmenu = optmenu;
9046 attach_prop.path_entry = path_entry;
9047 attach_prop.filename_entry = filename_entry;
9048 attach_prop.ok_btn = ok_btn;
9049 attach_prop.cancel_btn = cancel_btn;
9052 #undef SET_LABEL_AND_ENTRY
9054 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9060 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9066 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9067 gboolean *cancelled)
9075 static gboolean attach_property_key_pressed(GtkWidget *widget,
9077 gboolean *cancelled)
9079 if (event && event->keyval == GDK_KEY_Escape) {
9083 if (event && event->keyval == GDK_KEY_Return) {
9091 static void compose_exec_ext_editor(Compose *compose)
9098 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9099 G_DIR_SEPARATOR, compose);
9101 if (pipe(pipe_fds) < 0) {
9107 if ((pid = fork()) < 0) {
9114 /* close the write side of the pipe */
9117 compose->exteditor_file = g_strdup(tmp);
9118 compose->exteditor_pid = pid;
9120 compose_set_ext_editor_sensitive(compose, FALSE);
9123 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9125 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9127 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9131 } else { /* process-monitoring process */
9137 /* close the read side of the pipe */
9140 if (compose_write_body_to_file(compose, tmp) < 0) {
9141 fd_write_all(pipe_fds[1], "2\n", 2);
9145 pid_ed = compose_exec_ext_editor_real(tmp);
9147 fd_write_all(pipe_fds[1], "1\n", 2);
9151 /* wait until editor is terminated */
9152 waitpid(pid_ed, NULL, 0);
9154 fd_write_all(pipe_fds[1], "0\n", 2);
9161 #endif /* G_OS_UNIX */
9165 static gint compose_exec_ext_editor_real(const gchar *file)
9172 cm_return_val_if_fail(file != NULL, -1);
9174 if ((pid = fork()) < 0) {
9179 if (pid != 0) return pid;
9181 /* grandchild process */
9183 if (setpgid(0, getppid()))
9186 if (prefs_common_get_ext_editor_cmd() &&
9187 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9188 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9189 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9191 if (prefs_common_get_ext_editor_cmd())
9192 g_warning("External editor command-line is invalid: '%s'\n",
9193 prefs_common_get_ext_editor_cmd());
9194 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9197 cmdline = strsplit_with_quote(buf, " ", 1024);
9198 execvp(cmdline[0], cmdline);
9201 g_strfreev(cmdline);
9206 static gboolean compose_ext_editor_kill(Compose *compose)
9208 pid_t pgid = compose->exteditor_pid * -1;
9211 ret = kill(pgid, 0);
9213 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9217 msg = g_strdup_printf
9218 (_("The external editor is still working.\n"
9219 "Force terminating the process?\n"
9220 "process group id: %d"), -pgid);
9221 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9222 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9226 if (val == G_ALERTALTERNATE) {
9227 g_source_remove(compose->exteditor_tag);
9228 g_io_channel_shutdown(compose->exteditor_ch,
9230 g_io_channel_unref(compose->exteditor_ch);
9232 if (kill(pgid, SIGTERM) < 0) perror("kill");
9233 waitpid(compose->exteditor_pid, NULL, 0);
9235 g_warning("Terminated process group id: %d", -pgid);
9236 g_warning("Temporary file: %s",
9237 compose->exteditor_file);
9239 compose_set_ext_editor_sensitive(compose, TRUE);
9241 g_free(compose->exteditor_file);
9242 compose->exteditor_file = NULL;
9243 compose->exteditor_pid = -1;
9244 compose->exteditor_ch = NULL;
9245 compose->exteditor_tag = -1;
9253 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9257 Compose *compose = (Compose *)data;
9260 debug_print("Compose: input from monitoring process\n");
9262 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9264 g_io_channel_shutdown(source, FALSE, NULL);
9265 g_io_channel_unref(source);
9267 waitpid(compose->exteditor_pid, NULL, 0);
9269 if (buf[0] == '0') { /* success */
9270 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9271 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9273 gtk_text_buffer_set_text(buffer, "", -1);
9274 compose_insert_file(compose, compose->exteditor_file);
9275 compose_changed_cb(NULL, compose);
9276 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9278 if (claws_unlink(compose->exteditor_file) < 0)
9279 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9280 } else if (buf[0] == '1') { /* failed */
9281 g_warning("Couldn't exec external editor\n");
9282 if (claws_unlink(compose->exteditor_file) < 0)
9283 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9284 } else if (buf[0] == '2') {
9285 g_warning("Couldn't write to file\n");
9286 } else if (buf[0] == '3') {
9287 g_warning("Pipe read failed\n");
9290 compose_set_ext_editor_sensitive(compose, TRUE);
9292 g_free(compose->exteditor_file);
9293 compose->exteditor_file = NULL;
9294 compose->exteditor_pid = -1;
9295 compose->exteditor_ch = NULL;
9296 compose->exteditor_tag = -1;
9301 static void compose_set_ext_editor_sensitive(Compose *compose,
9304 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9305 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9306 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9307 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9308 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9309 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9310 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9311 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9313 gtk_widget_set_sensitive(compose->text, sensitive);
9314 if (compose->toolbar->send_btn)
9315 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9316 if (compose->toolbar->sendl_btn)
9317 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9318 if (compose->toolbar->draft_btn)
9319 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9320 if (compose->toolbar->insert_btn)
9321 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9322 if (compose->toolbar->sig_btn)
9323 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9324 if (compose->toolbar->exteditor_btn)
9325 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9326 if (compose->toolbar->linewrap_current_btn)
9327 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9328 if (compose->toolbar->linewrap_all_btn)
9329 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9331 #endif /* G_OS_UNIX */
9334 * compose_undo_state_changed:
9336 * Change the sensivity of the menuentries undo and redo
9338 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9339 gint redo_state, gpointer data)
9341 Compose *compose = (Compose *)data;
9343 switch (undo_state) {
9344 case UNDO_STATE_TRUE:
9345 if (!undostruct->undo_state) {
9346 undostruct->undo_state = TRUE;
9347 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9350 case UNDO_STATE_FALSE:
9351 if (undostruct->undo_state) {
9352 undostruct->undo_state = FALSE;
9353 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9356 case UNDO_STATE_UNCHANGED:
9358 case UNDO_STATE_REFRESH:
9359 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9362 g_warning("Undo state not recognized");
9366 switch (redo_state) {
9367 case UNDO_STATE_TRUE:
9368 if (!undostruct->redo_state) {
9369 undostruct->redo_state = TRUE;
9370 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9373 case UNDO_STATE_FALSE:
9374 if (undostruct->redo_state) {
9375 undostruct->redo_state = FALSE;
9376 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9379 case UNDO_STATE_UNCHANGED:
9381 case UNDO_STATE_REFRESH:
9382 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9385 g_warning("Redo state not recognized");
9390 /* callback functions */
9392 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9393 GtkAllocation *allocation,
9396 prefs_common.compose_notebook_height = allocation->height;
9399 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9400 * includes "non-client" (windows-izm) in calculation, so this calculation
9401 * may not be accurate.
9403 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9404 GtkAllocation *allocation,
9405 GtkSHRuler *shruler)
9407 if (prefs_common.show_ruler) {
9408 gint char_width = 0, char_height = 0;
9409 gint line_width_in_chars;
9411 gtkut_get_font_size(GTK_WIDGET(widget),
9412 &char_width, &char_height);
9413 line_width_in_chars =
9414 (allocation->width - allocation->x) / char_width;
9416 /* got the maximum */
9417 gtk_shruler_set_range(GTK_SHRULER(shruler),
9418 0.0, line_width_in_chars, 0);
9427 ComposePrefType type;
9428 gboolean entry_marked;
9431 static void account_activated(GtkComboBox *optmenu, gpointer data)
9433 Compose *compose = (Compose *)data;
9436 gchar *folderidentifier;
9437 gint account_id = 0;
9440 GSList *list, *saved_list = NULL;
9441 HeaderEntryState *state;
9442 GtkRcStyle *style = NULL;
9443 #if !GTK_CHECK_VERSION(3, 0, 0)
9444 static GdkColor yellow;
9445 static gboolean color_set = FALSE;
9447 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9450 /* Get ID of active account in the combo box */
9451 menu = gtk_combo_box_get_model(optmenu);
9452 gtk_combo_box_get_active_iter(optmenu, &iter);
9453 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9455 ac = account_find_from_id(account_id);
9456 cm_return_if_fail(ac != NULL);
9458 if (ac != compose->account) {
9459 compose_select_account(compose, ac, FALSE);
9461 for (list = compose->header_list; list; list = list->next) {
9462 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9464 if (hentry->type == PREF_ACCOUNT || !list->next) {
9465 compose_destroy_headerentry(compose, hentry);
9469 state = g_malloc0(sizeof(HeaderEntryState));
9470 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9471 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9472 state->entry = gtk_editable_get_chars(
9473 GTK_EDITABLE(hentry->entry), 0, -1);
9474 state->type = hentry->type;
9476 #if !GTK_CHECK_VERSION(3, 0, 0)
9478 gdk_color_parse("#f5f6be", &yellow);
9479 color_set = gdk_colormap_alloc_color(
9480 gdk_colormap_get_system(),
9481 &yellow, FALSE, TRUE);
9485 style = gtk_widget_get_modifier_style(hentry->entry);
9486 state->entry_marked = gdk_color_equal(&yellow,
9487 &style->base[GTK_STATE_NORMAL]);
9489 saved_list = g_slist_append(saved_list, state);
9490 compose_destroy_headerentry(compose, hentry);
9493 compose->header_last = NULL;
9494 g_slist_free(compose->header_list);
9495 compose->header_list = NULL;
9496 compose->header_nextrow = 1;
9497 compose_create_header_entry(compose);
9499 if (ac->set_autocc && ac->auto_cc)
9500 compose_entry_append(compose, ac->auto_cc,
9501 COMPOSE_CC, PREF_ACCOUNT);
9503 if (ac->set_autobcc && ac->auto_bcc)
9504 compose_entry_append(compose, ac->auto_bcc,
9505 COMPOSE_BCC, PREF_ACCOUNT);
9507 if (ac->set_autoreplyto && ac->auto_replyto)
9508 compose_entry_append(compose, ac->auto_replyto,
9509 COMPOSE_REPLYTO, PREF_ACCOUNT);
9511 for (list = saved_list; list; list = list->next) {
9512 state = (HeaderEntryState *) list->data;
9514 compose_add_header_entry(compose, state->header,
9515 state->entry, state->type);
9516 if (state->entry_marked)
9517 compose_entry_mark_default_to(compose, state->entry);
9519 g_free(state->header);
9520 g_free(state->entry);
9523 g_slist_free(saved_list);
9525 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9526 (ac->protocol == A_NNTP) ?
9527 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9530 /* Set message save folder */
9531 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9532 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9534 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9535 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9537 compose_set_save_to(compose, NULL);
9538 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9539 folderidentifier = folder_item_get_identifier(account_get_special_folder
9540 (compose->account, F_OUTBOX));
9541 compose_set_save_to(compose, folderidentifier);
9542 g_free(folderidentifier);
9546 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9547 GtkTreeViewColumn *column, Compose *compose)
9549 compose_attach_property(NULL, compose);
9552 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9555 Compose *compose = (Compose *)data;
9556 GtkTreeSelection *attach_selection;
9557 gint attach_nr_selected;
9559 if (!event) return FALSE;
9561 if (event->button == 3) {
9562 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9563 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9565 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9566 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9568 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9569 NULL, NULL, event->button, event->time);
9576 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9579 Compose *compose = (Compose *)data;
9581 if (!event) return FALSE;
9583 switch (event->keyval) {
9584 case GDK_KEY_Delete:
9585 compose_attach_remove_selected(NULL, compose);
9591 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9593 toolbar_comp_set_sensitive(compose, allow);
9594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9599 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9600 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9601 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9603 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9607 static void compose_send_cb(GtkAction *action, gpointer data)
9609 Compose *compose = (Compose *)data;
9611 if (prefs_common.work_offline &&
9612 !inc_offline_should_override(TRUE,
9613 _("Claws Mail needs network access in order "
9614 "to send this email.")))
9617 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9618 g_source_remove(compose->draft_timeout_tag);
9619 compose->draft_timeout_tag = -1;
9622 compose_send(compose);
9625 static void compose_send_later_cb(GtkAction *action, gpointer data)
9627 Compose *compose = (Compose *)data;
9631 compose_allow_user_actions(compose, FALSE);
9632 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9633 compose_allow_user_actions(compose, TRUE);
9637 compose_close(compose);
9638 } else if (val == -1) {
9639 alertpanel_error(_("Could not queue message."));
9640 } else if (val == -2) {
9641 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9642 } else if (val == -3) {
9643 if (privacy_peek_error())
9644 alertpanel_error(_("Could not queue message for sending:\n\n"
9645 "Signature failed: %s"), privacy_get_error());
9646 } else if (val == -4) {
9647 alertpanel_error(_("Could not queue message for sending:\n\n"
9648 "Charset conversion failed."));
9649 } else if (val == -5) {
9650 alertpanel_error(_("Could not queue message for sending:\n\n"
9651 "Couldn't get recipient encryption key."));
9652 } else if (val == -6) {
9655 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9658 #define DRAFTED_AT_EXIT "drafted_at_exit"
9659 static void compose_register_draft(MsgInfo *info)
9661 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9662 DRAFTED_AT_EXIT, NULL);
9663 FILE *fp = g_fopen(filepath, "ab");
9666 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9674 gboolean compose_draft (gpointer data, guint action)
9676 Compose *compose = (Compose *)data;
9681 MsgFlags flag = {0, 0};
9682 static gboolean lock = FALSE;
9683 MsgInfo *newmsginfo;
9685 gboolean target_locked = FALSE;
9686 gboolean err = FALSE;
9688 if (lock) return FALSE;
9690 if (compose->sending)
9693 draft = account_get_special_folder(compose->account, F_DRAFT);
9694 cm_return_val_if_fail(draft != NULL, FALSE);
9696 if (!g_mutex_trylock(compose->mutex)) {
9697 /* we don't want to lock the mutex once it's available,
9698 * because as the only other part of compose.c locking
9699 * it is compose_close - which means once unlocked,
9700 * the compose struct will be freed */
9701 debug_print("couldn't lock mutex, probably sending\n");
9707 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9708 G_DIR_SEPARATOR, compose);
9709 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9710 FILE_OP_ERROR(tmp, "fopen");
9714 /* chmod for security */
9715 if (change_file_mode_rw(fp, tmp) < 0) {
9716 FILE_OP_ERROR(tmp, "chmod");
9717 g_warning("can't change file mode\n");
9720 /* Save draft infos */
9721 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9722 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9724 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9725 gchar *savefolderid;
9727 savefolderid = compose_get_save_to(compose);
9728 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9729 g_free(savefolderid);
9731 if (compose->return_receipt) {
9732 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9734 if (compose->privacy_system) {
9735 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9736 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9737 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9740 /* Message-ID of message replying to */
9741 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9744 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9745 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9748 /* Message-ID of message forwarding to */
9749 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9752 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9753 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9757 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9758 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9760 sheaders = compose_get_manual_headers_info(compose);
9761 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9764 /* end of headers */
9765 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9772 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9776 if (fclose(fp) == EOF) {
9780 if (compose->targetinfo) {
9781 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9782 flag.perm_flags = target_locked?MSG_LOCKED:0;
9784 flag.tmp_flags = MSG_DRAFT;
9786 folder_item_scan(draft);
9787 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9788 MsgInfo *tmpinfo = NULL;
9789 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9790 if (compose->msgid) {
9791 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9794 msgnum = tmpinfo->msgnum;
9795 procmsg_msginfo_free(tmpinfo);
9796 debug_print("got draft msgnum %d from scanning\n", msgnum);
9798 debug_print("didn't get draft msgnum after scanning\n");
9801 debug_print("got draft msgnum %d from adding\n", msgnum);
9807 if (action != COMPOSE_AUTO_SAVE) {
9808 if (action != COMPOSE_DRAFT_FOR_EXIT)
9809 alertpanel_error(_("Could not save draft."));
9812 gtkut_window_popup(compose->window);
9813 val = alertpanel_full(_("Could not save draft"),
9814 _("Could not save draft.\n"
9815 "Do you want to cancel exit or discard this email?"),
9816 _("_Cancel exit"), _("_Discard email"), NULL,
9817 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9818 if (val == G_ALERTALTERNATE) {
9820 g_mutex_unlock(compose->mutex); /* must be done before closing */
9821 compose_close(compose);
9825 g_mutex_unlock(compose->mutex); /* must be done before closing */
9834 if (compose->mode == COMPOSE_REEDIT) {
9835 compose_remove_reedit_target(compose, TRUE);
9838 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9841 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9843 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9845 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9846 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9847 procmsg_msginfo_set_flags(newmsginfo, 0,
9848 MSG_HAS_ATTACHMENT);
9850 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9851 compose_register_draft(newmsginfo);
9853 procmsg_msginfo_free(newmsginfo);
9856 folder_item_scan(draft);
9858 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9860 g_mutex_unlock(compose->mutex); /* must be done before closing */
9861 compose_close(compose);
9867 path = folder_item_fetch_msg(draft, msgnum);
9869 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9872 if (g_stat(path, &s) < 0) {
9873 FILE_OP_ERROR(path, "stat");
9879 procmsg_msginfo_free(compose->targetinfo);
9880 compose->targetinfo = procmsg_msginfo_new();
9881 compose->targetinfo->msgnum = msgnum;
9882 compose->targetinfo->size = (goffset)s.st_size;
9883 compose->targetinfo->mtime = s.st_mtime;
9884 compose->targetinfo->folder = draft;
9886 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9887 compose->mode = COMPOSE_REEDIT;
9889 if (action == COMPOSE_AUTO_SAVE) {
9890 compose->autosaved_draft = compose->targetinfo;
9892 compose->modified = FALSE;
9893 compose_set_title(compose);
9897 g_mutex_unlock(compose->mutex);
9901 void compose_clear_exit_drafts(void)
9903 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9904 DRAFTED_AT_EXIT, NULL);
9905 if (is_file_exist(filepath))
9906 claws_unlink(filepath);
9911 void compose_reopen_exit_drafts(void)
9913 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9914 DRAFTED_AT_EXIT, NULL);
9915 FILE *fp = g_fopen(filepath, "rb");
9919 while (fgets(buf, sizeof(buf), fp)) {
9920 gchar **parts = g_strsplit(buf, "\t", 2);
9921 const gchar *folder = parts[0];
9922 int msgnum = parts[1] ? atoi(parts[1]):-1;
9924 if (folder && *folder && msgnum > -1) {
9925 FolderItem *item = folder_find_item_from_identifier(folder);
9926 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9928 compose_reedit(info, FALSE);
9935 compose_clear_exit_drafts();
9938 static void compose_save_cb(GtkAction *action, gpointer data)
9940 Compose *compose = (Compose *)data;
9941 compose_draft(compose, COMPOSE_KEEP_EDITING);
9942 compose->rmode = COMPOSE_REEDIT;
9945 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9947 if (compose && file_list) {
9950 for ( tmp = file_list; tmp; tmp = tmp->next) {
9951 gchar *file = (gchar *) tmp->data;
9952 gchar *utf8_filename = conv_filename_to_utf8(file);
9953 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9954 compose_changed_cb(NULL, compose);
9959 g_free(utf8_filename);
9964 static void compose_attach_cb(GtkAction *action, gpointer data)
9966 Compose *compose = (Compose *)data;
9969 if (compose->redirect_filename != NULL)
9972 /* Set focus_window properly, in case we were called via popup menu,
9973 * which unsets it (via focus_out_event callback on compose window). */
9974 manage_window_focus_in(compose->window, NULL, NULL);
9976 file_list = filesel_select_multiple_files_open(_("Select file"));
9979 compose_attach_from_list(compose, file_list, TRUE);
9980 g_list_free(file_list);
9984 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9986 Compose *compose = (Compose *)data;
9988 gint files_inserted = 0;
9990 file_list = filesel_select_multiple_files_open(_("Select file"));
9995 for ( tmp = file_list; tmp; tmp = tmp->next) {
9996 gchar *file = (gchar *) tmp->data;
9997 gchar *filedup = g_strdup(file);
9998 gchar *shortfile = g_path_get_basename(filedup);
9999 ComposeInsertResult res;
10000 /* insert the file if the file is short or if the user confirmed that
10001 he/she wants to insert the large file */
10002 res = compose_insert_file(compose, file);
10003 if (res == COMPOSE_INSERT_READ_ERROR) {
10004 alertpanel_error(_("File '%s' could not be read."), shortfile);
10005 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10006 alertpanel_error(_("File '%s' contained invalid characters\n"
10007 "for the current encoding, insertion may be incorrect."),
10009 } else if (res == COMPOSE_INSERT_SUCCESS)
10016 g_list_free(file_list);
10020 if (files_inserted > 0 && compose->gtkaspell &&
10021 compose->gtkaspell->check_while_typing)
10022 gtkaspell_highlight_all(compose->gtkaspell);
10026 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10028 Compose *compose = (Compose *)data;
10030 compose_insert_sig(compose, FALSE);
10033 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10035 Compose *compose = (Compose *)data;
10037 compose_insert_sig(compose, TRUE);
10040 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10044 Compose *compose = (Compose *)data;
10046 gtkut_widget_get_uposition(widget, &x, &y);
10047 if (!compose->batch) {
10048 prefs_common.compose_x = x;
10049 prefs_common.compose_y = y;
10051 if (compose->sending || compose->updating)
10053 compose_close_cb(NULL, compose);
10057 void compose_close_toolbar(Compose *compose)
10059 compose_close_cb(NULL, compose);
10062 static gboolean compose_can_autosave(Compose *compose)
10064 if (compose->privacy_system && compose->use_encryption)
10065 return prefs_common.autosave && prefs_common.autosave_encrypted;
10067 return prefs_common.autosave;
10070 static void compose_close_cb(GtkAction *action, gpointer data)
10072 Compose *compose = (Compose *)data;
10076 if (compose->exteditor_tag != -1) {
10077 if (!compose_ext_editor_kill(compose))
10082 if (compose->modified) {
10083 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10084 if (!g_mutex_trylock(compose->mutex)) {
10085 /* we don't want to lock the mutex once it's available,
10086 * because as the only other part of compose.c locking
10087 * it is compose_close - which means once unlocked,
10088 * the compose struct will be freed */
10089 debug_print("couldn't lock mutex, probably sending\n");
10093 val = alertpanel(_("Discard message"),
10094 _("This message has been modified. Discard it?"),
10095 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10097 val = alertpanel(_("Save changes"),
10098 _("This message has been modified. Save the latest changes?"),
10099 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10101 g_mutex_unlock(compose->mutex);
10103 case G_ALERTDEFAULT:
10104 if (compose_can_autosave(compose) && !reedit)
10105 compose_remove_draft(compose);
10107 case G_ALERTALTERNATE:
10108 compose_draft(data, COMPOSE_QUIT_EDITING);
10115 compose_close(compose);
10118 static void compose_print_cb(GtkAction *action, gpointer data)
10120 Compose *compose = (Compose *) data;
10122 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10123 if (compose->targetinfo)
10124 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10127 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10129 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10130 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10131 Compose *compose = (Compose *) data;
10134 compose->out_encoding = (CharSet)value;
10137 static void compose_address_cb(GtkAction *action, gpointer data)
10139 Compose *compose = (Compose *)data;
10141 #ifndef USE_NEW_ADDRBOOK
10142 addressbook_open(compose);
10144 GError* error = NULL;
10145 addressbook_connect_signals(compose);
10146 addressbook_dbus_open(TRUE, &error);
10148 g_warning("%s", error->message);
10149 g_error_free(error);
10154 static void about_show_cb(GtkAction *action, gpointer data)
10159 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10161 Compose *compose = (Compose *)data;
10166 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10167 cm_return_if_fail(tmpl != NULL);
10169 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10171 val = alertpanel(_("Apply template"), msg,
10172 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10175 if (val == G_ALERTDEFAULT)
10176 compose_template_apply(compose, tmpl, TRUE);
10177 else if (val == G_ALERTALTERNATE)
10178 compose_template_apply(compose, tmpl, FALSE);
10181 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10183 Compose *compose = (Compose *)data;
10185 compose_exec_ext_editor(compose);
10188 static void compose_undo_cb(GtkAction *action, gpointer data)
10190 Compose *compose = (Compose *)data;
10191 gboolean prev_autowrap = compose->autowrap;
10193 compose->autowrap = FALSE;
10194 undo_undo(compose->undostruct);
10195 compose->autowrap = prev_autowrap;
10198 static void compose_redo_cb(GtkAction *action, gpointer data)
10200 Compose *compose = (Compose *)data;
10201 gboolean prev_autowrap = compose->autowrap;
10203 compose->autowrap = FALSE;
10204 undo_redo(compose->undostruct);
10205 compose->autowrap = prev_autowrap;
10208 static void entry_cut_clipboard(GtkWidget *entry)
10210 if (GTK_IS_EDITABLE(entry))
10211 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10212 else if (GTK_IS_TEXT_VIEW(entry))
10213 gtk_text_buffer_cut_clipboard(
10214 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10215 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10219 static void entry_copy_clipboard(GtkWidget *entry)
10221 if (GTK_IS_EDITABLE(entry))
10222 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10223 else if (GTK_IS_TEXT_VIEW(entry))
10224 gtk_text_buffer_copy_clipboard(
10225 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10226 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10229 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10230 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10232 if (GTK_IS_TEXT_VIEW(entry)) {
10233 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10234 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10235 GtkTextIter start_iter, end_iter;
10237 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10239 if (contents == NULL)
10242 /* we shouldn't delete the selection when middle-click-pasting, or we
10243 * can't mid-click-paste our own selection */
10244 if (clip != GDK_SELECTION_PRIMARY) {
10245 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10246 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10249 if (insert_place == NULL) {
10250 /* if insert_place isn't specified, insert at the cursor.
10251 * used for Ctrl-V pasting */
10252 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10253 start = gtk_text_iter_get_offset(&start_iter);
10254 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10256 /* if insert_place is specified, paste here.
10257 * used for mid-click-pasting */
10258 start = gtk_text_iter_get_offset(insert_place);
10259 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10260 if (prefs_common.primary_paste_unselects)
10261 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10265 /* paste unwrapped: mark the paste so it's not wrapped later */
10266 end = start + strlen(contents);
10267 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10268 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10269 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10270 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10271 /* rewrap paragraph now (after a mid-click-paste) */
10272 mark_start = gtk_text_buffer_get_insert(buffer);
10273 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10274 gtk_text_iter_backward_char(&start_iter);
10275 compose_beautify_paragraph(compose, &start_iter, TRUE);
10277 } else if (GTK_IS_EDITABLE(entry))
10278 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10280 compose->modified = TRUE;
10283 static void entry_allsel(GtkWidget *entry)
10285 if (GTK_IS_EDITABLE(entry))
10286 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10287 else if (GTK_IS_TEXT_VIEW(entry)) {
10288 GtkTextIter startiter, enditer;
10289 GtkTextBuffer *textbuf;
10291 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10292 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10293 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10295 gtk_text_buffer_move_mark_by_name(textbuf,
10296 "selection_bound", &startiter);
10297 gtk_text_buffer_move_mark_by_name(textbuf,
10298 "insert", &enditer);
10302 static void compose_cut_cb(GtkAction *action, gpointer data)
10304 Compose *compose = (Compose *)data;
10305 if (compose->focused_editable
10306 #ifndef GENERIC_UMPC
10307 && gtk_widget_has_focus(compose->focused_editable)
10310 entry_cut_clipboard(compose->focused_editable);
10313 static void compose_copy_cb(GtkAction *action, gpointer data)
10315 Compose *compose = (Compose *)data;
10316 if (compose->focused_editable
10317 #ifndef GENERIC_UMPC
10318 && gtk_widget_has_focus(compose->focused_editable)
10321 entry_copy_clipboard(compose->focused_editable);
10324 static void compose_paste_cb(GtkAction *action, gpointer data)
10326 Compose *compose = (Compose *)data;
10327 gint prev_autowrap;
10328 GtkTextBuffer *buffer;
10330 if (compose->focused_editable &&
10331 #ifndef GENERIC_UMPC
10332 gtk_widget_has_focus(compose->focused_editable)
10335 entry_paste_clipboard(compose, compose->focused_editable,
10336 prefs_common.linewrap_pastes,
10337 GDK_SELECTION_CLIPBOARD, NULL);
10342 #ifndef GENERIC_UMPC
10343 gtk_widget_has_focus(compose->text) &&
10345 compose->gtkaspell &&
10346 compose->gtkaspell->check_while_typing)
10347 gtkaspell_highlight_all(compose->gtkaspell);
10351 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10353 Compose *compose = (Compose *)data;
10354 gint wrap_quote = prefs_common.linewrap_quote;
10355 if (compose->focused_editable
10356 #ifndef GENERIC_UMPC
10357 && gtk_widget_has_focus(compose->focused_editable)
10360 /* let text_insert() (called directly or at a later time
10361 * after the gtk_editable_paste_clipboard) know that
10362 * text is to be inserted as a quotation. implemented
10363 * by using a simple refcount... */
10364 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10365 G_OBJECT(compose->focused_editable),
10366 "paste_as_quotation"));
10367 g_object_set_data(G_OBJECT(compose->focused_editable),
10368 "paste_as_quotation",
10369 GINT_TO_POINTER(paste_as_quotation + 1));
10370 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10371 entry_paste_clipboard(compose, compose->focused_editable,
10372 prefs_common.linewrap_pastes,
10373 GDK_SELECTION_CLIPBOARD, NULL);
10374 prefs_common.linewrap_quote = wrap_quote;
10378 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10380 Compose *compose = (Compose *)data;
10381 gint prev_autowrap;
10382 GtkTextBuffer *buffer;
10384 if (compose->focused_editable
10385 #ifndef GENERIC_UMPC
10386 && gtk_widget_has_focus(compose->focused_editable)
10389 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10390 GDK_SELECTION_CLIPBOARD, NULL);
10395 #ifndef GENERIC_UMPC
10396 gtk_widget_has_focus(compose->text) &&
10398 compose->gtkaspell &&
10399 compose->gtkaspell->check_while_typing)
10400 gtkaspell_highlight_all(compose->gtkaspell);
10404 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10406 Compose *compose = (Compose *)data;
10407 gint prev_autowrap;
10408 GtkTextBuffer *buffer;
10410 if (compose->focused_editable
10411 #ifndef GENERIC_UMPC
10412 && gtk_widget_has_focus(compose->focused_editable)
10415 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10416 GDK_SELECTION_CLIPBOARD, NULL);
10421 #ifndef GENERIC_UMPC
10422 gtk_widget_has_focus(compose->text) &&
10424 compose->gtkaspell &&
10425 compose->gtkaspell->check_while_typing)
10426 gtkaspell_highlight_all(compose->gtkaspell);
10430 static void compose_allsel_cb(GtkAction *action, gpointer data)
10432 Compose *compose = (Compose *)data;
10433 if (compose->focused_editable
10434 #ifndef GENERIC_UMPC
10435 && gtk_widget_has_focus(compose->focused_editable)
10438 entry_allsel(compose->focused_editable);
10441 static void textview_move_beginning_of_line (GtkTextView *text)
10443 GtkTextBuffer *buffer;
10447 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10449 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10450 mark = gtk_text_buffer_get_insert(buffer);
10451 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10452 gtk_text_iter_set_line_offset(&ins, 0);
10453 gtk_text_buffer_place_cursor(buffer, &ins);
10456 static void textview_move_forward_character (GtkTextView *text)
10458 GtkTextBuffer *buffer;
10462 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10464 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10465 mark = gtk_text_buffer_get_insert(buffer);
10466 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10467 if (gtk_text_iter_forward_cursor_position(&ins))
10468 gtk_text_buffer_place_cursor(buffer, &ins);
10471 static void textview_move_backward_character (GtkTextView *text)
10473 GtkTextBuffer *buffer;
10477 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10479 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10480 mark = gtk_text_buffer_get_insert(buffer);
10481 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10482 if (gtk_text_iter_backward_cursor_position(&ins))
10483 gtk_text_buffer_place_cursor(buffer, &ins);
10486 static void textview_move_forward_word (GtkTextView *text)
10488 GtkTextBuffer *buffer;
10493 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10495 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10496 mark = gtk_text_buffer_get_insert(buffer);
10497 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10498 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10499 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10500 gtk_text_iter_backward_word_start(&ins);
10501 gtk_text_buffer_place_cursor(buffer, &ins);
10505 static void textview_move_backward_word (GtkTextView *text)
10507 GtkTextBuffer *buffer;
10511 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10513 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10514 mark = gtk_text_buffer_get_insert(buffer);
10515 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10516 if (gtk_text_iter_backward_word_starts(&ins, 1))
10517 gtk_text_buffer_place_cursor(buffer, &ins);
10520 static void textview_move_end_of_line (GtkTextView *text)
10522 GtkTextBuffer *buffer;
10526 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10528 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10529 mark = gtk_text_buffer_get_insert(buffer);
10530 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10531 if (gtk_text_iter_forward_to_line_end(&ins))
10532 gtk_text_buffer_place_cursor(buffer, &ins);
10535 static void textview_move_next_line (GtkTextView *text)
10537 GtkTextBuffer *buffer;
10542 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10544 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10545 mark = gtk_text_buffer_get_insert(buffer);
10546 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10547 offset = gtk_text_iter_get_line_offset(&ins);
10548 if (gtk_text_iter_forward_line(&ins)) {
10549 gtk_text_iter_set_line_offset(&ins, offset);
10550 gtk_text_buffer_place_cursor(buffer, &ins);
10554 static void textview_move_previous_line (GtkTextView *text)
10556 GtkTextBuffer *buffer;
10561 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10563 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10564 mark = gtk_text_buffer_get_insert(buffer);
10565 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10566 offset = gtk_text_iter_get_line_offset(&ins);
10567 if (gtk_text_iter_backward_line(&ins)) {
10568 gtk_text_iter_set_line_offset(&ins, offset);
10569 gtk_text_buffer_place_cursor(buffer, &ins);
10573 static void textview_delete_forward_character (GtkTextView *text)
10575 GtkTextBuffer *buffer;
10577 GtkTextIter ins, end_iter;
10579 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10581 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10582 mark = gtk_text_buffer_get_insert(buffer);
10583 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10585 if (gtk_text_iter_forward_char(&end_iter)) {
10586 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10590 static void textview_delete_backward_character (GtkTextView *text)
10592 GtkTextBuffer *buffer;
10594 GtkTextIter ins, end_iter;
10596 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10598 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10599 mark = gtk_text_buffer_get_insert(buffer);
10600 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10602 if (gtk_text_iter_backward_char(&end_iter)) {
10603 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10607 static void textview_delete_forward_word (GtkTextView *text)
10609 GtkTextBuffer *buffer;
10611 GtkTextIter ins, end_iter;
10613 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10615 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10616 mark = gtk_text_buffer_get_insert(buffer);
10617 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10619 if (gtk_text_iter_forward_word_end(&end_iter)) {
10620 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10624 static void textview_delete_backward_word (GtkTextView *text)
10626 GtkTextBuffer *buffer;
10628 GtkTextIter ins, end_iter;
10630 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10632 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10633 mark = gtk_text_buffer_get_insert(buffer);
10634 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10636 if (gtk_text_iter_backward_word_start(&end_iter)) {
10637 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10641 static void textview_delete_line (GtkTextView *text)
10643 GtkTextBuffer *buffer;
10645 GtkTextIter ins, start_iter, end_iter;
10647 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10649 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10650 mark = gtk_text_buffer_get_insert(buffer);
10651 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10654 gtk_text_iter_set_line_offset(&start_iter, 0);
10657 if (gtk_text_iter_ends_line(&end_iter)){
10658 if (!gtk_text_iter_forward_char(&end_iter))
10659 gtk_text_iter_backward_char(&start_iter);
10662 gtk_text_iter_forward_to_line_end(&end_iter);
10663 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10666 static void textview_delete_to_line_end (GtkTextView *text)
10668 GtkTextBuffer *buffer;
10670 GtkTextIter ins, end_iter;
10672 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10674 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10675 mark = gtk_text_buffer_get_insert(buffer);
10676 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10678 if (gtk_text_iter_ends_line(&end_iter))
10679 gtk_text_iter_forward_char(&end_iter);
10681 gtk_text_iter_forward_to_line_end(&end_iter);
10682 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10685 #define DO_ACTION(name, act) { \
10686 if(!strcmp(name, a_name)) { \
10690 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10692 const gchar *a_name = gtk_action_get_name(action);
10693 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10694 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10695 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10696 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10697 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10698 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10699 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10700 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10701 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10702 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10703 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10704 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10705 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10706 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10710 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10712 Compose *compose = (Compose *)data;
10713 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10714 ComposeCallAdvancedAction action = -1;
10716 action = compose_call_advanced_action_from_path(gaction);
10719 void (*do_action) (GtkTextView *text);
10720 } action_table[] = {
10721 {textview_move_beginning_of_line},
10722 {textview_move_forward_character},
10723 {textview_move_backward_character},
10724 {textview_move_forward_word},
10725 {textview_move_backward_word},
10726 {textview_move_end_of_line},
10727 {textview_move_next_line},
10728 {textview_move_previous_line},
10729 {textview_delete_forward_character},
10730 {textview_delete_backward_character},
10731 {textview_delete_forward_word},
10732 {textview_delete_backward_word},
10733 {textview_delete_line},
10734 {textview_delete_to_line_end}
10737 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10739 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10740 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10741 if (action_table[action].do_action)
10742 action_table[action].do_action(text);
10744 g_warning("Not implemented yet.");
10748 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10750 GtkAllocation allocation;
10754 if (GTK_IS_EDITABLE(widget)) {
10755 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10756 gtk_editable_set_position(GTK_EDITABLE(widget),
10759 if ((parent = gtk_widget_get_parent(widget))
10760 && (parent = gtk_widget_get_parent(parent))
10761 && (parent = gtk_widget_get_parent(parent))) {
10762 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10763 gtk_widget_get_allocation(widget, &allocation);
10764 gint y = allocation.y;
10765 gint height = allocation.height;
10766 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10767 (GTK_SCROLLED_WINDOW(parent));
10769 gfloat value = gtk_adjustment_get_value(shown);
10770 gfloat upper = gtk_adjustment_get_upper(shown);
10771 gfloat page_size = gtk_adjustment_get_page_size(shown);
10772 if (y < (int)value) {
10773 gtk_adjustment_set_value(shown, y - 1);
10775 if ((y + height) > ((int)value + (int)page_size)) {
10776 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10777 gtk_adjustment_set_value(shown,
10778 y + height - (int)page_size - 1);
10780 gtk_adjustment_set_value(shown,
10781 (int)upper - (int)page_size - 1);
10788 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10789 compose->focused_editable = widget;
10791 #ifdef GENERIC_UMPC
10792 if (GTK_IS_TEXT_VIEW(widget)
10793 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10794 g_object_ref(compose->notebook);
10795 g_object_ref(compose->edit_vbox);
10796 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10797 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10798 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10799 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10800 g_object_unref(compose->notebook);
10801 g_object_unref(compose->edit_vbox);
10802 g_signal_handlers_block_by_func(G_OBJECT(widget),
10803 G_CALLBACK(compose_grab_focus_cb),
10805 gtk_widget_grab_focus(widget);
10806 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10807 G_CALLBACK(compose_grab_focus_cb),
10809 } else if (!GTK_IS_TEXT_VIEW(widget)
10810 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10811 g_object_ref(compose->notebook);
10812 g_object_ref(compose->edit_vbox);
10813 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10814 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10815 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10816 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10817 g_object_unref(compose->notebook);
10818 g_object_unref(compose->edit_vbox);
10819 g_signal_handlers_block_by_func(G_OBJECT(widget),
10820 G_CALLBACK(compose_grab_focus_cb),
10822 gtk_widget_grab_focus(widget);
10823 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10824 G_CALLBACK(compose_grab_focus_cb),
10830 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10832 compose->modified = TRUE;
10833 // compose_beautify_paragraph(compose, NULL, TRUE);
10834 #ifndef GENERIC_UMPC
10835 compose_set_title(compose);
10839 static void compose_wrap_cb(GtkAction *action, gpointer data)
10841 Compose *compose = (Compose *)data;
10842 compose_beautify_paragraph(compose, NULL, TRUE);
10845 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10847 Compose *compose = (Compose *)data;
10848 compose_wrap_all_full(compose, TRUE);
10851 static void compose_find_cb(GtkAction *action, gpointer data)
10853 Compose *compose = (Compose *)data;
10855 message_search_compose(compose);
10858 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10861 Compose *compose = (Compose *)data;
10862 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10863 if (compose->autowrap)
10864 compose_wrap_all_full(compose, TRUE);
10865 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10868 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10871 Compose *compose = (Compose *)data;
10872 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10875 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10877 Compose *compose = (Compose *)data;
10879 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10882 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10884 Compose *compose = (Compose *)data;
10886 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10889 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10891 g_free(compose->privacy_system);
10893 compose->privacy_system = g_strdup(account->default_privacy_system);
10894 compose_update_privacy_system_menu_item(compose, warn);
10897 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10899 Compose *compose = (Compose *)data;
10901 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10902 gtk_widget_show(compose->ruler_hbox);
10903 prefs_common.show_ruler = TRUE;
10905 gtk_widget_hide(compose->ruler_hbox);
10906 gtk_widget_queue_resize(compose->edit_vbox);
10907 prefs_common.show_ruler = FALSE;
10911 static void compose_attach_drag_received_cb (GtkWidget *widget,
10912 GdkDragContext *context,
10915 GtkSelectionData *data,
10918 gpointer user_data)
10920 Compose *compose = (Compose *)user_data;
10924 type = gtk_selection_data_get_data_type(data);
10925 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10927 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10929 ) && gtk_drag_get_source_widget(context) !=
10930 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10931 list = uri_list_extract_filenames(
10932 (const gchar *)gtk_selection_data_get_data(data));
10933 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10934 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10935 compose_attach_append
10936 (compose, (const gchar *)tmp->data,
10937 utf8_filename, NULL, NULL);
10938 g_free(utf8_filename);
10940 if (list) compose_changed_cb(NULL, compose);
10941 list_free_strings(list);
10943 } else if (gtk_drag_get_source_widget(context)
10944 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10945 /* comes from our summaryview */
10946 SummaryView * summaryview = NULL;
10947 GSList * list = NULL, *cur = NULL;
10949 if (mainwindow_get_mainwindow())
10950 summaryview = mainwindow_get_mainwindow()->summaryview;
10953 list = summary_get_selected_msg_list(summaryview);
10955 for (cur = list; cur; cur = cur->next) {
10956 MsgInfo *msginfo = (MsgInfo *)cur->data;
10957 gchar *file = NULL;
10959 file = procmsg_get_message_file_full(msginfo,
10962 compose_attach_append(compose, (const gchar *)file,
10963 (const gchar *)file, "message/rfc822", NULL);
10967 g_slist_free(list);
10971 static gboolean compose_drag_drop(GtkWidget *widget,
10972 GdkDragContext *drag_context,
10974 guint time, gpointer user_data)
10976 /* not handling this signal makes compose_insert_drag_received_cb
10981 static gboolean completion_set_focus_to_subject
10982 (GtkWidget *widget,
10983 GdkEventKey *event,
10986 cm_return_val_if_fail(compose != NULL, FALSE);
10988 /* make backtab move to subject field */
10989 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10990 gtk_widget_grab_focus(compose->subject_entry);
10996 static void compose_insert_drag_received_cb (GtkWidget *widget,
10997 GdkDragContext *drag_context,
11000 GtkSelectionData *data,
11003 gpointer user_data)
11005 Compose *compose = (Compose *)user_data;
11009 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11011 type = gtk_selection_data_get_data_type(data);
11013 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11015 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11017 AlertValue val = G_ALERTDEFAULT;
11018 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11020 list = uri_list_extract_filenames(ddata);
11021 if (list == NULL && strstr(ddata, "://")) {
11022 /* Assume a list of no files, and data has ://, is a remote link */
11023 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11024 gchar *tmpfile = get_tmp_file();
11025 str_write_to_file(tmpdata, tmpfile);
11027 compose_insert_file(compose, tmpfile);
11028 claws_unlink(tmpfile);
11030 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11031 compose_beautify_paragraph(compose, NULL, TRUE);
11034 switch (prefs_common.compose_dnd_mode) {
11035 case COMPOSE_DND_ASK:
11036 val = alertpanel_full(_("Insert or attach?"),
11037 _("Do you want to insert the contents of the file(s) "
11038 "into the message body, or attach it to the email?"),
11039 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11040 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11042 case COMPOSE_DND_INSERT:
11043 val = G_ALERTALTERNATE;
11045 case COMPOSE_DND_ATTACH:
11046 val = G_ALERTOTHER;
11049 /* unexpected case */
11050 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11053 if (val & G_ALERTDISABLE) {
11054 val &= ~G_ALERTDISABLE;
11055 /* remember what action to perform by default, only if we don't click Cancel */
11056 if (val == G_ALERTALTERNATE)
11057 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11058 else if (val == G_ALERTOTHER)
11059 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11062 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11063 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11064 list_free_strings(list);
11067 } else if (val == G_ALERTOTHER) {
11068 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11069 list_free_strings(list);
11074 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11075 compose_insert_file(compose, (const gchar *)tmp->data);
11077 list_free_strings(list);
11079 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11084 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11087 static void compose_header_drag_received_cb (GtkWidget *widget,
11088 GdkDragContext *drag_context,
11091 GtkSelectionData *data,
11094 gpointer user_data)
11096 GtkEditable *entry = (GtkEditable *)user_data;
11097 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11099 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11102 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11103 gchar *decoded=g_new(gchar, strlen(email));
11106 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11107 gtk_editable_delete_text(entry, 0, -1);
11108 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11109 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11113 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11116 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11118 Compose *compose = (Compose *)data;
11120 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11121 compose->return_receipt = TRUE;
11123 compose->return_receipt = FALSE;
11126 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11128 Compose *compose = (Compose *)data;
11130 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11131 compose->remove_references = TRUE;
11133 compose->remove_references = FALSE;
11136 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11137 ComposeHeaderEntry *headerentry)
11139 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11143 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11144 GdkEventKey *event,
11145 ComposeHeaderEntry *headerentry)
11147 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11148 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11149 !(event->state & GDK_MODIFIER_MASK) &&
11150 (event->keyval == GDK_KEY_BackSpace) &&
11151 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11152 gtk_container_remove
11153 (GTK_CONTAINER(headerentry->compose->header_table),
11154 headerentry->combo);
11155 gtk_container_remove
11156 (GTK_CONTAINER(headerentry->compose->header_table),
11157 headerentry->entry);
11158 headerentry->compose->header_list =
11159 g_slist_remove(headerentry->compose->header_list,
11161 g_free(headerentry);
11162 } else if (event->keyval == GDK_KEY_Tab) {
11163 if (headerentry->compose->header_last == headerentry) {
11164 /* Override default next focus, and give it to subject_entry
11165 * instead of notebook tabs
11167 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11168 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11175 static gboolean scroll_postpone(gpointer data)
11177 Compose *compose = (Compose *)data;
11179 cm_return_val_if_fail(!compose->batch, FALSE);
11181 GTK_EVENTS_FLUSH();
11182 compose_show_first_last_header(compose, FALSE);
11186 static void compose_headerentry_changed_cb(GtkWidget *entry,
11187 ComposeHeaderEntry *headerentry)
11189 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11190 compose_create_header_entry(headerentry->compose);
11191 g_signal_handlers_disconnect_matched
11192 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11193 0, 0, NULL, NULL, headerentry);
11195 if (!headerentry->compose->batch)
11196 g_timeout_add(0, scroll_postpone, headerentry->compose);
11200 static gboolean compose_defer_auto_save_draft(Compose *compose)
11202 compose->draft_timeout_tag = -1;
11203 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11207 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11209 GtkAdjustment *vadj;
11211 cm_return_if_fail(compose);
11212 cm_return_if_fail(!compose->batch);
11213 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11214 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11215 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11216 gtk_widget_get_parent(compose->header_table)));
11217 gtk_adjustment_set_value(vadj, (show_first ?
11218 gtk_adjustment_get_lower(vadj) :
11219 (gtk_adjustment_get_upper(vadj) -
11220 gtk_adjustment_get_page_size(vadj))));
11221 gtk_adjustment_changed(vadj);
11224 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11225 const gchar *text, gint len, Compose *compose)
11227 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11228 (G_OBJECT(compose->text), "paste_as_quotation"));
11231 cm_return_if_fail(text != NULL);
11233 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11234 G_CALLBACK(text_inserted),
11236 if (paste_as_quotation) {
11238 const gchar *qmark;
11240 GtkTextIter start_iter;
11243 len = strlen(text);
11245 new_text = g_strndup(text, len);
11247 qmark = compose_quote_char_from_context(compose);
11249 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11250 gtk_text_buffer_place_cursor(buffer, iter);
11252 pos = gtk_text_iter_get_offset(iter);
11254 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11255 _("Quote format error at line %d."));
11256 quote_fmt_reset_vartable();
11258 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11259 GINT_TO_POINTER(paste_as_quotation - 1));
11261 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11262 gtk_text_buffer_place_cursor(buffer, iter);
11263 gtk_text_buffer_delete_mark(buffer, mark);
11265 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11266 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11267 compose_beautify_paragraph(compose, &start_iter, FALSE);
11268 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11269 gtk_text_buffer_delete_mark(buffer, mark);
11271 if (strcmp(text, "\n") || compose->automatic_break
11272 || gtk_text_iter_starts_line(iter)) {
11273 GtkTextIter before_ins;
11274 gtk_text_buffer_insert(buffer, iter, text, len);
11275 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11276 before_ins = *iter;
11277 gtk_text_iter_backward_chars(&before_ins, len);
11278 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11281 /* check if the preceding is just whitespace or quote */
11282 GtkTextIter start_line;
11283 gchar *tmp = NULL, *quote = NULL;
11284 gint quote_len = 0, is_normal = 0;
11285 start_line = *iter;
11286 gtk_text_iter_set_line_offset(&start_line, 0);
11287 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11290 if (*tmp == '\0') {
11293 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11301 gtk_text_buffer_insert(buffer, iter, text, len);
11303 gtk_text_buffer_insert_with_tags_by_name(buffer,
11304 iter, text, len, "no_join", NULL);
11309 if (!paste_as_quotation) {
11310 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11311 compose_beautify_paragraph(compose, iter, FALSE);
11312 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11313 gtk_text_buffer_delete_mark(buffer, mark);
11316 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11317 G_CALLBACK(text_inserted),
11319 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11321 if (compose_can_autosave(compose) &&
11322 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11323 compose->draft_timeout_tag != -2 /* disabled while loading */)
11324 compose->draft_timeout_tag = g_timeout_add
11325 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11329 static void compose_check_all(GtkAction *action, gpointer data)
11331 Compose *compose = (Compose *)data;
11332 if (!compose->gtkaspell)
11335 if (gtk_widget_has_focus(compose->subject_entry))
11336 claws_spell_entry_check_all(
11337 CLAWS_SPELL_ENTRY(compose->subject_entry));
11339 gtkaspell_check_all(compose->gtkaspell);
11342 static void compose_highlight_all(GtkAction *action, gpointer data)
11344 Compose *compose = (Compose *)data;
11345 if (compose->gtkaspell) {
11346 claws_spell_entry_recheck_all(
11347 CLAWS_SPELL_ENTRY(compose->subject_entry));
11348 gtkaspell_highlight_all(compose->gtkaspell);
11352 static void compose_check_backwards(GtkAction *action, gpointer data)
11354 Compose *compose = (Compose *)data;
11355 if (!compose->gtkaspell) {
11356 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11360 if (gtk_widget_has_focus(compose->subject_entry))
11361 claws_spell_entry_check_backwards(
11362 CLAWS_SPELL_ENTRY(compose->subject_entry));
11364 gtkaspell_check_backwards(compose->gtkaspell);
11367 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11369 Compose *compose = (Compose *)data;
11370 if (!compose->gtkaspell) {
11371 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11375 if (gtk_widget_has_focus(compose->subject_entry))
11376 claws_spell_entry_check_forwards_go(
11377 CLAWS_SPELL_ENTRY(compose->subject_entry));
11379 gtkaspell_check_forwards_go(compose->gtkaspell);
11384 *\brief Guess originating forward account from MsgInfo and several
11385 * "common preference" settings. Return NULL if no guess.
11387 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11389 PrefsAccount *account = NULL;
11391 cm_return_val_if_fail(msginfo, NULL);
11392 cm_return_val_if_fail(msginfo->folder, NULL);
11393 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11395 if (msginfo->folder->prefs->enable_default_account)
11396 account = account_find_from_id(msginfo->folder->prefs->default_account);
11399 account = msginfo->folder->folder->account;
11401 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11403 Xstrdup_a(to, msginfo->to, return NULL);
11404 extract_address(to);
11405 account = account_find_from_address(to, FALSE);
11408 if (!account && prefs_common.forward_account_autosel) {
11409 gchar cc[BUFFSIZE];
11410 if (!procheader_get_header_from_msginfo
11411 (msginfo, cc,sizeof cc , "Cc:")) {
11412 gchar *buf = cc + strlen("Cc:");
11413 extract_address(buf);
11414 account = account_find_from_address(buf, FALSE);
11418 if (!account && prefs_common.forward_account_autosel) {
11419 gchar deliveredto[BUFFSIZE];
11420 if (!procheader_get_header_from_msginfo
11421 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11422 gchar *buf = deliveredto + strlen("Delivered-To:");
11423 extract_address(buf);
11424 account = account_find_from_address(buf, FALSE);
11431 gboolean compose_close(Compose *compose)
11435 if (!g_mutex_trylock(compose->mutex)) {
11436 /* we have to wait for the (possibly deferred by auto-save)
11437 * drafting to be done, before destroying the compose under
11439 debug_print("waiting for drafting to finish...\n");
11440 compose_allow_user_actions(compose, FALSE);
11441 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11444 cm_return_val_if_fail(compose, FALSE);
11445 gtkut_widget_get_uposition(compose->window, &x, &y);
11446 if (!compose->batch) {
11447 prefs_common.compose_x = x;
11448 prefs_common.compose_y = y;
11450 g_mutex_unlock(compose->mutex);
11451 compose_destroy(compose);
11456 * Add entry field for each address in list.
11457 * \param compose E-Mail composition object.
11458 * \param listAddress List of (formatted) E-Mail addresses.
11460 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11463 node = listAddress;
11465 addr = ( gchar * ) node->data;
11466 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11467 node = g_list_next( node );
11471 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11472 guint action, gboolean opening_multiple)
11474 gchar *body = NULL;
11475 GSList *new_msglist = NULL;
11476 MsgInfo *tmp_msginfo = NULL;
11477 gboolean originally_enc = FALSE;
11478 gboolean originally_sig = FALSE;
11479 Compose *compose = NULL;
11480 gchar *s_system = NULL;
11482 cm_return_if_fail(msgview != NULL);
11484 cm_return_if_fail(msginfo_list != NULL);
11486 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11487 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11488 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11490 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11491 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11492 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11493 orig_msginfo, mimeinfo);
11494 if (tmp_msginfo != NULL) {
11495 new_msglist = g_slist_append(NULL, tmp_msginfo);
11497 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11498 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11499 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11501 tmp_msginfo->folder = orig_msginfo->folder;
11502 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11503 if (orig_msginfo->tags) {
11504 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11505 tmp_msginfo->folder->tags_dirty = TRUE;
11511 if (!opening_multiple)
11512 body = messageview_get_selection(msgview);
11515 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11516 procmsg_msginfo_free(tmp_msginfo);
11517 g_slist_free(new_msglist);
11519 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11521 if (compose && originally_enc) {
11522 compose_force_encryption(compose, compose->account, FALSE, s_system);
11525 if (compose && originally_sig && compose->account->default_sign_reply) {
11526 compose_force_signing(compose, compose->account, s_system);
11530 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11533 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11536 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11537 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11538 GSList *cur = msginfo_list;
11539 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11540 "messages. Opening the windows "
11541 "could take some time. Do you "
11542 "want to continue?"),
11543 g_slist_length(msginfo_list));
11544 if (g_slist_length(msginfo_list) > 9
11545 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11546 != G_ALERTALTERNATE) {
11551 /* We'll open multiple compose windows */
11552 /* let the WM place the next windows */
11553 compose_force_window_origin = FALSE;
11554 for (; cur; cur = cur->next) {
11556 tmplist.data = cur->data;
11557 tmplist.next = NULL;
11558 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11560 compose_force_window_origin = TRUE;
11562 /* forwarding multiple mails as attachments is done via a
11563 * single compose window */
11564 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11568 void compose_check_for_email_account(Compose *compose)
11570 PrefsAccount *ac = NULL, *curr = NULL;
11576 if (compose->account && compose->account->protocol == A_NNTP) {
11577 ac = account_get_cur_account();
11578 if (ac->protocol == A_NNTP) {
11579 list = account_get_list();
11581 for( ; list != NULL ; list = g_list_next(list)) {
11582 curr = (PrefsAccount *) list->data;
11583 if (curr->protocol != A_NNTP) {
11589 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11594 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11595 const gchar *address)
11597 GSList *msginfo_list = NULL;
11598 gchar *body = messageview_get_selection(msgview);
11601 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11603 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11604 compose_check_for_email_account(compose);
11605 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11606 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11607 compose_reply_set_subject(compose, msginfo);
11610 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11613 void compose_set_position(Compose *compose, gint pos)
11615 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11617 gtkut_text_view_set_position(text, pos);
11620 gboolean compose_search_string(Compose *compose,
11621 const gchar *str, gboolean case_sens)
11623 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11625 return gtkut_text_view_search_string(text, str, case_sens);
11628 gboolean compose_search_string_backward(Compose *compose,
11629 const gchar *str, gboolean case_sens)
11631 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11633 return gtkut_text_view_search_string_backward(text, str, case_sens);
11636 /* allocate a msginfo structure and populate its data from a compose data structure */
11637 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11639 MsgInfo *newmsginfo;
11641 gchar buf[BUFFSIZE];
11643 cm_return_val_if_fail( compose != NULL, NULL );
11645 newmsginfo = procmsg_msginfo_new();
11648 get_rfc822_date(buf, sizeof(buf));
11649 newmsginfo->date = g_strdup(buf);
11652 if (compose->from_name) {
11653 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11654 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11658 if (compose->subject_entry)
11659 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11661 /* to, cc, reply-to, newsgroups */
11662 for (list = compose->header_list; list; list = list->next) {
11663 gchar *header = gtk_editable_get_chars(
11665 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11666 gchar *entry = gtk_editable_get_chars(
11667 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11669 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11670 if ( newmsginfo->to == NULL ) {
11671 newmsginfo->to = g_strdup(entry);
11672 } else if (entry && *entry) {
11673 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11674 g_free(newmsginfo->to);
11675 newmsginfo->to = tmp;
11678 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11679 if ( newmsginfo->cc == NULL ) {
11680 newmsginfo->cc = g_strdup(entry);
11681 } else if (entry && *entry) {
11682 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11683 g_free(newmsginfo->cc);
11684 newmsginfo->cc = tmp;
11687 if ( strcasecmp(header,
11688 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11689 if ( newmsginfo->newsgroups == NULL ) {
11690 newmsginfo->newsgroups = g_strdup(entry);
11691 } else if (entry && *entry) {
11692 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11693 g_free(newmsginfo->newsgroups);
11694 newmsginfo->newsgroups = tmp;
11702 /* other data is unset */
11708 /* update compose's dictionaries from folder dict settings */
11709 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11710 FolderItem *folder_item)
11712 cm_return_if_fail(compose != NULL);
11714 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11715 FolderItemPrefs *prefs = folder_item->prefs;
11717 if (prefs->enable_default_dictionary)
11718 gtkaspell_change_dict(compose->gtkaspell,
11719 prefs->default_dictionary, FALSE);
11720 if (folder_item->prefs->enable_default_alt_dictionary)
11721 gtkaspell_change_alt_dict(compose->gtkaspell,
11722 prefs->default_alt_dictionary);
11723 if (prefs->enable_default_dictionary
11724 || prefs->enable_default_alt_dictionary)
11725 compose_spell_menu_changed(compose);
11730 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11732 Compose *compose = (Compose *)data;
11734 cm_return_if_fail(compose != NULL);
11736 gtk_widget_grab_focus(compose->text);