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_ext_editor_cb(NULL, compose);
2678 case A_LINEWRAP_CURRENT:
2679 compose_beautify_paragraph(compose, NULL, TRUE);
2681 case A_LINEWRAP_ALL:
2682 compose_wrap_all_full(compose, TRUE);
2685 compose_address_cb(NULL, compose);
2688 case A_CHECK_SPELLING:
2689 compose_check_all(NULL, compose);
2697 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2702 gchar *subject = NULL;
2706 gchar **attach = NULL;
2707 gchar *inreplyto = NULL;
2708 MailField mfield = NO_FIELD_PRESENT;
2710 /* get mailto parts but skip from */
2711 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2714 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2715 mfield = TO_FIELD_PRESENT;
2718 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2720 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2722 if (!g_utf8_validate (subject, -1, NULL)) {
2723 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2724 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2727 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2729 mfield = SUBJECT_FIELD_PRESENT;
2732 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2733 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2736 gboolean prev_autowrap = compose->autowrap;
2738 compose->autowrap = FALSE;
2740 mark = gtk_text_buffer_get_insert(buffer);
2741 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2743 if (!g_utf8_validate (body, -1, NULL)) {
2744 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2745 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2748 gtk_text_buffer_insert(buffer, &iter, body, -1);
2750 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2752 compose->autowrap = prev_autowrap;
2753 if (compose->autowrap)
2754 compose_wrap_all(compose);
2755 mfield = BODY_FIELD_PRESENT;
2759 gint i = 0, att = 0;
2760 gchar *warn_files = NULL;
2761 while (attach[i] != NULL) {
2762 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2763 if (utf8_filename) {
2764 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2765 gchar *tmp = g_strdup_printf("%s%s\n",
2766 warn_files?warn_files:"",
2772 g_free(utf8_filename);
2774 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2779 alertpanel_notice(ngettext(
2780 "The following file has been attached: \n%s",
2781 "The following files have been attached: \n%s", att), warn_files);
2786 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2799 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2801 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2802 {"Cc:", NULL, TRUE},
2803 {"References:", NULL, FALSE},
2804 {"Bcc:", NULL, TRUE},
2805 {"Newsgroups:", NULL, TRUE},
2806 {"Followup-To:", NULL, TRUE},
2807 {"List-Post:", NULL, FALSE},
2808 {"X-Priority:", NULL, FALSE},
2809 {NULL, NULL, FALSE}};
2825 cm_return_val_if_fail(msginfo != NULL, -1);
2827 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2828 procheader_get_header_fields(fp, hentry);
2831 if (hentry[H_REPLY_TO].body != NULL) {
2832 if (hentry[H_REPLY_TO].body[0] != '\0') {
2834 conv_unmime_header(hentry[H_REPLY_TO].body,
2837 g_free(hentry[H_REPLY_TO].body);
2838 hentry[H_REPLY_TO].body = NULL;
2840 if (hentry[H_CC].body != NULL) {
2841 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2842 g_free(hentry[H_CC].body);
2843 hentry[H_CC].body = NULL;
2845 if (hentry[H_REFERENCES].body != NULL) {
2846 if (compose->mode == COMPOSE_REEDIT)
2847 compose->references = hentry[H_REFERENCES].body;
2849 compose->references = compose_parse_references
2850 (hentry[H_REFERENCES].body, msginfo->msgid);
2851 g_free(hentry[H_REFERENCES].body);
2853 hentry[H_REFERENCES].body = NULL;
2855 if (hentry[H_BCC].body != NULL) {
2856 if (compose->mode == COMPOSE_REEDIT)
2858 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2859 g_free(hentry[H_BCC].body);
2860 hentry[H_BCC].body = NULL;
2862 if (hentry[H_NEWSGROUPS].body != NULL) {
2863 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2864 hentry[H_NEWSGROUPS].body = NULL;
2866 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2867 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2868 compose->followup_to =
2869 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2872 g_free(hentry[H_FOLLOWUP_TO].body);
2873 hentry[H_FOLLOWUP_TO].body = NULL;
2875 if (hentry[H_LIST_POST].body != NULL) {
2876 gchar *to = NULL, *start = NULL;
2878 extract_address(hentry[H_LIST_POST].body);
2879 if (hentry[H_LIST_POST].body[0] != '\0') {
2880 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2882 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2883 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2886 g_free(compose->ml_post);
2887 compose->ml_post = to;
2890 g_free(hentry[H_LIST_POST].body);
2891 hentry[H_LIST_POST].body = NULL;
2894 /* CLAWS - X-Priority */
2895 if (compose->mode == COMPOSE_REEDIT)
2896 if (hentry[H_X_PRIORITY].body != NULL) {
2899 priority = atoi(hentry[H_X_PRIORITY].body);
2900 g_free(hentry[H_X_PRIORITY].body);
2902 hentry[H_X_PRIORITY].body = NULL;
2904 if (priority < PRIORITY_HIGHEST ||
2905 priority > PRIORITY_LOWEST)
2906 priority = PRIORITY_NORMAL;
2908 compose->priority = priority;
2911 if (compose->mode == COMPOSE_REEDIT) {
2912 if (msginfo->inreplyto && *msginfo->inreplyto)
2913 compose->inreplyto = g_strdup(msginfo->inreplyto);
2917 if (msginfo->msgid && *msginfo->msgid)
2918 compose->inreplyto = g_strdup(msginfo->msgid);
2920 if (!compose->references) {
2921 if (msginfo->msgid && *msginfo->msgid) {
2922 if (msginfo->inreplyto && *msginfo->inreplyto)
2923 compose->references =
2924 g_strdup_printf("<%s>\n\t<%s>",
2928 compose->references =
2929 g_strconcat("<", msginfo->msgid, ">",
2931 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2932 compose->references =
2933 g_strconcat("<", msginfo->inreplyto, ">",
2941 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2946 cm_return_val_if_fail(msginfo != NULL, -1);
2948 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2949 procheader_get_header_fields(fp, entries);
2953 while (he != NULL && he->name != NULL) {
2955 GtkListStore *model = NULL;
2957 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2958 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2959 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2960 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2961 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2968 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2970 GSList *ref_id_list, *cur;
2974 ref_id_list = references_list_append(NULL, ref);
2975 if (!ref_id_list) return NULL;
2976 if (msgid && *msgid)
2977 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2982 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2983 /* "<" + Message-ID + ">" + CR+LF+TAB */
2984 len += strlen((gchar *)cur->data) + 5;
2986 if (len > MAX_REFERENCES_LEN) {
2987 /* remove second message-ID */
2988 if (ref_id_list && ref_id_list->next &&
2989 ref_id_list->next->next) {
2990 g_free(ref_id_list->next->data);
2991 ref_id_list = g_slist_remove
2992 (ref_id_list, ref_id_list->next->data);
2994 slist_free_strings_full(ref_id_list);
3001 new_ref = g_string_new("");
3002 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3003 if (new_ref->len > 0)
3004 g_string_append(new_ref, "\n\t");
3005 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3008 slist_free_strings_full(ref_id_list);
3010 new_ref_str = new_ref->str;
3011 g_string_free(new_ref, FALSE);
3016 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3017 const gchar *fmt, const gchar *qmark,
3018 const gchar *body, gboolean rewrap,
3019 gboolean need_unescape,
3020 const gchar *err_msg)
3022 MsgInfo* dummyinfo = NULL;
3023 gchar *quote_str = NULL;
3025 gboolean prev_autowrap;
3026 const gchar *trimmed_body = body;
3027 gint cursor_pos = -1;
3028 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3029 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3034 SIGNAL_BLOCK(buffer);
3037 dummyinfo = compose_msginfo_new_from_compose(compose);
3038 msginfo = dummyinfo;
3041 if (qmark != NULL) {
3043 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3044 compose->gtkaspell);
3046 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3048 quote_fmt_scan_string(qmark);
3051 buf = quote_fmt_get_buffer();
3053 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3055 Xstrdup_a(quote_str, buf, goto error)
3058 if (fmt && *fmt != '\0') {
3061 while (*trimmed_body == '\n')
3065 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3066 compose->gtkaspell);
3068 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3070 if (need_unescape) {
3073 /* decode \-escape sequences in the internal representation of the quote format */
3074 tmp = g_malloc(strlen(fmt)+1);
3075 pref_get_unescaped_pref(tmp, fmt);
3076 quote_fmt_scan_string(tmp);
3080 quote_fmt_scan_string(fmt);
3084 buf = quote_fmt_get_buffer();
3086 gint line = quote_fmt_get_line();
3087 alertpanel_error(err_msg, line);
3093 prev_autowrap = compose->autowrap;
3094 compose->autowrap = FALSE;
3096 mark = gtk_text_buffer_get_insert(buffer);
3097 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3098 if (g_utf8_validate(buf, -1, NULL)) {
3099 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3101 gchar *tmpout = NULL;
3102 tmpout = conv_codeset_strdup
3103 (buf, conv_get_locale_charset_str_no_utf8(),
3105 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3107 tmpout = g_malloc(strlen(buf)*2+1);
3108 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3110 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3114 cursor_pos = quote_fmt_get_cursor_pos();
3115 if (cursor_pos == -1)
3116 cursor_pos = gtk_text_iter_get_offset(&iter);
3117 compose->set_cursor_pos = cursor_pos;
3119 gtk_text_buffer_get_start_iter(buffer, &iter);
3120 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3121 gtk_text_buffer_place_cursor(buffer, &iter);
3123 compose->autowrap = prev_autowrap;
3124 if (compose->autowrap && rewrap)
3125 compose_wrap_all(compose);
3132 SIGNAL_UNBLOCK(buffer);
3134 procmsg_msginfo_free( dummyinfo );
3139 /* if ml_post is of type addr@host and from is of type
3140 * addr-anything@host, return TRUE
3142 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3144 gchar *left_ml = NULL;
3145 gchar *right_ml = NULL;
3146 gchar *left_from = NULL;
3147 gchar *right_from = NULL;
3148 gboolean result = FALSE;
3150 if (!ml_post || !from)
3153 left_ml = g_strdup(ml_post);
3154 if (strstr(left_ml, "@")) {
3155 right_ml = strstr(left_ml, "@")+1;
3156 *(strstr(left_ml, "@")) = '\0';
3159 left_from = g_strdup(from);
3160 if (strstr(left_from, "@")) {
3161 right_from = strstr(left_from, "@")+1;
3162 *(strstr(left_from, "@")) = '\0';
3165 if (left_ml && left_from && right_ml && right_from
3166 && !strncmp(left_from, left_ml, strlen(left_ml))
3167 && !strcmp(right_from, right_ml)) {
3176 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3177 gboolean respect_default_to)
3181 if (!folder || !folder->prefs)
3184 if (respect_default_to && folder->prefs->enable_default_to) {
3185 compose_entry_append(compose, folder->prefs->default_to,
3186 COMPOSE_TO, PREF_FOLDER);
3187 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3189 if (folder->prefs->enable_default_cc)
3190 compose_entry_append(compose, folder->prefs->default_cc,
3191 COMPOSE_CC, PREF_FOLDER);
3192 if (folder->prefs->enable_default_bcc)
3193 compose_entry_append(compose, folder->prefs->default_bcc,
3194 COMPOSE_BCC, PREF_FOLDER);
3195 if (folder->prefs->enable_default_replyto)
3196 compose_entry_append(compose, folder->prefs->default_replyto,
3197 COMPOSE_REPLYTO, PREF_FOLDER);
3200 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3205 if (!compose || !msginfo)
3208 if (msginfo->subject && *msginfo->subject) {
3209 buf = p = g_strdup(msginfo->subject);
3210 p += subject_get_prefix_length(p);
3211 memmove(buf, p, strlen(p) + 1);
3213 buf2 = g_strdup_printf("Re: %s", buf);
3214 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3219 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3222 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3223 gboolean to_all, gboolean to_ml,
3225 gboolean followup_and_reply_to)
3227 GSList *cc_list = NULL;
3230 gchar *replyto = NULL;
3231 gchar *ac_email = NULL;
3233 gboolean reply_to_ml = FALSE;
3234 gboolean default_reply_to = FALSE;
3236 cm_return_if_fail(compose->account != NULL);
3237 cm_return_if_fail(msginfo != NULL);
3239 reply_to_ml = to_ml && compose->ml_post;
3241 default_reply_to = msginfo->folder &&
3242 msginfo->folder->prefs->enable_default_reply_to;
3244 if (compose->account->protocol != A_NNTP) {
3245 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3247 if (reply_to_ml && !default_reply_to) {
3249 gboolean is_subscr = is_subscription(compose->ml_post,
3252 /* normal answer to ml post with a reply-to */
3253 compose_entry_append(compose,
3255 COMPOSE_TO, PREF_ML);
3256 if (compose->replyto)
3257 compose_entry_append(compose,
3259 COMPOSE_CC, PREF_ML);
3261 /* answer to subscription confirmation */
3262 if (compose->replyto)
3263 compose_entry_append(compose,
3265 COMPOSE_TO, PREF_ML);
3266 else if (msginfo->from)
3267 compose_entry_append(compose,
3269 COMPOSE_TO, PREF_ML);
3272 else if (!(to_all || to_sender) && default_reply_to) {
3273 compose_entry_append(compose,
3274 msginfo->folder->prefs->default_reply_to,
3275 COMPOSE_TO, PREF_FOLDER);
3276 compose_entry_mark_default_to(compose,
3277 msginfo->folder->prefs->default_reply_to);
3282 Xstrdup_a(tmp1, msginfo->from, return);
3283 extract_address(tmp1);
3284 if (to_all || to_sender ||
3285 !account_find_from_address(tmp1, FALSE))
3286 compose_entry_append(compose,
3287 (compose->replyto && !to_sender)
3288 ? compose->replyto :
3289 msginfo->from ? msginfo->from : "",
3290 COMPOSE_TO, PREF_NONE);
3291 else if (!to_all && !to_sender) {
3292 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3293 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3294 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3295 if (compose->replyto) {
3296 compose_entry_append(compose,
3298 COMPOSE_TO, PREF_NONE);
3300 compose_entry_append(compose,
3301 msginfo->from ? msginfo->from : "",
3302 COMPOSE_TO, PREF_NONE);
3305 /* replying to own mail, use original recp */
3306 compose_entry_append(compose,
3307 msginfo->to ? msginfo->to : "",
3308 COMPOSE_TO, PREF_NONE);
3309 compose_entry_append(compose,
3310 msginfo->cc ? msginfo->cc : "",
3311 COMPOSE_CC, PREF_NONE);
3316 if (to_sender || (compose->followup_to &&
3317 !strncmp(compose->followup_to, "poster", 6)))
3318 compose_entry_append
3320 (compose->replyto ? compose->replyto :
3321 msginfo->from ? msginfo->from : ""),
3322 COMPOSE_TO, PREF_NONE);
3324 else if (followup_and_reply_to || to_all) {
3325 compose_entry_append
3327 (compose->replyto ? compose->replyto :
3328 msginfo->from ? msginfo->from : ""),
3329 COMPOSE_TO, PREF_NONE);
3331 compose_entry_append
3333 compose->followup_to ? compose->followup_to :
3334 compose->newsgroups ? compose->newsgroups : "",
3335 COMPOSE_NEWSGROUPS, PREF_NONE);
3338 compose_entry_append
3340 compose->followup_to ? compose->followup_to :
3341 compose->newsgroups ? compose->newsgroups : "",
3342 COMPOSE_NEWSGROUPS, PREF_NONE);
3344 compose_reply_set_subject(compose, msginfo);
3346 if (to_ml && compose->ml_post) return;
3347 if (!to_all || compose->account->protocol == A_NNTP) return;
3349 if (compose->replyto) {
3350 Xstrdup_a(replyto, compose->replyto, return);
3351 extract_address(replyto);
3353 if (msginfo->from) {
3354 Xstrdup_a(from, msginfo->from, return);
3355 extract_address(from);
3358 if (replyto && from)
3359 cc_list = address_list_append_with_comments(cc_list, from);
3360 if (to_all && msginfo->folder &&
3361 msginfo->folder->prefs->enable_default_reply_to)
3362 cc_list = address_list_append_with_comments(cc_list,
3363 msginfo->folder->prefs->default_reply_to);
3364 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3365 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3367 ac_email = g_utf8_strdown(compose->account->address, -1);
3370 for (cur = cc_list; cur != NULL; cur = cur->next) {
3371 gchar *addr = g_utf8_strdown(cur->data, -1);
3372 extract_address(addr);
3374 if (strcmp(ac_email, addr))
3375 compose_entry_append(compose, (gchar *)cur->data,
3376 COMPOSE_CC, PREF_NONE);
3378 debug_print("Cc address same as compose account's, ignoring\n");
3383 slist_free_strings_full(cc_list);
3389 #define SET_ENTRY(entry, str) \
3392 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3395 #define SET_ADDRESS(type, str) \
3398 compose_entry_append(compose, str, type, PREF_NONE); \
3401 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3403 cm_return_if_fail(msginfo != NULL);
3405 SET_ENTRY(subject_entry, msginfo->subject);
3406 SET_ENTRY(from_name, msginfo->from);
3407 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3408 SET_ADDRESS(COMPOSE_CC, compose->cc);
3409 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3410 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3411 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3412 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3414 compose_update_priority_menu_item(compose);
3415 compose_update_privacy_system_menu_item(compose, FALSE);
3416 compose_show_first_last_header(compose, TRUE);
3422 static void compose_insert_sig(Compose *compose, gboolean replace)
3424 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3425 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3427 GtkTextIter iter, iter_end;
3428 gint cur_pos, ins_pos;
3429 gboolean prev_autowrap;
3430 gboolean found = FALSE;
3431 gboolean exists = FALSE;
3433 cm_return_if_fail(compose->account != NULL);
3437 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3438 G_CALLBACK(compose_changed_cb),
3441 mark = gtk_text_buffer_get_insert(buffer);
3442 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3443 cur_pos = gtk_text_iter_get_offset (&iter);
3446 gtk_text_buffer_get_end_iter(buffer, &iter);
3448 exists = (compose->sig_str != NULL);
3451 GtkTextIter first_iter, start_iter, end_iter;
3453 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3455 if (!exists || compose->sig_str[0] == '\0')
3458 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3459 compose->signature_tag);
3462 /* include previous \n\n */
3463 gtk_text_iter_backward_chars(&first_iter, 1);
3464 start_iter = first_iter;
3465 end_iter = first_iter;
3467 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3468 compose->signature_tag);
3469 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3470 compose->signature_tag);
3472 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3478 g_free(compose->sig_str);
3479 compose->sig_str = account_get_signature_str(compose->account);
3481 cur_pos = gtk_text_iter_get_offset(&iter);
3483 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3484 g_free(compose->sig_str);
3485 compose->sig_str = NULL;
3487 if (compose->sig_inserted == FALSE)
3488 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3489 compose->sig_inserted = TRUE;
3491 cur_pos = gtk_text_iter_get_offset(&iter);
3492 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3494 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3495 gtk_text_iter_forward_chars(&iter, 1);
3496 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3497 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3499 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3500 cur_pos = gtk_text_buffer_get_char_count (buffer);
3503 /* put the cursor where it should be
3504 * either where the quote_fmt says, either where it was */
3505 if (compose->set_cursor_pos < 0)
3506 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3508 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3509 compose->set_cursor_pos);
3511 compose->set_cursor_pos = -1;
3512 gtk_text_buffer_place_cursor(buffer, &iter);
3513 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3514 G_CALLBACK(compose_changed_cb),
3520 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3523 GtkTextBuffer *buffer;
3526 const gchar *cur_encoding;
3527 gchar buf[BUFFSIZE];
3530 gboolean prev_autowrap;
3531 gboolean badtxt = FALSE;
3532 struct stat file_stat;
3534 GString *file_contents = NULL;
3536 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3538 /* get the size of the file we are about to insert */
3539 ret = g_stat(file, &file_stat);
3541 gchar *shortfile = g_path_get_basename(file);
3542 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3544 return COMPOSE_INSERT_NO_FILE;
3545 } else if (prefs_common.warn_large_insert == TRUE) {
3547 /* ask user for confirmation if the file is large */
3548 if (prefs_common.warn_large_insert_size < 0 ||
3549 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3553 msg = g_strdup_printf(_("You are about to insert a file of %s "
3554 "in the message body. Are you sure you want to do that?"),
3555 to_human_readable(file_stat.st_size));
3556 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3557 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3560 /* do we ask for confirmation next time? */
3561 if (aval & G_ALERTDISABLE) {
3562 /* no confirmation next time, disable feature in preferences */
3563 aval &= ~G_ALERTDISABLE;
3564 prefs_common.warn_large_insert = FALSE;
3567 /* abort file insertion if user canceled action */
3568 if (aval != G_ALERTALTERNATE) {
3569 return COMPOSE_INSERT_NO_FILE;
3575 if ((fp = g_fopen(file, "rb")) == NULL) {
3576 FILE_OP_ERROR(file, "fopen");
3577 return COMPOSE_INSERT_READ_ERROR;
3580 prev_autowrap = compose->autowrap;
3581 compose->autowrap = FALSE;
3583 text = GTK_TEXT_VIEW(compose->text);
3584 buffer = gtk_text_view_get_buffer(text);
3585 mark = gtk_text_buffer_get_insert(buffer);
3586 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3588 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3589 G_CALLBACK(text_inserted),
3592 cur_encoding = conv_get_locale_charset_str_no_utf8();
3594 file_contents = g_string_new("");
3595 while (fgets(buf, sizeof(buf), fp) != NULL) {
3598 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3599 str = g_strdup(buf);
3601 str = conv_codeset_strdup
3602 (buf, cur_encoding, CS_INTERNAL);
3605 /* strip <CR> if DOS/Windows file,
3606 replace <CR> with <LF> if Macintosh file. */
3609 if (len > 0 && str[len - 1] != '\n') {
3611 if (str[len] == '\r') str[len] = '\n';
3614 file_contents = g_string_append(file_contents, str);
3618 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3619 g_string_free(file_contents, TRUE);
3621 compose_changed_cb(NULL, compose);
3622 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3623 G_CALLBACK(text_inserted),
3625 compose->autowrap = prev_autowrap;
3626 if (compose->autowrap)
3627 compose_wrap_all(compose);
3632 return COMPOSE_INSERT_INVALID_CHARACTER;
3634 return COMPOSE_INSERT_SUCCESS;
3637 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3638 const gchar *filename,
3639 const gchar *content_type,
3640 const gchar *charset)
3648 GtkListStore *store;
3650 gboolean has_binary = FALSE;
3652 if (!is_file_exist(file)) {
3653 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3654 gboolean result = FALSE;
3655 if (file_from_uri && is_file_exist(file_from_uri)) {
3656 result = compose_attach_append(
3657 compose, file_from_uri,
3658 filename, content_type,
3661 g_free(file_from_uri);
3664 alertpanel_error("File %s doesn't exist\n", filename);
3667 if ((size = get_file_size(file)) < 0) {
3668 alertpanel_error("Can't get file size of %s\n", filename);
3672 alertpanel_error(_("File %s is empty."), filename);
3675 if ((fp = g_fopen(file, "rb")) == NULL) {
3676 alertpanel_error(_("Can't read %s."), filename);
3681 ainfo = g_new0(AttachInfo, 1);
3682 auto_ainfo = g_auto_pointer_new_with_free
3683 (ainfo, (GFreeFunc) compose_attach_info_free);
3684 ainfo->file = g_strdup(file);
3687 ainfo->content_type = g_strdup(content_type);
3688 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3690 MsgFlags flags = {0, 0};
3692 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3693 ainfo->encoding = ENC_7BIT;
3695 ainfo->encoding = ENC_8BIT;
3697 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3698 if (msginfo && msginfo->subject)
3699 name = g_strdup(msginfo->subject);
3701 name = g_path_get_basename(filename ? filename : file);
3703 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3705 procmsg_msginfo_free(msginfo);
3707 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3708 ainfo->charset = g_strdup(charset);
3709 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3711 ainfo->encoding = ENC_BASE64;
3713 name = g_path_get_basename(filename ? filename : file);
3714 ainfo->name = g_strdup(name);
3718 ainfo->content_type = procmime_get_mime_type(file);
3719 if (!ainfo->content_type) {
3720 ainfo->content_type =
3721 g_strdup("application/octet-stream");
3722 ainfo->encoding = ENC_BASE64;
3723 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3725 procmime_get_encoding_for_text_file(file, &has_binary);
3727 ainfo->encoding = ENC_BASE64;
3728 name = g_path_get_basename(filename ? filename : file);
3729 ainfo->name = g_strdup(name);
3733 if (ainfo->name != NULL
3734 && !strcmp(ainfo->name, ".")) {
3735 g_free(ainfo->name);
3739 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3740 g_free(ainfo->content_type);
3741 ainfo->content_type = g_strdup("application/octet-stream");
3742 g_free(ainfo->charset);
3743 ainfo->charset = NULL;
3746 ainfo->size = (goffset)size;
3747 size_text = to_human_readable((goffset)size);
3749 store = GTK_LIST_STORE(gtk_tree_view_get_model
3750 (GTK_TREE_VIEW(compose->attach_clist)));
3752 gtk_list_store_append(store, &iter);
3753 gtk_list_store_set(store, &iter,
3754 COL_MIMETYPE, ainfo->content_type,
3755 COL_SIZE, size_text,
3756 COL_NAME, ainfo->name,
3757 COL_CHARSET, ainfo->charset,
3759 COL_AUTODATA, auto_ainfo,
3762 g_auto_pointer_free(auto_ainfo);
3763 compose_attach_update_label(compose);
3767 static void compose_use_signing(Compose *compose, gboolean use_signing)
3769 compose->use_signing = use_signing;
3770 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3773 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3775 compose->use_encryption = use_encryption;
3776 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3779 #define NEXT_PART_NOT_CHILD(info) \
3781 node = info->node; \
3782 while (node->children) \
3783 node = g_node_last_child(node); \
3784 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3787 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3791 MimeInfo *firsttext = NULL;
3792 MimeInfo *encrypted = NULL;
3795 const gchar *partname = NULL;
3797 mimeinfo = procmime_scan_message(msginfo);
3798 if (!mimeinfo) return;
3800 if (mimeinfo->node->children == NULL) {
3801 procmime_mimeinfo_free_all(mimeinfo);
3805 /* find first content part */
3806 child = (MimeInfo *) mimeinfo->node->children->data;
3807 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3808 child = (MimeInfo *)child->node->children->data;
3811 if (child->type == MIMETYPE_TEXT) {
3813 debug_print("First text part found\n");
3814 } else if (compose->mode == COMPOSE_REEDIT &&
3815 child->type == MIMETYPE_APPLICATION &&
3816 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3817 encrypted = (MimeInfo *)child->node->parent->data;
3820 child = (MimeInfo *) mimeinfo->node->children->data;
3821 while (child != NULL) {
3824 if (child == encrypted) {
3825 /* skip this part of tree */
3826 NEXT_PART_NOT_CHILD(child);
3830 if (child->type == MIMETYPE_MULTIPART) {
3831 /* get the actual content */
3832 child = procmime_mimeinfo_next(child);
3836 if (child == firsttext) {
3837 child = procmime_mimeinfo_next(child);
3841 outfile = procmime_get_tmp_file_name(child);
3842 if ((err = procmime_get_part(outfile, child)) < 0)
3843 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3845 gchar *content_type;
3847 content_type = procmime_get_content_type_str(child->type, child->subtype);
3849 /* if we meet a pgp signature, we don't attach it, but
3850 * we force signing. */
3851 if ((strcmp(content_type, "application/pgp-signature") &&
3852 strcmp(content_type, "application/pkcs7-signature") &&
3853 strcmp(content_type, "application/x-pkcs7-signature"))
3854 || compose->mode == COMPOSE_REDIRECT) {
3855 partname = procmime_mimeinfo_get_parameter(child, "filename");
3856 if (partname == NULL)
3857 partname = procmime_mimeinfo_get_parameter(child, "name");
3858 if (partname == NULL)
3860 compose_attach_append(compose, outfile,
3861 partname, content_type,
3862 procmime_mimeinfo_get_parameter(child, "charset"));
3864 compose_force_signing(compose, compose->account, NULL);
3866 g_free(content_type);
3869 NEXT_PART_NOT_CHILD(child);
3871 procmime_mimeinfo_free_all(mimeinfo);
3874 #undef NEXT_PART_NOT_CHILD
3879 WAIT_FOR_INDENT_CHAR,
3880 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3883 /* return indent length, we allow:
3884 indent characters followed by indent characters or spaces/tabs,
3885 alphabets and numbers immediately followed by indent characters,
3886 and the repeating sequences of the above
3887 If quote ends with multiple spaces, only the first one is included. */
3888 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3889 const GtkTextIter *start, gint *len)
3891 GtkTextIter iter = *start;
3895 IndentState state = WAIT_FOR_INDENT_CHAR;
3898 gint alnum_count = 0;
3899 gint space_count = 0;
3902 if (prefs_common.quote_chars == NULL) {
3906 while (!gtk_text_iter_ends_line(&iter)) {
3907 wc = gtk_text_iter_get_char(&iter);
3908 if (g_unichar_iswide(wc))
3910 clen = g_unichar_to_utf8(wc, ch);
3914 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3915 is_space = g_unichar_isspace(wc);
3917 if (state == WAIT_FOR_INDENT_CHAR) {
3918 if (!is_indent && !g_unichar_isalnum(wc))
3921 quote_len += alnum_count + space_count + 1;
3922 alnum_count = space_count = 0;
3923 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3926 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3927 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3931 else if (is_indent) {
3932 quote_len += alnum_count + space_count + 1;
3933 alnum_count = space_count = 0;
3936 state = WAIT_FOR_INDENT_CHAR;
3940 gtk_text_iter_forward_char(&iter);
3943 if (quote_len > 0 && space_count > 0)
3949 if (quote_len > 0) {
3951 gtk_text_iter_forward_chars(&iter, quote_len);
3952 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3958 /* return >0 if the line is itemized */
3959 static int compose_itemized_length(GtkTextBuffer *buffer,
3960 const GtkTextIter *start)
3962 GtkTextIter iter = *start;
3967 if (gtk_text_iter_ends_line(&iter))
3972 wc = gtk_text_iter_get_char(&iter);
3973 if (!g_unichar_isspace(wc))
3975 gtk_text_iter_forward_char(&iter);
3976 if (gtk_text_iter_ends_line(&iter))
3980 clen = g_unichar_to_utf8(wc, ch);
3984 if (!strchr("*-+", ch[0]))
3987 gtk_text_iter_forward_char(&iter);
3988 if (gtk_text_iter_ends_line(&iter))
3990 wc = gtk_text_iter_get_char(&iter);
3991 if (g_unichar_isspace(wc)) {
3997 /* return the string at the start of the itemization */
3998 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3999 const GtkTextIter *start)
4001 GtkTextIter iter = *start;
4004 GString *item_chars = g_string_new("");
4007 if (gtk_text_iter_ends_line(&iter))
4012 wc = gtk_text_iter_get_char(&iter);
4013 if (!g_unichar_isspace(wc))
4015 gtk_text_iter_forward_char(&iter);
4016 if (gtk_text_iter_ends_line(&iter))
4018 g_string_append_unichar(item_chars, wc);
4021 str = item_chars->str;
4022 g_string_free(item_chars, FALSE);
4026 /* return the number of spaces at a line's start */
4027 static int compose_left_offset_length(GtkTextBuffer *buffer,
4028 const GtkTextIter *start)
4030 GtkTextIter iter = *start;
4033 if (gtk_text_iter_ends_line(&iter))
4037 wc = gtk_text_iter_get_char(&iter);
4038 if (!g_unichar_isspace(wc))
4041 gtk_text_iter_forward_char(&iter);
4042 if (gtk_text_iter_ends_line(&iter))
4046 gtk_text_iter_forward_char(&iter);
4047 if (gtk_text_iter_ends_line(&iter))
4052 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4053 const GtkTextIter *start,
4054 GtkTextIter *break_pos,
4058 GtkTextIter iter = *start, line_end = *start;
4059 PangoLogAttr *attrs;
4066 gboolean can_break = FALSE;
4067 gboolean do_break = FALSE;
4068 gboolean was_white = FALSE;
4069 gboolean prev_dont_break = FALSE;
4071 gtk_text_iter_forward_to_line_end(&line_end);
4072 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4073 len = g_utf8_strlen(str, -1);
4077 g_warning("compose_get_line_break_pos: len = 0!\n");
4081 /* g_print("breaking line: %d: %s (len = %d)\n",
4082 gtk_text_iter_get_line(&iter), str, len); */
4084 attrs = g_new(PangoLogAttr, len + 1);
4086 pango_default_break(str, -1, NULL, attrs, len + 1);
4090 /* skip quote and leading spaces */
4091 for (i = 0; *p != '\0' && i < len; i++) {
4094 wc = g_utf8_get_char(p);
4095 if (i >= quote_len && !g_unichar_isspace(wc))
4097 if (g_unichar_iswide(wc))
4099 else if (*p == '\t')
4103 p = g_utf8_next_char(p);
4106 for (; *p != '\0' && i < len; i++) {
4107 PangoLogAttr *attr = attrs + i;
4111 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4114 was_white = attr->is_white;
4116 /* don't wrap URI */
4117 if ((uri_len = get_uri_len(p)) > 0) {
4119 if (pos > 0 && col > max_col) {
4129 wc = g_utf8_get_char(p);
4130 if (g_unichar_iswide(wc)) {
4132 if (prev_dont_break && can_break && attr->is_line_break)
4134 } else if (*p == '\t')
4138 if (pos > 0 && col > max_col) {
4143 if (*p == '-' || *p == '/')
4144 prev_dont_break = TRUE;
4146 prev_dont_break = FALSE;
4148 p = g_utf8_next_char(p);
4152 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4157 *break_pos = *start;
4158 gtk_text_iter_set_line_offset(break_pos, pos);
4163 static gboolean compose_join_next_line(Compose *compose,
4164 GtkTextBuffer *buffer,
4166 const gchar *quote_str)
4168 GtkTextIter iter_ = *iter, cur, prev, next, end;
4169 PangoLogAttr attrs[3];
4171 gchar *next_quote_str;
4174 gboolean keep_cursor = FALSE;
4176 if (!gtk_text_iter_forward_line(&iter_) ||
4177 gtk_text_iter_ends_line(&iter_)) {
4180 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4182 if ((quote_str || next_quote_str) &&
4183 strcmp2(quote_str, next_quote_str) != 0) {
4184 g_free(next_quote_str);
4187 g_free(next_quote_str);
4190 if (quote_len > 0) {
4191 gtk_text_iter_forward_chars(&end, quote_len);
4192 if (gtk_text_iter_ends_line(&end)) {
4197 /* don't join itemized lines */
4198 if (compose_itemized_length(buffer, &end) > 0) {
4202 /* don't join signature separator */
4203 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4206 /* delete quote str */
4208 gtk_text_buffer_delete(buffer, &iter_, &end);
4210 /* don't join line breaks put by the user */
4212 gtk_text_iter_backward_char(&cur);
4213 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4214 gtk_text_iter_forward_char(&cur);
4218 gtk_text_iter_forward_char(&cur);
4219 /* delete linebreak and extra spaces */
4220 while (gtk_text_iter_backward_char(&cur)) {
4221 wc1 = gtk_text_iter_get_char(&cur);
4222 if (!g_unichar_isspace(wc1))
4227 while (!gtk_text_iter_ends_line(&cur)) {
4228 wc1 = gtk_text_iter_get_char(&cur);
4229 if (!g_unichar_isspace(wc1))
4231 gtk_text_iter_forward_char(&cur);
4234 if (!gtk_text_iter_equal(&prev, &next)) {
4237 mark = gtk_text_buffer_get_insert(buffer);
4238 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4239 if (gtk_text_iter_equal(&prev, &cur))
4241 gtk_text_buffer_delete(buffer, &prev, &next);
4245 /* insert space if required */
4246 gtk_text_iter_backward_char(&prev);
4247 wc1 = gtk_text_iter_get_char(&prev);
4248 wc2 = gtk_text_iter_get_char(&next);
4249 gtk_text_iter_forward_char(&next);
4250 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4251 pango_default_break(str, -1, NULL, attrs, 3);
4252 if (!attrs[1].is_line_break ||
4253 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4254 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4256 gtk_text_iter_backward_char(&iter_);
4257 gtk_text_buffer_place_cursor(buffer, &iter_);
4266 #define ADD_TXT_POS(bp_, ep_, pti_) \
4267 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4268 last = last->next; \
4269 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4270 last->next = NULL; \
4272 g_warning("alloc error scanning URIs\n"); \
4275 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4277 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4278 GtkTextBuffer *buffer;
4279 GtkTextIter iter, break_pos, end_of_line;
4280 gchar *quote_str = NULL;
4282 gboolean wrap_quote = prefs_common.linewrap_quote;
4283 gboolean prev_autowrap = compose->autowrap;
4284 gint startq_offset = -1, noq_offset = -1;
4285 gint uri_start = -1, uri_stop = -1;
4286 gint nouri_start = -1, nouri_stop = -1;
4287 gint num_blocks = 0;
4288 gint quotelevel = -1;
4289 gboolean modified = force;
4290 gboolean removed = FALSE;
4291 gboolean modified_before_remove = FALSE;
4293 gboolean start = TRUE;
4294 gint itemized_len = 0, rem_item_len = 0;
4295 gchar *itemized_chars = NULL;
4296 gboolean item_continuation = FALSE;
4301 if (compose->draft_timeout_tag == -2) {
4305 compose->autowrap = FALSE;
4307 buffer = gtk_text_view_get_buffer(text);
4308 undo_wrapping(compose->undostruct, TRUE);
4313 mark = gtk_text_buffer_get_insert(buffer);
4314 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4318 if (compose->draft_timeout_tag == -2) {
4319 if (gtk_text_iter_ends_line(&iter)) {
4320 while (gtk_text_iter_ends_line(&iter) &&
4321 gtk_text_iter_forward_line(&iter))
4324 while (gtk_text_iter_backward_line(&iter)) {
4325 if (gtk_text_iter_ends_line(&iter)) {
4326 gtk_text_iter_forward_line(&iter);
4332 /* move to line start */
4333 gtk_text_iter_set_line_offset(&iter, 0);
4336 itemized_len = compose_itemized_length(buffer, &iter);
4338 if (!itemized_len) {
4339 itemized_len = compose_left_offset_length(buffer, &iter);
4340 item_continuation = TRUE;
4344 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4346 /* go until paragraph end (empty line) */
4347 while (start || !gtk_text_iter_ends_line(&iter)) {
4348 gchar *scanpos = NULL;
4349 /* parse table - in order of priority */
4351 const gchar *needle; /* token */
4353 /* token search function */
4354 gchar *(*search) (const gchar *haystack,
4355 const gchar *needle);
4356 /* part parsing function */
4357 gboolean (*parse) (const gchar *start,
4358 const gchar *scanpos,
4362 /* part to URI function */
4363 gchar *(*build_uri) (const gchar *bp,
4367 static struct table parser[] = {
4368 {"http://", strcasestr, get_uri_part, make_uri_string},
4369 {"https://", strcasestr, get_uri_part, make_uri_string},
4370 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4371 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4372 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4373 {"www.", strcasestr, get_uri_part, make_http_string},
4374 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4375 {"@", strcasestr, get_email_part, make_email_string}
4377 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4378 gint last_index = PARSE_ELEMS;
4380 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4384 if (!prev_autowrap && num_blocks == 0) {
4386 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4387 G_CALLBACK(text_inserted),
4390 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4393 uri_start = uri_stop = -1;
4395 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4398 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4399 if (startq_offset == -1)
4400 startq_offset = gtk_text_iter_get_offset(&iter);
4401 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4402 if (quotelevel > 2) {
4403 /* recycle colors */
4404 if (prefs_common.recycle_quote_colors)
4413 if (startq_offset == -1)
4414 noq_offset = gtk_text_iter_get_offset(&iter);
4418 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4421 if (gtk_text_iter_ends_line(&iter)) {
4423 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4424 prefs_common.linewrap_len,
4426 GtkTextIter prev, next, cur;
4427 if (prev_autowrap != FALSE || force) {
4428 compose->automatic_break = TRUE;
4430 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4431 compose->automatic_break = FALSE;
4432 if (itemized_len && compose->autoindent) {
4433 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4434 if (!item_continuation)
4435 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4437 } else if (quote_str && wrap_quote) {
4438 compose->automatic_break = TRUE;
4440 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4441 compose->automatic_break = FALSE;
4442 if (itemized_len && compose->autoindent) {
4443 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4444 if (!item_continuation)
4445 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4449 /* remove trailing spaces */
4451 rem_item_len = itemized_len;
4452 while (compose->autoindent && rem_item_len-- > 0)
4453 gtk_text_iter_backward_char(&cur);
4454 gtk_text_iter_backward_char(&cur);
4457 while (!gtk_text_iter_starts_line(&cur)) {
4460 gtk_text_iter_backward_char(&cur);
4461 wc = gtk_text_iter_get_char(&cur);
4462 if (!g_unichar_isspace(wc))
4466 if (!gtk_text_iter_equal(&prev, &next)) {
4467 gtk_text_buffer_delete(buffer, &prev, &next);
4469 gtk_text_iter_forward_char(&break_pos);
4473 gtk_text_buffer_insert(buffer, &break_pos,
4477 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4479 /* move iter to current line start */
4480 gtk_text_iter_set_line_offset(&iter, 0);
4487 /* move iter to next line start */
4493 if (!prev_autowrap && num_blocks > 0) {
4495 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4496 G_CALLBACK(text_inserted),
4500 while (!gtk_text_iter_ends_line(&end_of_line)) {
4501 gtk_text_iter_forward_char(&end_of_line);
4503 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4505 nouri_start = gtk_text_iter_get_offset(&iter);
4506 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4508 walk_pos = gtk_text_iter_get_offset(&iter);
4509 /* FIXME: this looks phony. scanning for anything in the parse table */
4510 for (n = 0; n < PARSE_ELEMS; n++) {
4513 tmp = parser[n].search(walk, parser[n].needle);
4515 if (scanpos == NULL || tmp < scanpos) {
4524 /* check if URI can be parsed */
4525 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4526 (const gchar **)&ep, FALSE)
4527 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4531 strlen(parser[last_index].needle);
4534 uri_start = walk_pos + (bp - o_walk);
4535 uri_stop = walk_pos + (ep - o_walk);
4539 gtk_text_iter_forward_line(&iter);
4542 if (startq_offset != -1) {
4543 GtkTextIter startquote, endquote;
4544 gtk_text_buffer_get_iter_at_offset(
4545 buffer, &startquote, startq_offset);
4548 switch (quotelevel) {
4550 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4551 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4552 gtk_text_buffer_apply_tag_by_name(
4553 buffer, "quote0", &startquote, &endquote);
4554 gtk_text_buffer_remove_tag_by_name(
4555 buffer, "quote1", &startquote, &endquote);
4556 gtk_text_buffer_remove_tag_by_name(
4557 buffer, "quote2", &startquote, &endquote);
4562 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4563 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4564 gtk_text_buffer_apply_tag_by_name(
4565 buffer, "quote1", &startquote, &endquote);
4566 gtk_text_buffer_remove_tag_by_name(
4567 buffer, "quote0", &startquote, &endquote);
4568 gtk_text_buffer_remove_tag_by_name(
4569 buffer, "quote2", &startquote, &endquote);
4574 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4575 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4576 gtk_text_buffer_apply_tag_by_name(
4577 buffer, "quote2", &startquote, &endquote);
4578 gtk_text_buffer_remove_tag_by_name(
4579 buffer, "quote0", &startquote, &endquote);
4580 gtk_text_buffer_remove_tag_by_name(
4581 buffer, "quote1", &startquote, &endquote);
4587 } else if (noq_offset != -1) {
4588 GtkTextIter startnoquote, endnoquote;
4589 gtk_text_buffer_get_iter_at_offset(
4590 buffer, &startnoquote, noq_offset);
4593 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4594 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4595 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4596 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4597 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4598 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4599 gtk_text_buffer_remove_tag_by_name(
4600 buffer, "quote0", &startnoquote, &endnoquote);
4601 gtk_text_buffer_remove_tag_by_name(
4602 buffer, "quote1", &startnoquote, &endnoquote);
4603 gtk_text_buffer_remove_tag_by_name(
4604 buffer, "quote2", &startnoquote, &endnoquote);
4610 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4611 GtkTextIter nouri_start_iter, nouri_end_iter;
4612 gtk_text_buffer_get_iter_at_offset(
4613 buffer, &nouri_start_iter, nouri_start);
4614 gtk_text_buffer_get_iter_at_offset(
4615 buffer, &nouri_end_iter, nouri_stop);
4616 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4617 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4618 gtk_text_buffer_remove_tag_by_name(
4619 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4620 modified_before_remove = modified;
4625 if (uri_start >= 0 && uri_stop > 0) {
4626 GtkTextIter uri_start_iter, uri_end_iter, back;
4627 gtk_text_buffer_get_iter_at_offset(
4628 buffer, &uri_start_iter, uri_start);
4629 gtk_text_buffer_get_iter_at_offset(
4630 buffer, &uri_end_iter, uri_stop);
4631 back = uri_end_iter;
4632 gtk_text_iter_backward_char(&back);
4633 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4634 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4635 gtk_text_buffer_apply_tag_by_name(
4636 buffer, "link", &uri_start_iter, &uri_end_iter);
4638 if (removed && !modified_before_remove) {
4644 // debug_print("not modified, out after %d lines\n", lines);
4648 // debug_print("modified, out after %d lines\n", lines);
4650 g_free(itemized_chars);
4653 undo_wrapping(compose->undostruct, FALSE);
4654 compose->autowrap = prev_autowrap;
4659 void compose_action_cb(void *data)
4661 Compose *compose = (Compose *)data;
4662 compose_wrap_all(compose);
4665 static void compose_wrap_all(Compose *compose)
4667 compose_wrap_all_full(compose, FALSE);
4670 static void compose_wrap_all_full(Compose *compose, gboolean force)
4672 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4673 GtkTextBuffer *buffer;
4675 gboolean modified = TRUE;
4677 buffer = gtk_text_view_get_buffer(text);
4679 gtk_text_buffer_get_start_iter(buffer, &iter);
4680 while (!gtk_text_iter_is_end(&iter) && modified)
4681 modified = compose_beautify_paragraph(compose, &iter, force);
4685 static void compose_set_title(Compose *compose)
4691 edited = compose->modified ? _(" [Edited]") : "";
4693 subject = gtk_editable_get_chars(
4694 GTK_EDITABLE(compose->subject_entry), 0, -1);
4696 #ifndef GENERIC_UMPC
4697 if (subject && strlen(subject))
4698 str = g_strdup_printf(_("%s - Compose message%s"),
4701 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4703 str = g_strdup(_("Compose message"));
4706 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4712 * compose_current_mail_account:
4714 * Find a current mail account (the currently selected account, or the
4715 * default account, if a news account is currently selected). If a
4716 * mail account cannot be found, display an error message.
4718 * Return value: Mail account, or NULL if not found.
4720 static PrefsAccount *
4721 compose_current_mail_account(void)
4725 if (cur_account && cur_account->protocol != A_NNTP)
4728 ac = account_get_default();
4729 if (!ac || ac->protocol == A_NNTP) {
4730 alertpanel_error(_("Account for sending mail is not specified.\n"
4731 "Please select a mail account before sending."));
4738 #define QUOTE_IF_REQUIRED(out, str) \
4740 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\")) { \
4744 len = strlen(str) + 3; \
4745 if ((__tmp = alloca(len)) == NULL) { \
4746 g_warning("can't allocate memory\n"); \
4747 g_string_free(header, TRUE); \
4750 g_snprintf(__tmp, len, "\"%s\"", str); \
4755 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4756 g_warning("can't allocate memory\n"); \
4757 g_string_free(header, TRUE); \
4760 strcpy(__tmp, str); \
4766 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4768 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\")) { \
4772 len = strlen(str) + 3; \
4773 if ((__tmp = alloca(len)) == NULL) { \
4774 g_warning("can't allocate memory\n"); \
4777 g_snprintf(__tmp, len, "\"%s\"", str); \
4782 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4783 g_warning("can't allocate memory\n"); \
4786 strcpy(__tmp, str); \
4792 static void compose_select_account(Compose *compose, PrefsAccount *account,
4795 gchar *from = NULL, *header = NULL;
4796 ComposeHeaderEntry *header_entry;
4797 #if GTK_CHECK_VERSION(2, 24, 0)
4801 cm_return_if_fail(account != NULL);
4803 compose->account = account;
4804 if (account->name && *account->name) {
4806 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4807 from = g_strdup_printf("%s <%s>",
4808 buf, account->address);
4809 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4811 from = g_strdup_printf("<%s>",
4813 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4818 compose_set_title(compose);
4820 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4821 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4823 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4824 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4825 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4827 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4829 activate_privacy_system(compose, account, FALSE);
4831 if (!init && compose->mode != COMPOSE_REDIRECT) {
4832 undo_block(compose->undostruct);
4833 compose_insert_sig(compose, TRUE);
4834 undo_unblock(compose->undostruct);
4837 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4838 #if !GTK_CHECK_VERSION(2, 24, 0)
4839 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4841 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4842 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4843 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4846 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4847 if (account->protocol == A_NNTP) {
4848 if (!strcmp(header, _("To:")))
4849 combobox_select_by_text(
4850 GTK_COMBO_BOX(header_entry->combo),
4853 if (!strcmp(header, _("Newsgroups:")))
4854 combobox_select_by_text(
4855 GTK_COMBO_BOX(header_entry->combo),
4863 /* use account's dict info if set */
4864 if (compose->gtkaspell) {
4865 if (account->enable_default_dictionary)
4866 gtkaspell_change_dict(compose->gtkaspell,
4867 account->default_dictionary, FALSE);
4868 if (account->enable_default_alt_dictionary)
4869 gtkaspell_change_alt_dict(compose->gtkaspell,
4870 account->default_alt_dictionary);
4871 if (account->enable_default_dictionary
4872 || account->enable_default_alt_dictionary)
4873 compose_spell_menu_changed(compose);
4878 gboolean compose_check_for_valid_recipient(Compose *compose) {
4879 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4880 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4881 gboolean recipient_found = FALSE;
4885 /* free to and newsgroup list */
4886 slist_free_strings_full(compose->to_list);
4887 compose->to_list = NULL;
4889 slist_free_strings_full(compose->newsgroup_list);
4890 compose->newsgroup_list = NULL;
4892 /* search header entries for to and newsgroup entries */
4893 for (list = compose->header_list; list; list = list->next) {
4896 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4897 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4900 if (entry[0] != '\0') {
4901 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4902 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4903 compose->to_list = address_list_append(compose->to_list, entry);
4904 recipient_found = TRUE;
4907 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4908 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4909 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4910 recipient_found = TRUE;
4917 return recipient_found;
4920 static gboolean compose_check_for_set_recipients(Compose *compose)
4922 if (compose->account->set_autocc && compose->account->auto_cc) {
4923 gboolean found_other = FALSE;
4925 /* search header entries for to and newsgroup entries */
4926 for (list = compose->header_list; list; list = list->next) {
4929 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4930 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4933 if (strcmp(entry, compose->account->auto_cc)
4934 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4944 if (compose->batch) {
4945 gtk_widget_show_all(compose->window);
4947 aval = alertpanel(_("Send"),
4948 _("The only recipient is the default CC address. Send anyway?"),
4949 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4950 if (aval != G_ALERTALTERNATE)
4954 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4955 gboolean found_other = FALSE;
4957 /* search header entries for to and newsgroup entries */
4958 for (list = compose->header_list; list; list = list->next) {
4961 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4962 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4965 if (strcmp(entry, compose->account->auto_bcc)
4966 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4976 if (compose->batch) {
4977 gtk_widget_show_all(compose->window);
4979 aval = alertpanel(_("Send"),
4980 _("The only recipient is the default BCC address. Send anyway?"),
4981 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4982 if (aval != G_ALERTALTERNATE)
4989 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4993 if (compose_check_for_valid_recipient(compose) == FALSE) {
4994 if (compose->batch) {
4995 gtk_widget_show_all(compose->window);
4997 alertpanel_error(_("Recipient is not specified."));
5001 if (compose_check_for_set_recipients(compose) == FALSE) {
5005 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5006 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5007 if (*str == '\0' && check_everything == TRUE &&
5008 compose->mode != COMPOSE_REDIRECT) {
5010 gchar *button_label;
5013 if (compose->sending)
5014 button_label = _("+_Send");
5016 button_label = _("+_Queue");
5017 message = g_strdup_printf(_("Subject is empty. %s"),
5018 compose->sending?_("Send it anyway?"):
5019 _("Queue it anyway?"));
5021 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5022 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5023 ALERT_QUESTION, G_ALERTDEFAULT);
5025 if (aval & G_ALERTDISABLE) {
5026 aval &= ~G_ALERTDISABLE;
5027 prefs_common.warn_empty_subj = FALSE;
5029 if (aval != G_ALERTALTERNATE)
5034 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5040 gint compose_send(Compose *compose)
5043 FolderItem *folder = NULL;
5045 gchar *msgpath = NULL;
5046 gboolean discard_window = FALSE;
5047 gchar *errstr = NULL;
5048 gchar *tmsgid = NULL;
5049 MainWindow *mainwin = mainwindow_get_mainwindow();
5050 gboolean queued_removed = FALSE;
5052 if (prefs_common.send_dialog_invisible
5053 || compose->batch == TRUE)
5054 discard_window = TRUE;
5056 compose_allow_user_actions (compose, FALSE);
5057 compose->sending = TRUE;
5059 if (compose_check_entries(compose, TRUE) == FALSE) {
5060 if (compose->batch) {
5061 gtk_widget_show_all(compose->window);
5067 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5070 if (compose->batch) {
5071 gtk_widget_show_all(compose->window);
5074 alertpanel_error(_("Could not queue message for sending:\n\n"
5075 "Charset conversion failed."));
5076 } else if (val == -5) {
5077 alertpanel_error(_("Could not queue message for sending:\n\n"
5078 "Couldn't get recipient encryption key."));
5079 } else if (val == -6) {
5081 } else if (val == -3) {
5082 if (privacy_peek_error())
5083 alertpanel_error(_("Could not queue message for sending:\n\n"
5084 "Signature failed: %s"), privacy_get_error());
5085 } else if (val == -2 && errno != 0) {
5086 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5088 alertpanel_error(_("Could not queue message for sending."));
5093 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5094 if (discard_window) {
5095 compose->sending = FALSE;
5096 compose_close(compose);
5097 /* No more compose access in the normal codepath
5098 * after this point! */
5103 alertpanel_error(_("The message was queued but could not be "
5104 "sent.\nUse \"Send queued messages\" from "
5105 "the main window to retry."));
5106 if (!discard_window) {
5113 if (msgpath == NULL) {
5114 msgpath = folder_item_fetch_msg(folder, msgnum);
5115 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5118 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5119 claws_unlink(msgpath);
5122 if (!discard_window) {
5124 if (!queued_removed)
5125 folder_item_remove_msg(folder, msgnum);
5126 folder_item_scan(folder);
5128 /* make sure we delete that */
5129 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5131 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5132 folder_item_remove_msg(folder, tmp->msgnum);
5133 procmsg_msginfo_free(tmp);
5140 if (!queued_removed)
5141 folder_item_remove_msg(folder, msgnum);
5142 folder_item_scan(folder);
5144 /* make sure we delete that */
5145 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5147 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5148 folder_item_remove_msg(folder, tmp->msgnum);
5149 procmsg_msginfo_free(tmp);
5152 if (!discard_window) {
5153 compose->sending = FALSE;
5154 compose_allow_user_actions (compose, TRUE);
5155 compose_close(compose);
5159 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5160 "the main window to retry."), errstr);
5163 alertpanel_error_log(_("The message was queued but could not be "
5164 "sent.\nUse \"Send queued messages\" from "
5165 "the main window to retry."));
5167 if (!discard_window) {
5176 toolbar_main_set_sensitive(mainwin);
5177 main_window_set_menu_sensitive(mainwin);
5183 compose_allow_user_actions (compose, TRUE);
5184 compose->sending = FALSE;
5185 compose->modified = TRUE;
5186 toolbar_main_set_sensitive(mainwin);
5187 main_window_set_menu_sensitive(mainwin);
5192 static gboolean compose_use_attach(Compose *compose)
5194 GtkTreeModel *model = gtk_tree_view_get_model
5195 (GTK_TREE_VIEW(compose->attach_clist));
5196 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5199 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5202 gchar buf[BUFFSIZE];
5204 gboolean first_to_address;
5205 gboolean first_cc_address;
5207 ComposeHeaderEntry *headerentry;
5208 const gchar *headerentryname;
5209 const gchar *cc_hdr;
5210 const gchar *to_hdr;
5211 gboolean err = FALSE;
5213 debug_print("Writing redirect header\n");
5215 cc_hdr = prefs_common_translated_header_name("Cc:");
5216 to_hdr = prefs_common_translated_header_name("To:");
5218 first_to_address = TRUE;
5219 for (list = compose->header_list; list; list = list->next) {
5220 headerentry = ((ComposeHeaderEntry *)list->data);
5221 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5223 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5224 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5225 Xstrdup_a(str, entstr, return -1);
5227 if (str[0] != '\0') {
5228 compose_convert_header
5229 (compose, buf, sizeof(buf), str,
5230 strlen("Resent-To") + 2, TRUE);
5232 if (first_to_address) {
5233 err |= (fprintf(fp, "Resent-To: ") < 0);
5234 first_to_address = FALSE;
5236 err |= (fprintf(fp, ",") < 0);
5238 err |= (fprintf(fp, "%s", buf) < 0);
5242 if (!first_to_address) {
5243 err |= (fprintf(fp, "\n") < 0);
5246 first_cc_address = TRUE;
5247 for (list = compose->header_list; list; list = list->next) {
5248 headerentry = ((ComposeHeaderEntry *)list->data);
5249 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5251 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5252 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5253 Xstrdup_a(str, strg, return -1);
5255 if (str[0] != '\0') {
5256 compose_convert_header
5257 (compose, buf, sizeof(buf), str,
5258 strlen("Resent-Cc") + 2, TRUE);
5260 if (first_cc_address) {
5261 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5262 first_cc_address = FALSE;
5264 err |= (fprintf(fp, ",") < 0);
5266 err |= (fprintf(fp, "%s", buf) < 0);
5270 if (!first_cc_address) {
5271 err |= (fprintf(fp, "\n") < 0);
5274 return (err ? -1:0);
5277 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5279 gchar buf[BUFFSIZE];
5281 const gchar *entstr;
5282 /* struct utsname utsbuf; */
5283 gboolean err = FALSE;
5285 cm_return_val_if_fail(fp != NULL, -1);
5286 cm_return_val_if_fail(compose->account != NULL, -1);
5287 cm_return_val_if_fail(compose->account->address != NULL, -1);
5290 get_rfc822_date(buf, sizeof(buf));
5291 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5294 if (compose->account->name && *compose->account->name) {
5295 compose_convert_header
5296 (compose, buf, sizeof(buf), compose->account->name,
5297 strlen("From: "), TRUE);
5298 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5299 buf, compose->account->address) < 0);
5301 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5304 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5305 if (*entstr != '\0') {
5306 Xstrdup_a(str, entstr, return -1);
5309 compose_convert_header(compose, buf, sizeof(buf), str,
5310 strlen("Subject: "), FALSE);
5311 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5315 /* Resent-Message-ID */
5316 if (compose->account->set_domain && compose->account->domain) {
5317 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5318 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5319 g_snprintf(buf, sizeof(buf), "%s",
5320 strchr(compose->account->address, '@') ?
5321 strchr(compose->account->address, '@')+1 :
5322 compose->account->address);
5324 g_snprintf(buf, sizeof(buf), "%s", "");
5327 if (compose->account->gen_msgid) {
5329 if (compose->account->msgid_with_addr) {
5330 addr = compose->account->address;
5332 generate_msgid(buf, sizeof(buf), addr);
5333 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5334 compose->msgid = g_strdup(buf);
5336 compose->msgid = NULL;
5339 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5342 /* separator between header and body */
5343 err |= (fputs("\n", fp) == EOF);
5345 return (err ? -1:0);
5348 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5352 gchar buf[BUFFSIZE];
5354 gboolean skip = FALSE;
5355 gboolean err = FALSE;
5356 gchar *not_included[]={
5357 "Return-Path:", "Delivered-To:", "Received:",
5358 "Subject:", "X-UIDL:", "AF:",
5359 "NF:", "PS:", "SRH:",
5360 "SFN:", "DSR:", "MID:",
5361 "CFG:", "PT:", "S:",
5362 "RQ:", "SSV:", "NSV:",
5363 "SSH:", "R:", "MAID:",
5364 "NAID:", "RMID:", "FMID:",
5365 "SCF:", "RRCPT:", "NG:",
5366 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5367 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5368 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5369 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5370 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5373 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5374 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5378 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5380 for (i = 0; not_included[i] != NULL; i++) {
5381 if (g_ascii_strncasecmp(buf, not_included[i],
5382 strlen(not_included[i])) == 0) {
5389 if (fputs(buf, fdest) == -1)
5392 if (!prefs_common.redirect_keep_from) {
5393 if (g_ascii_strncasecmp(buf, "From:",
5394 strlen("From:")) == 0) {
5395 err |= (fputs(" (by way of ", fdest) == EOF);
5396 if (compose->account->name
5397 && *compose->account->name) {
5398 compose_convert_header
5399 (compose, buf, sizeof(buf),
5400 compose->account->name,
5403 err |= (fprintf(fdest, "%s <%s>",
5405 compose->account->address) < 0);
5407 err |= (fprintf(fdest, "%s",
5408 compose->account->address) < 0);
5409 err |= (fputs(")", fdest) == EOF);
5413 if (fputs("\n", fdest) == -1)
5420 if (compose_redirect_write_headers(compose, fdest))
5423 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5424 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5437 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5439 GtkTextBuffer *buffer;
5440 GtkTextIter start, end;
5443 const gchar *out_codeset;
5444 EncodingType encoding = ENC_UNKNOWN;
5445 MimeInfo *mimemsg, *mimetext;
5447 const gchar *src_codeset = CS_INTERNAL;
5448 gchar *from_addr = NULL;
5449 gchar *from_name = NULL;
5451 if (action == COMPOSE_WRITE_FOR_SEND)
5452 attach_parts = TRUE;
5454 /* create message MimeInfo */
5455 mimemsg = procmime_mimeinfo_new();
5456 mimemsg->type = MIMETYPE_MESSAGE;
5457 mimemsg->subtype = g_strdup("rfc822");
5458 mimemsg->content = MIMECONTENT_MEM;
5459 mimemsg->tmp = TRUE; /* must free content later */
5460 mimemsg->data.mem = compose_get_header(compose);
5462 /* Create text part MimeInfo */
5463 /* get all composed text */
5464 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5465 gtk_text_buffer_get_start_iter(buffer, &start);
5466 gtk_text_buffer_get_end_iter(buffer, &end);
5467 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5469 out_codeset = conv_get_charset_str(compose->out_encoding);
5471 if (!out_codeset && is_ascii_str(chars)) {
5472 out_codeset = CS_US_ASCII;
5473 } else if (prefs_common.outgoing_fallback_to_ascii &&
5474 is_ascii_str(chars)) {
5475 out_codeset = CS_US_ASCII;
5476 encoding = ENC_7BIT;
5480 gchar *test_conv_global_out = NULL;
5481 gchar *test_conv_reply = NULL;
5483 /* automatic mode. be automatic. */
5484 codeconv_set_strict(TRUE);
5486 out_codeset = conv_get_outgoing_charset_str();
5488 debug_print("trying to convert to %s\n", out_codeset);
5489 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5492 if (!test_conv_global_out && compose->orig_charset
5493 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5494 out_codeset = compose->orig_charset;
5495 debug_print("failure; trying to convert to %s\n", out_codeset);
5496 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5499 if (!test_conv_global_out && !test_conv_reply) {
5501 out_codeset = CS_INTERNAL;
5502 debug_print("failure; finally using %s\n", out_codeset);
5504 g_free(test_conv_global_out);
5505 g_free(test_conv_reply);
5506 codeconv_set_strict(FALSE);
5509 if (encoding == ENC_UNKNOWN) {
5510 if (prefs_common.encoding_method == CTE_BASE64)
5511 encoding = ENC_BASE64;
5512 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5513 encoding = ENC_QUOTED_PRINTABLE;
5514 else if (prefs_common.encoding_method == CTE_8BIT)
5515 encoding = ENC_8BIT;
5517 encoding = procmime_get_encoding_for_charset(out_codeset);
5520 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5521 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5523 if (action == COMPOSE_WRITE_FOR_SEND) {
5524 codeconv_set_strict(TRUE);
5525 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5526 codeconv_set_strict(FALSE);
5532 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5533 "to the specified %s charset.\n"
5534 "Send it as %s?"), out_codeset, src_codeset);
5535 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5536 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5539 if (aval != G_ALERTALTERNATE) {
5544 out_codeset = src_codeset;
5550 out_codeset = src_codeset;
5555 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5556 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5557 strstr(buf, "\nFrom ") != NULL) {
5558 encoding = ENC_QUOTED_PRINTABLE;
5562 mimetext = procmime_mimeinfo_new();
5563 mimetext->content = MIMECONTENT_MEM;
5564 mimetext->tmp = TRUE; /* must free content later */
5565 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5566 * and free the data, which we need later. */
5567 mimetext->data.mem = g_strdup(buf);
5568 mimetext->type = MIMETYPE_TEXT;
5569 mimetext->subtype = g_strdup("plain");
5570 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5571 g_strdup(out_codeset));
5573 /* protect trailing spaces when signing message */
5574 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5575 privacy_system_can_sign(compose->privacy_system)) {
5576 encoding = ENC_QUOTED_PRINTABLE;
5579 debug_print("main text: %zd bytes encoded as %s in %d\n",
5580 strlen(buf), out_codeset, encoding);
5582 /* check for line length limit */
5583 if (action == COMPOSE_WRITE_FOR_SEND &&
5584 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5585 check_line_length(buf, 1000, &line) < 0) {
5589 msg = g_strdup_printf
5590 (_("Line %d exceeds the line length limit (998 bytes).\n"
5591 "The contents of the message might be broken on the way to the delivery.\n"
5593 "Send it anyway?"), line + 1);
5594 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5596 if (aval != G_ALERTALTERNATE) {
5602 if (encoding != ENC_UNKNOWN)
5603 procmime_encode_content(mimetext, encoding);
5605 /* append attachment parts */
5606 if (compose_use_attach(compose) && attach_parts) {
5607 MimeInfo *mimempart;
5608 gchar *boundary = NULL;
5609 mimempart = procmime_mimeinfo_new();
5610 mimempart->content = MIMECONTENT_EMPTY;
5611 mimempart->type = MIMETYPE_MULTIPART;
5612 mimempart->subtype = g_strdup("mixed");
5616 boundary = generate_mime_boundary(NULL);
5617 } while (strstr(buf, boundary) != NULL);
5619 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5622 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5624 g_node_append(mimempart->node, mimetext->node);
5625 g_node_append(mimemsg->node, mimempart->node);
5627 if (compose_add_attachments(compose, mimempart) < 0)
5630 g_node_append(mimemsg->node, mimetext->node);
5634 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5635 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5636 /* extract name and address */
5637 if (strstr(spec, " <") && strstr(spec, ">")) {
5638 from_addr = g_strdup(strrchr(spec, '<')+1);
5639 *(strrchr(from_addr, '>')) = '\0';
5640 from_name = g_strdup(spec);
5641 *(strrchr(from_name, '<')) = '\0';
5648 /* sign message if sending */
5649 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5650 privacy_system_can_sign(compose->privacy_system))
5651 if (!privacy_sign(compose->privacy_system, mimemsg,
5652 compose->account, from_addr)) {
5659 procmime_write_mimeinfo(mimemsg, fp);
5661 procmime_mimeinfo_free_all(mimemsg);
5666 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5668 GtkTextBuffer *buffer;
5669 GtkTextIter start, end;
5674 if ((fp = g_fopen(file, "wb")) == NULL) {
5675 FILE_OP_ERROR(file, "fopen");
5679 /* chmod for security */
5680 if (change_file_mode_rw(fp, file) < 0) {
5681 FILE_OP_ERROR(file, "chmod");
5682 g_warning("can't change file mode\n");
5685 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5686 gtk_text_buffer_get_start_iter(buffer, &start);
5687 gtk_text_buffer_get_end_iter(buffer, &end);
5688 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5690 chars = conv_codeset_strdup
5691 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5694 if (!chars) return -1;
5697 len = strlen(chars);
5698 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5699 FILE_OP_ERROR(file, "fwrite");
5708 if (fclose(fp) == EOF) {
5709 FILE_OP_ERROR(file, "fclose");
5716 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5719 MsgInfo *msginfo = compose->targetinfo;
5721 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5722 if (!msginfo) return -1;
5724 if (!force && MSG_IS_LOCKED(msginfo->flags))
5727 item = msginfo->folder;
5728 cm_return_val_if_fail(item != NULL, -1);
5730 if (procmsg_msg_exist(msginfo) &&
5731 (folder_has_parent_of_type(item, F_QUEUE) ||
5732 folder_has_parent_of_type(item, F_DRAFT)
5733 || msginfo == compose->autosaved_draft)) {
5734 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5735 g_warning("can't remove the old message\n");
5738 debug_print("removed reedit target %d\n", msginfo->msgnum);
5745 static void compose_remove_draft(Compose *compose)
5748 MsgInfo *msginfo = compose->targetinfo;
5749 drafts = account_get_special_folder(compose->account, F_DRAFT);
5751 if (procmsg_msg_exist(msginfo)) {
5752 folder_item_remove_msg(drafts, msginfo->msgnum);
5757 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5758 gboolean remove_reedit_target)
5760 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5763 static gboolean compose_warn_encryption(Compose *compose)
5765 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5766 AlertValue val = G_ALERTALTERNATE;
5768 if (warning == NULL)
5771 val = alertpanel_full(_("Encryption warning"), warning,
5772 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5773 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5774 if (val & G_ALERTDISABLE) {
5775 val &= ~G_ALERTDISABLE;
5776 if (val == G_ALERTALTERNATE)
5777 privacy_inhibit_encrypt_warning(compose->privacy_system,
5781 if (val == G_ALERTALTERNATE) {
5788 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5789 gchar **msgpath, gboolean check_subject,
5790 gboolean remove_reedit_target)
5797 PrefsAccount *mailac = NULL, *newsac = NULL;
5798 gboolean err = FALSE;
5800 debug_print("queueing message...\n");
5801 cm_return_val_if_fail(compose->account != NULL, -1);
5803 if (compose_check_entries(compose, check_subject) == FALSE) {
5804 if (compose->batch) {
5805 gtk_widget_show_all(compose->window);
5810 if (!compose->to_list && !compose->newsgroup_list) {
5811 g_warning("can't get recipient list.");
5815 if (compose->to_list) {
5816 if (compose->account->protocol != A_NNTP)
5817 mailac = compose->account;
5818 else if (cur_account && cur_account->protocol != A_NNTP)
5819 mailac = cur_account;
5820 else if (!(mailac = compose_current_mail_account())) {
5821 alertpanel_error(_("No account for sending mails available!"));
5826 if (compose->newsgroup_list) {
5827 if (compose->account->protocol == A_NNTP)
5828 newsac = compose->account;
5830 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5835 /* write queue header */
5836 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5837 G_DIR_SEPARATOR, compose, (guint) rand());
5838 debug_print("queuing to %s\n", tmp);
5839 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5840 FILE_OP_ERROR(tmp, "fopen");
5845 if (change_file_mode_rw(fp, tmp) < 0) {
5846 FILE_OP_ERROR(tmp, "chmod");
5847 g_warning("can't change file mode\n");
5850 /* queueing variables */
5851 err |= (fprintf(fp, "AF:\n") < 0);
5852 err |= (fprintf(fp, "NF:0\n") < 0);
5853 err |= (fprintf(fp, "PS:10\n") < 0);
5854 err |= (fprintf(fp, "SRH:1\n") < 0);
5855 err |= (fprintf(fp, "SFN:\n") < 0);
5856 err |= (fprintf(fp, "DSR:\n") < 0);
5858 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5860 err |= (fprintf(fp, "MID:\n") < 0);
5861 err |= (fprintf(fp, "CFG:\n") < 0);
5862 err |= (fprintf(fp, "PT:0\n") < 0);
5863 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5864 err |= (fprintf(fp, "RQ:\n") < 0);
5866 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5868 err |= (fprintf(fp, "SSV:\n") < 0);
5870 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5872 err |= (fprintf(fp, "NSV:\n") < 0);
5873 err |= (fprintf(fp, "SSH:\n") < 0);
5874 /* write recepient list */
5875 if (compose->to_list) {
5876 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5877 for (cur = compose->to_list->next; cur != NULL;
5879 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5880 err |= (fprintf(fp, "\n") < 0);
5882 /* write newsgroup list */
5883 if (compose->newsgroup_list) {
5884 err |= (fprintf(fp, "NG:") < 0);
5885 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5886 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5887 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5888 err |= (fprintf(fp, "\n") < 0);
5890 /* Sylpheed account IDs */
5892 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5894 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5897 if (compose->privacy_system != NULL) {
5898 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5899 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5900 if (compose->use_encryption) {
5902 if (!compose_warn_encryption(compose)) {
5908 if (mailac && mailac->encrypt_to_self) {
5909 GSList *tmp_list = g_slist_copy(compose->to_list);
5910 tmp_list = g_slist_append(tmp_list, compose->account->address);
5911 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5912 g_slist_free(tmp_list);
5914 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5916 if (encdata != NULL) {
5917 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5918 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5919 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5921 } /* else we finally dont want to encrypt */
5923 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5924 /* and if encdata was null, it means there's been a problem in
5927 g_warning("failed to write queue message");
5937 /* Save copy folder */
5938 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5939 gchar *savefolderid;
5941 savefolderid = compose_get_save_to(compose);
5942 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5943 g_free(savefolderid);
5945 /* Save copy folder */
5946 if (compose->return_receipt) {
5947 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5949 /* Message-ID of message replying to */
5950 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5953 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5954 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5957 /* Message-ID of message forwarding to */
5958 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5961 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5962 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5966 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5967 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5969 /* end of headers */
5970 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5972 if (compose->redirect_filename != NULL) {
5973 if (compose_redirect_write_to_file(compose, fp) < 0) {
5981 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5985 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5989 g_warning("failed to write queue message\n");
5995 if (fclose(fp) == EOF) {
5996 FILE_OP_ERROR(tmp, "fclose");
6002 if (item && *item) {
6005 queue = account_get_special_folder(compose->account, F_QUEUE);
6008 g_warning("can't find queue folder\n");
6013 folder_item_scan(queue);
6014 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6015 g_warning("can't queue the message\n");
6021 if (msgpath == NULL) {
6027 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6028 compose_remove_reedit_target(compose, FALSE);
6031 if ((msgnum != NULL) && (item != NULL)) {
6039 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6042 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6044 struct stat statbuf;
6045 gchar *type, *subtype;
6046 GtkTreeModel *model;
6049 model = gtk_tree_view_get_model(tree_view);
6051 if (!gtk_tree_model_get_iter_first(model, &iter))
6054 gtk_tree_model_get(model, &iter,
6058 if (!is_file_exist(ainfo->file)) {
6059 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6060 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6061 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6063 if (val == G_ALERTDEFAULT) {
6068 mimepart = procmime_mimeinfo_new();
6069 mimepart->content = MIMECONTENT_FILE;
6070 mimepart->data.filename = g_strdup(ainfo->file);
6071 mimepart->tmp = FALSE; /* or we destroy our attachment */
6072 mimepart->offset = 0;
6074 g_stat(ainfo->file, &statbuf);
6075 mimepart->length = statbuf.st_size;
6077 type = g_strdup(ainfo->content_type);
6079 if (!strchr(type, '/')) {
6081 type = g_strdup("application/octet-stream");
6084 subtype = strchr(type, '/') + 1;
6085 *(subtype - 1) = '\0';
6086 mimepart->type = procmime_get_media_type(type);
6087 mimepart->subtype = g_strdup(subtype);
6090 if (mimepart->type == MIMETYPE_MESSAGE &&
6091 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6092 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6093 } else if (mimepart->type == MIMETYPE_TEXT) {
6094 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6095 /* Text parts with no name come from multipart/alternative
6096 * forwards. Make sure the recipient won't look at the
6097 * original HTML part by mistake. */
6098 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6099 ainfo->name = g_strdup_printf(_("Original %s part"),
6103 g_hash_table_insert(mimepart->typeparameters,
6104 g_strdup("charset"), g_strdup(ainfo->charset));
6106 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6107 if (mimepart->type == MIMETYPE_APPLICATION &&
6108 !strcmp2(mimepart->subtype, "octet-stream"))
6109 g_hash_table_insert(mimepart->typeparameters,
6110 g_strdup("name"), g_strdup(ainfo->name));
6111 g_hash_table_insert(mimepart->dispositionparameters,
6112 g_strdup("filename"), g_strdup(ainfo->name));
6113 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6116 if (mimepart->type == MIMETYPE_MESSAGE
6117 || mimepart->type == MIMETYPE_MULTIPART)
6118 ainfo->encoding = ENC_BINARY;
6119 else if (compose->use_signing) {
6120 if (ainfo->encoding == ENC_7BIT)
6121 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6122 else if (ainfo->encoding == ENC_8BIT)
6123 ainfo->encoding = ENC_BASE64;
6128 procmime_encode_content(mimepart, ainfo->encoding);
6130 g_node_append(parent->node, mimepart->node);
6131 } while (gtk_tree_model_iter_next(model, &iter));
6136 static gchar *compose_quote_list_of_addresses(gchar *str)
6138 GSList *list = NULL, *item = NULL;
6139 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6141 list = address_list_append_with_comments(list, str);
6142 for (item = list; item != NULL; item = item->next) {
6143 gchar *spec = item->data;
6144 gchar *endofname = strstr(spec, " <");
6145 if (endofname != NULL) {
6147 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6149 if (*qname != *spec) { /* has been quoted, compute new */
6150 gchar *addr = g_strdup(endofname);
6151 gchar *name = g_strdup(qname);
6152 faddr = g_strconcat(name, addr, NULL);
6155 debug_print("new auto-quoted address: '%s'", faddr);
6159 result = g_strdup((faddr != NULL)? faddr: spec);
6161 result = g_strconcat(result,
6163 (faddr != NULL)? faddr: spec,
6166 if (faddr != NULL) {
6171 slist_free_strings_full(list);
6176 #define IS_IN_CUSTOM_HEADER(header) \
6177 (compose->account->add_customhdr && \
6178 custom_header_find(compose->account->customhdr_list, header) != NULL)
6180 static void compose_add_headerfield_from_headerlist(Compose *compose,
6182 const gchar *fieldname,
6183 const gchar *seperator)
6185 gchar *str, *fieldname_w_colon;
6186 gboolean add_field = FALSE;
6188 ComposeHeaderEntry *headerentry;
6189 const gchar *headerentryname;
6190 const gchar *trans_fieldname;
6193 if (IS_IN_CUSTOM_HEADER(fieldname))
6196 debug_print("Adding %s-fields\n", fieldname);
6198 fieldstr = g_string_sized_new(64);
6200 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6201 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6203 for (list = compose->header_list; list; list = list->next) {
6204 headerentry = ((ComposeHeaderEntry *)list->data);
6205 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6207 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6208 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6210 str = compose_quote_list_of_addresses(ustr);
6212 if (str != NULL && str[0] != '\0') {
6214 g_string_append(fieldstr, seperator);
6215 g_string_append(fieldstr, str);
6224 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6225 compose_convert_header
6226 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6227 strlen(fieldname) + 2, TRUE);
6228 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6232 g_free(fieldname_w_colon);
6233 g_string_free(fieldstr, TRUE);
6238 static gchar *compose_get_manual_headers_info(Compose *compose)
6240 GString *sh_header = g_string_new(" ");
6242 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6244 for (list = compose->header_list; list; list = list->next) {
6245 ComposeHeaderEntry *headerentry;
6248 gchar *headername_wcolon;
6249 const gchar *headername_trans;
6251 gboolean standard_header = FALSE;
6253 headerentry = ((ComposeHeaderEntry *)list->data);
6255 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6257 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6262 if (!strstr(tmp, ":")) {
6263 headername_wcolon = g_strconcat(tmp, ":", NULL);
6264 headername = g_strdup(tmp);
6266 headername_wcolon = g_strdup(tmp);
6267 headername = g_strdup(strtok(tmp, ":"));
6271 string = std_headers;
6272 while (*string != NULL) {
6273 headername_trans = prefs_common_translated_header_name(*string);
6274 if (!strcmp(headername_trans, headername_wcolon))
6275 standard_header = TRUE;
6278 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6279 g_string_append_printf(sh_header, "%s ", headername);
6281 g_free(headername_wcolon);
6283 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6284 return g_string_free(sh_header, FALSE);
6287 static gchar *compose_get_header(Compose *compose)
6289 gchar buf[BUFFSIZE];
6290 const gchar *entry_str;
6294 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6296 gchar *from_name = NULL, *from_address = NULL;
6299 cm_return_val_if_fail(compose->account != NULL, NULL);
6300 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6302 header = g_string_sized_new(64);
6305 get_rfc822_date(buf, sizeof(buf));
6306 g_string_append_printf(header, "Date: %s\n", buf);
6310 if (compose->account->name && *compose->account->name) {
6312 QUOTE_IF_REQUIRED(buf, compose->account->name);
6313 tmp = g_strdup_printf("%s <%s>",
6314 buf, compose->account->address);
6316 tmp = g_strdup_printf("%s",
6317 compose->account->address);
6319 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6320 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6322 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6323 from_address = g_strdup(compose->account->address);
6325 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6326 /* extract name and address */
6327 if (strstr(spec, " <") && strstr(spec, ">")) {
6328 from_address = g_strdup(strrchr(spec, '<')+1);
6329 *(strrchr(from_address, '>')) = '\0';
6330 from_name = g_strdup(spec);
6331 *(strrchr(from_name, '<')) = '\0';
6334 from_address = g_strdup(spec);
6341 if (from_name && *from_name) {
6342 compose_convert_header
6343 (compose, buf, sizeof(buf), from_name,
6344 strlen("From: "), TRUE);
6345 QUOTE_IF_REQUIRED(name, buf);
6347 g_string_append_printf(header, "From: %s <%s>\n",
6348 name, from_address);
6350 g_string_append_printf(header, "From: %s\n", from_address);
6353 g_free(from_address);
6356 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6359 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6362 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6366 * If this account is a NNTP account remove Bcc header from
6367 * message body since it otherwise will be publicly shown
6369 if (compose->account->protocol != A_NNTP)
6370 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6373 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6375 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6378 compose_convert_header(compose, buf, sizeof(buf), str,
6379 strlen("Subject: "), FALSE);
6380 g_string_append_printf(header, "Subject: %s\n", buf);
6386 if (compose->account->set_domain && compose->account->domain) {
6387 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6388 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6389 g_snprintf(buf, sizeof(buf), "%s",
6390 strchr(compose->account->address, '@') ?
6391 strchr(compose->account->address, '@')+1 :
6392 compose->account->address);
6394 g_snprintf(buf, sizeof(buf), "%s", "");
6397 if (compose->account->gen_msgid) {
6399 if (compose->account->msgid_with_addr) {
6400 addr = compose->account->address;
6402 generate_msgid(buf, sizeof(buf), addr);
6403 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6404 compose->msgid = g_strdup(buf);
6406 compose->msgid = NULL;
6409 if (compose->remove_references == FALSE) {
6411 if (compose->inreplyto && compose->to_list)
6412 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6415 if (compose->references)
6416 g_string_append_printf(header, "References: %s\n", compose->references);
6420 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6423 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6426 if (compose->account->organization &&
6427 strlen(compose->account->organization) &&
6428 !IS_IN_CUSTOM_HEADER("Organization")) {
6429 compose_convert_header(compose, buf, sizeof(buf),
6430 compose->account->organization,
6431 strlen("Organization: "), FALSE);
6432 g_string_append_printf(header, "Organization: %s\n", buf);
6435 /* Program version and system info */
6436 if (compose->account->gen_xmailer &&
6437 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6438 !compose->newsgroup_list) {
6439 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6441 gtk_major_version, gtk_minor_version, gtk_micro_version,
6444 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6445 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6447 gtk_major_version, gtk_minor_version, gtk_micro_version,
6451 /* custom headers */
6452 if (compose->account->add_customhdr) {
6455 for (cur = compose->account->customhdr_list; cur != NULL;
6457 CustomHeader *chdr = (CustomHeader *)cur->data;
6459 if (custom_header_is_allowed(chdr->name)
6460 && chdr->value != NULL
6461 && *(chdr->value) != '\0') {
6462 compose_convert_header
6463 (compose, buf, sizeof(buf),
6465 strlen(chdr->name) + 2, FALSE);
6466 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6471 /* Automatic Faces and X-Faces */
6472 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6473 g_string_append_printf(header, "X-Face: %s\n", buf);
6475 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6476 g_string_append_printf(header, "X-Face: %s\n", buf);
6478 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6479 g_string_append_printf(header, "Face: %s\n", buf);
6481 else if (get_default_face (buf, sizeof(buf)) == 0) {
6482 g_string_append_printf(header, "Face: %s\n", buf);
6486 switch (compose->priority) {
6487 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6488 "X-Priority: 1 (Highest)\n");
6490 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6491 "X-Priority: 2 (High)\n");
6493 case PRIORITY_NORMAL: break;
6494 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6495 "X-Priority: 4 (Low)\n");
6497 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6498 "X-Priority: 5 (Lowest)\n");
6500 default: debug_print("compose: priority unknown : %d\n",
6504 /* Request Return Receipt */
6505 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6506 if (compose->return_receipt) {
6507 if (compose->account->name
6508 && *compose->account->name) {
6509 compose_convert_header(compose, buf, sizeof(buf),
6510 compose->account->name,
6511 strlen("Disposition-Notification-To: "),
6513 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6515 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6519 /* get special headers */
6520 for (list = compose->header_list; list; list = list->next) {
6521 ComposeHeaderEntry *headerentry;
6524 gchar *headername_wcolon;
6525 const gchar *headername_trans;
6528 gboolean standard_header = FALSE;
6530 headerentry = ((ComposeHeaderEntry *)list->data);
6532 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6534 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6539 if (!strstr(tmp, ":")) {
6540 headername_wcolon = g_strconcat(tmp, ":", NULL);
6541 headername = g_strdup(tmp);
6543 headername_wcolon = g_strdup(tmp);
6544 headername = g_strdup(strtok(tmp, ":"));
6548 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6549 Xstrdup_a(headervalue, entry_str, return NULL);
6550 subst_char(headervalue, '\r', ' ');
6551 subst_char(headervalue, '\n', ' ');
6552 string = std_headers;
6553 while (*string != NULL) {
6554 headername_trans = prefs_common_translated_header_name(*string);
6555 if (!strcmp(headername_trans, headername_wcolon))
6556 standard_header = TRUE;
6559 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6560 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6563 g_free(headername_wcolon);
6567 g_string_free(header, FALSE);
6572 #undef IS_IN_CUSTOM_HEADER
6574 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6575 gint header_len, gboolean addr_field)
6577 gchar *tmpstr = NULL;
6578 const gchar *out_codeset = NULL;
6580 cm_return_if_fail(src != NULL);
6581 cm_return_if_fail(dest != NULL);
6583 if (len < 1) return;
6585 tmpstr = g_strdup(src);
6587 subst_char(tmpstr, '\n', ' ');
6588 subst_char(tmpstr, '\r', ' ');
6591 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6592 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6593 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6598 codeconv_set_strict(TRUE);
6599 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6600 conv_get_charset_str(compose->out_encoding));
6601 codeconv_set_strict(FALSE);
6603 if (!dest || *dest == '\0') {
6604 gchar *test_conv_global_out = NULL;
6605 gchar *test_conv_reply = NULL;
6607 /* automatic mode. be automatic. */
6608 codeconv_set_strict(TRUE);
6610 out_codeset = conv_get_outgoing_charset_str();
6612 debug_print("trying to convert to %s\n", out_codeset);
6613 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6616 if (!test_conv_global_out && compose->orig_charset
6617 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6618 out_codeset = compose->orig_charset;
6619 debug_print("failure; trying to convert to %s\n", out_codeset);
6620 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6623 if (!test_conv_global_out && !test_conv_reply) {
6625 out_codeset = CS_INTERNAL;
6626 debug_print("finally using %s\n", out_codeset);
6628 g_free(test_conv_global_out);
6629 g_free(test_conv_reply);
6630 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6632 codeconv_set_strict(FALSE);
6637 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6641 cm_return_if_fail(user_data != NULL);
6643 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6644 g_strstrip(address);
6645 if (*address != '\0') {
6646 gchar *name = procheader_get_fromname(address);
6647 extract_address(address);
6648 #ifndef USE_NEW_ADDRBOOK
6649 addressbook_add_contact(name, address, NULL, NULL);
6651 debug_print("%s: %s\n", name, address);
6652 if (addressadd_selection(name, address, NULL, NULL)) {
6653 debug_print( "addressbook_add_contact - added\n" );
6660 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6662 GtkWidget *menuitem;
6665 cm_return_if_fail(menu != NULL);
6666 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6668 menuitem = gtk_separator_menu_item_new();
6669 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6670 gtk_widget_show(menuitem);
6672 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6673 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6675 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6676 g_strstrip(address);
6677 if (*address == '\0') {
6678 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6681 g_signal_connect(G_OBJECT(menuitem), "activate",
6682 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6683 gtk_widget_show(menuitem);
6686 void compose_add_extra_header(gchar *header, GtkListStore *model)
6689 if (strcmp(header, "")) {
6690 COMBOBOX_ADD(model, header, COMPOSE_TO);
6694 void compose_add_extra_header_entries(GtkListStore *model)
6698 gchar buf[BUFFSIZE];
6701 if (extra_headers == NULL) {
6702 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6703 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6704 debug_print("extra headers file not found\n");
6705 goto extra_headers_done;
6707 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6708 lastc = strlen(buf) - 1; /* remove trailing control chars */
6709 while (lastc >= 0 && buf[lastc] != ':')
6710 buf[lastc--] = '\0';
6711 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6712 buf[lastc] = '\0'; /* remove trailing : for comparison */
6713 if (custom_header_is_allowed(buf)) {
6715 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6718 g_message("disallowed extra header line: %s\n", buf);
6722 g_message("invalid extra header line: %s\n", buf);
6728 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6729 extra_headers = g_slist_reverse(extra_headers);
6731 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6734 static void compose_create_header_entry(Compose *compose)
6736 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6743 const gchar *header = NULL;
6744 ComposeHeaderEntry *headerentry;
6745 gboolean standard_header = FALSE;
6746 GtkListStore *model;
6748 #if !(GTK_CHECK_VERSION(2,12,0))
6749 GtkTooltips *tips = compose->tooltips;
6752 headerentry = g_new0(ComposeHeaderEntry, 1);
6754 /* Combo box model */
6755 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6756 #if !GTK_CHECK_VERSION(2, 24, 0)
6757 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6759 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6761 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6763 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6765 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6766 COMPOSE_NEWSGROUPS);
6767 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6769 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6770 COMPOSE_FOLLOWUPTO);
6771 compose_add_extra_header_entries(model);
6774 #if GTK_CHECK_VERSION(2, 24, 0)
6775 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6776 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6777 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6778 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6779 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6781 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6782 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6783 G_CALLBACK(compose_grab_focus_cb), compose);
6784 gtk_widget_show(combo);
6787 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6788 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6791 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6792 compose->header_nextrow, compose->header_nextrow+1,
6793 GTK_SHRINK, GTK_FILL, 0, 0);
6794 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6795 const gchar *last_header_entry = gtk_entry_get_text(
6796 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6798 while (*string != NULL) {
6799 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6800 standard_header = TRUE;
6803 if (standard_header)
6804 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6806 if (!compose->header_last || !standard_header) {
6807 switch(compose->account->protocol) {
6809 header = prefs_common_translated_header_name("Newsgroups:");
6812 header = prefs_common_translated_header_name("To:");
6817 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6819 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6820 G_CALLBACK(compose_grab_focus_cb), compose);
6822 /* Entry field with cleanup button */
6823 button = gtk_button_new();
6824 gtk_button_set_image(GTK_BUTTON(button),
6825 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6826 gtk_widget_show(button);
6827 CLAWS_SET_TIP(button,
6828 _("Delete entry contents"));
6829 entry = gtk_entry_new();
6830 gtk_widget_show(entry);
6831 CLAWS_SET_TIP(entry,
6832 _("Use <tab> to autocomplete from addressbook"));
6833 hbox = gtk_hbox_new (FALSE, 0);
6834 gtk_widget_show(hbox);
6835 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6836 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6837 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6838 compose->header_nextrow, compose->header_nextrow+1,
6839 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6841 g_signal_connect(G_OBJECT(entry), "key-press-event",
6842 G_CALLBACK(compose_headerentry_key_press_event_cb),
6844 g_signal_connect(G_OBJECT(entry), "changed",
6845 G_CALLBACK(compose_headerentry_changed_cb),
6847 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6848 G_CALLBACK(compose_grab_focus_cb), compose);
6850 g_signal_connect(G_OBJECT(button), "clicked",
6851 G_CALLBACK(compose_headerentry_button_clicked_cb),
6855 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6856 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6857 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6858 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6859 G_CALLBACK(compose_header_drag_received_cb),
6861 g_signal_connect(G_OBJECT(entry), "drag-drop",
6862 G_CALLBACK(compose_drag_drop),
6864 g_signal_connect(G_OBJECT(entry), "populate-popup",
6865 G_CALLBACK(compose_entry_popup_extend),
6868 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6870 headerentry->compose = compose;
6871 headerentry->combo = combo;
6872 headerentry->entry = entry;
6873 headerentry->button = button;
6874 headerentry->hbox = hbox;
6875 headerentry->headernum = compose->header_nextrow;
6876 headerentry->type = PREF_NONE;
6878 compose->header_nextrow++;
6879 compose->header_last = headerentry;
6880 compose->header_list =
6881 g_slist_append(compose->header_list,
6885 static void compose_add_header_entry(Compose *compose, const gchar *header,
6886 gchar *text, ComposePrefType pref_type)
6888 ComposeHeaderEntry *last_header = compose->header_last;
6889 gchar *tmp = g_strdup(text), *email;
6890 gboolean replyto_hdr;
6892 replyto_hdr = (!strcasecmp(header,
6893 prefs_common_translated_header_name("Reply-To:")) ||
6895 prefs_common_translated_header_name("Followup-To:")) ||
6897 prefs_common_translated_header_name("In-Reply-To:")));
6899 extract_address(tmp);
6900 email = g_utf8_strdown(tmp, -1);
6902 if (replyto_hdr == FALSE &&
6903 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6905 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6906 header, text, (gint) pref_type);
6912 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6913 gtk_entry_set_text(GTK_ENTRY(
6914 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6916 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6917 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6918 last_header->type = pref_type;
6920 if (replyto_hdr == FALSE)
6921 g_hash_table_insert(compose->email_hashtable, email,
6922 GUINT_TO_POINTER(1));
6929 static void compose_destroy_headerentry(Compose *compose,
6930 ComposeHeaderEntry *headerentry)
6932 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6935 extract_address(text);
6936 email = g_utf8_strdown(text, -1);
6937 g_hash_table_remove(compose->email_hashtable, email);
6941 gtk_widget_destroy(headerentry->combo);
6942 gtk_widget_destroy(headerentry->entry);
6943 gtk_widget_destroy(headerentry->button);
6944 gtk_widget_destroy(headerentry->hbox);
6945 g_free(headerentry);
6948 static void compose_remove_header_entries(Compose *compose)
6951 for (list = compose->header_list; list; list = list->next)
6952 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6954 compose->header_last = NULL;
6955 g_slist_free(compose->header_list);
6956 compose->header_list = NULL;
6957 compose->header_nextrow = 1;
6958 compose_create_header_entry(compose);
6961 static GtkWidget *compose_create_header(Compose *compose)
6963 GtkWidget *from_optmenu_hbox;
6964 GtkWidget *header_scrolledwin_main;
6965 GtkWidget *header_table_main;
6966 GtkWidget *header_scrolledwin;
6967 GtkWidget *header_table;
6969 /* parent with account selection and from header */
6970 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6971 gtk_widget_show(header_scrolledwin_main);
6972 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6974 header_table_main = gtk_table_new(2, 2, FALSE);
6975 gtk_widget_show(header_table_main);
6976 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6977 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6978 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6980 from_optmenu_hbox = compose_account_option_menu_create(compose);
6981 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6982 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6984 /* child with header labels and entries */
6985 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6986 gtk_widget_show(header_scrolledwin);
6987 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6989 header_table = gtk_table_new(2, 2, FALSE);
6990 gtk_widget_show(header_table);
6991 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6992 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6993 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6995 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6996 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6998 compose->header_table = header_table;
6999 compose->header_list = NULL;
7000 compose->header_nextrow = 0;
7002 compose_create_header_entry(compose);
7004 compose->table = NULL;
7006 return header_scrolledwin_main;
7009 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7011 Compose *compose = (Compose *)data;
7012 GdkEventButton event;
7015 event.time = gtk_get_current_event_time();
7017 return attach_button_pressed(compose->attach_clist, &event, compose);
7020 static GtkWidget *compose_create_attach(Compose *compose)
7022 GtkWidget *attach_scrwin;
7023 GtkWidget *attach_clist;
7025 GtkListStore *store;
7026 GtkCellRenderer *renderer;
7027 GtkTreeViewColumn *column;
7028 GtkTreeSelection *selection;
7030 /* attachment list */
7031 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7032 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7033 GTK_POLICY_AUTOMATIC,
7034 GTK_POLICY_AUTOMATIC);
7035 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7037 store = gtk_list_store_new(N_ATTACH_COLS,
7043 G_TYPE_AUTO_POINTER,
7045 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7046 (GTK_TREE_MODEL(store)));
7047 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7048 g_object_unref(store);
7050 renderer = gtk_cell_renderer_text_new();
7051 column = gtk_tree_view_column_new_with_attributes
7052 (_("Mime type"), renderer, "text",
7053 COL_MIMETYPE, NULL);
7054 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7056 renderer = gtk_cell_renderer_text_new();
7057 column = gtk_tree_view_column_new_with_attributes
7058 (_("Size"), renderer, "text",
7060 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7062 renderer = gtk_cell_renderer_text_new();
7063 column = gtk_tree_view_column_new_with_attributes
7064 (_("Name"), renderer, "text",
7066 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7068 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7069 prefs_common.use_stripes_everywhere);
7070 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7071 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7073 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7074 G_CALLBACK(attach_selected), compose);
7075 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7076 G_CALLBACK(attach_button_pressed), compose);
7077 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7078 G_CALLBACK(popup_attach_button_pressed), compose);
7079 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7080 G_CALLBACK(attach_key_pressed), compose);
7083 gtk_drag_dest_set(attach_clist,
7084 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7085 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7086 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7087 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7088 G_CALLBACK(compose_attach_drag_received_cb),
7090 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7091 G_CALLBACK(compose_drag_drop),
7094 compose->attach_scrwin = attach_scrwin;
7095 compose->attach_clist = attach_clist;
7097 return attach_scrwin;
7100 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7101 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7103 static GtkWidget *compose_create_others(Compose *compose)
7106 GtkWidget *savemsg_checkbtn;
7107 GtkWidget *savemsg_combo;
7108 GtkWidget *savemsg_select;
7111 gchar *folderidentifier;
7113 /* Table for settings */
7114 table = gtk_table_new(3, 1, FALSE);
7115 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7116 gtk_widget_show(table);
7117 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7120 /* Save Message to folder */
7121 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7122 gtk_widget_show(savemsg_checkbtn);
7123 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7124 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7125 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7127 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7128 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7130 #if !GTK_CHECK_VERSION(2, 24, 0)
7131 savemsg_combo = gtk_combo_box_entry_new_text();
7133 savemsg_combo = gtk_combo_box_text_new_with_entry();
7135 compose->savemsg_checkbtn = savemsg_checkbtn;
7136 compose->savemsg_combo = savemsg_combo;
7137 gtk_widget_show(savemsg_combo);
7139 if (prefs_common.compose_save_to_history)
7140 #if !GTK_CHECK_VERSION(2, 24, 0)
7141 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7142 prefs_common.compose_save_to_history);
7144 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7145 prefs_common.compose_save_to_history);
7147 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7148 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7149 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7150 G_CALLBACK(compose_grab_focus_cb), compose);
7151 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7152 folderidentifier = folder_item_get_identifier(account_get_special_folder
7153 (compose->account, F_OUTBOX));
7154 compose_set_save_to(compose, folderidentifier);
7155 g_free(folderidentifier);
7158 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7159 gtk_widget_show(savemsg_select);
7160 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7161 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7162 G_CALLBACK(compose_savemsg_select_cb),
7168 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7170 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7171 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7174 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7179 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7182 path = folder_item_get_identifier(dest);
7184 compose_set_save_to(compose, path);
7188 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7189 GdkAtom clip, GtkTextIter *insert_place);
7192 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7196 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7198 if (event->button == 3) {
7200 GtkTextIter sel_start, sel_end;
7201 gboolean stuff_selected;
7203 /* move the cursor to allow GtkAspell to check the word
7204 * under the mouse */
7205 if (event->x && event->y) {
7206 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7207 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7209 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7212 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7213 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7216 stuff_selected = gtk_text_buffer_get_selection_bounds(
7218 &sel_start, &sel_end);
7220 gtk_text_buffer_place_cursor (buffer, &iter);
7221 /* reselect stuff */
7223 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7224 gtk_text_buffer_select_range(buffer,
7225 &sel_start, &sel_end);
7227 return FALSE; /* pass the event so that the right-click goes through */
7230 if (event->button == 2) {
7235 /* get the middle-click position to paste at the correct place */
7236 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7237 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7239 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7242 entry_paste_clipboard(compose, text,
7243 prefs_common.linewrap_pastes,
7244 GDK_SELECTION_PRIMARY, &iter);
7252 static void compose_spell_menu_changed(void *data)
7254 Compose *compose = (Compose *)data;
7256 GtkWidget *menuitem;
7257 GtkWidget *parent_item;
7258 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7261 if (compose->gtkaspell == NULL)
7264 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7265 "/Menu/Spelling/Options");
7267 /* setting the submenu removes /Spelling/Options from the factory
7268 * so we need to save it */
7270 if (parent_item == NULL) {
7271 parent_item = compose->aspell_options_menu;
7272 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7274 compose->aspell_options_menu = parent_item;
7276 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7278 spell_menu = g_slist_reverse(spell_menu);
7279 for (items = spell_menu;
7280 items; items = items->next) {
7281 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7282 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7283 gtk_widget_show(GTK_WIDGET(menuitem));
7285 g_slist_free(spell_menu);
7287 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7288 gtk_widget_show(parent_item);
7291 static void compose_dict_changed(void *data)
7293 Compose *compose = (Compose *) data;
7295 if(compose->gtkaspell &&
7296 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7299 gtkaspell_highlight_all(compose->gtkaspell);
7300 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7304 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7306 Compose *compose = (Compose *)data;
7307 GdkEventButton event;
7310 event.time = gtk_get_current_event_time();
7314 return text_clicked(compose->text, &event, compose);
7317 static gboolean compose_force_window_origin = TRUE;
7318 static Compose *compose_create(PrefsAccount *account,
7327 GtkWidget *handlebox;
7329 GtkWidget *notebook;
7331 GtkWidget *attach_hbox;
7332 GtkWidget *attach_lab1;
7333 GtkWidget *attach_lab2;
7338 GtkWidget *subject_hbox;
7339 GtkWidget *subject_frame;
7340 GtkWidget *subject_entry;
7344 GtkWidget *edit_vbox;
7345 GtkWidget *ruler_hbox;
7347 GtkWidget *scrolledwin;
7349 GtkTextBuffer *buffer;
7350 GtkClipboard *clipboard;
7352 UndoMain *undostruct;
7354 GtkWidget *popupmenu;
7355 GtkWidget *tmpl_menu;
7356 GtkActionGroup *action_group = NULL;
7359 GtkAspell * gtkaspell = NULL;
7362 static GdkGeometry geometry;
7364 cm_return_val_if_fail(account != NULL, NULL);
7366 debug_print("Creating compose window...\n");
7367 compose = g_new0(Compose, 1);
7369 compose->batch = batch;
7370 compose->account = account;
7371 compose->folder = folder;
7373 compose->mutex = cm_mutex_new();
7374 compose->set_cursor_pos = -1;
7376 #if !(GTK_CHECK_VERSION(2,12,0))
7377 compose->tooltips = tips;
7380 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7382 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7383 gtk_widget_set_size_request(window, prefs_common.compose_width,
7384 prefs_common.compose_height);
7386 if (!geometry.max_width) {
7387 geometry.max_width = gdk_screen_width();
7388 geometry.max_height = gdk_screen_height();
7391 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7392 &geometry, GDK_HINT_MAX_SIZE);
7393 if (!geometry.min_width) {
7394 geometry.min_width = 600;
7395 geometry.min_height = 440;
7397 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7398 &geometry, GDK_HINT_MIN_SIZE);
7400 #ifndef GENERIC_UMPC
7401 if (compose_force_window_origin)
7402 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7403 prefs_common.compose_y);
7405 g_signal_connect(G_OBJECT(window), "delete_event",
7406 G_CALLBACK(compose_delete_cb), compose);
7407 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7408 gtk_widget_realize(window);
7410 gtkut_widget_set_composer_icon(window);
7412 vbox = gtk_vbox_new(FALSE, 0);
7413 gtk_container_add(GTK_CONTAINER(window), vbox);
7415 compose->ui_manager = gtk_ui_manager_new();
7416 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7417 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7418 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7419 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7420 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7421 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7422 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7423 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7424 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7425 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7429 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7430 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7432 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7434 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7435 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7439 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7444 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7445 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7446 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7447 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7451 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7470 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7481 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7489 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7493 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7499 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7500 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7544 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)
7545 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)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7551 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)
7552 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)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7557 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)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7561 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)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7567 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)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7573 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)
7574 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)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7587 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)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7605 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7606 gtk_widget_show_all(menubar);
7608 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7609 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7611 if (prefs_common.toolbar_detachable) {
7612 handlebox = gtk_handle_box_new();
7614 handlebox = gtk_hbox_new(FALSE, 0);
7616 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7618 gtk_widget_realize(handlebox);
7619 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7622 vbox2 = gtk_vbox_new(FALSE, 2);
7623 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7624 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7627 notebook = gtk_notebook_new();
7628 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7629 gtk_widget_show(notebook);
7631 /* header labels and entries */
7632 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7633 compose_create_header(compose),
7634 gtk_label_new_with_mnemonic(_("Hea_der")));
7635 /* attachment list */
7636 attach_hbox = gtk_hbox_new(FALSE, 0);
7637 gtk_widget_show(attach_hbox);
7639 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7640 gtk_widget_show(attach_lab1);
7641 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7643 attach_lab2 = gtk_label_new("");
7644 gtk_widget_show(attach_lab2);
7645 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7647 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7648 compose_create_attach(compose),
7651 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7652 compose_create_others(compose),
7653 gtk_label_new_with_mnemonic(_("Othe_rs")));
7656 subject_hbox = gtk_hbox_new(FALSE, 0);
7657 gtk_widget_show(subject_hbox);
7659 subject_frame = gtk_frame_new(NULL);
7660 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7661 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7662 gtk_widget_show(subject_frame);
7664 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7665 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7666 gtk_widget_show(subject);
7668 label = gtk_label_new(_("Subject:"));
7669 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7670 gtk_widget_show(label);
7673 subject_entry = claws_spell_entry_new();
7675 subject_entry = gtk_entry_new();
7677 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7678 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7679 G_CALLBACK(compose_grab_focus_cb), compose);
7680 gtk_widget_show(subject_entry);
7681 compose->subject_entry = subject_entry;
7682 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7684 edit_vbox = gtk_vbox_new(FALSE, 0);
7686 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7689 ruler_hbox = gtk_hbox_new(FALSE, 0);
7690 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7692 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7693 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7694 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7698 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7699 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7700 GTK_POLICY_AUTOMATIC,
7701 GTK_POLICY_AUTOMATIC);
7702 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7704 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7706 text = gtk_text_view_new();
7707 if (prefs_common.show_compose_margin) {
7708 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7709 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7711 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7712 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7713 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7714 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7715 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7717 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7718 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7719 G_CALLBACK(compose_notebook_size_alloc), compose);
7720 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7721 G_CALLBACK(compose_edit_size_alloc),
7723 g_signal_connect(G_OBJECT(buffer), "changed",
7724 G_CALLBACK(compose_changed_cb), compose);
7725 g_signal_connect(G_OBJECT(text), "grab_focus",
7726 G_CALLBACK(compose_grab_focus_cb), compose);
7727 g_signal_connect(G_OBJECT(buffer), "insert_text",
7728 G_CALLBACK(text_inserted), compose);
7729 g_signal_connect(G_OBJECT(text), "button_press_event",
7730 G_CALLBACK(text_clicked), compose);
7731 g_signal_connect(G_OBJECT(text), "popup-menu",
7732 G_CALLBACK(compose_popup_menu), compose);
7733 g_signal_connect(G_OBJECT(subject_entry), "changed",
7734 G_CALLBACK(compose_changed_cb), compose);
7735 g_signal_connect(G_OBJECT(subject_entry), "activate",
7736 G_CALLBACK(compose_subject_entry_activated), compose);
7739 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7740 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7741 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7742 g_signal_connect(G_OBJECT(text), "drag_data_received",
7743 G_CALLBACK(compose_insert_drag_received_cb),
7745 g_signal_connect(G_OBJECT(text), "drag-drop",
7746 G_CALLBACK(compose_drag_drop),
7748 g_signal_connect(G_OBJECT(text), "key-press-event",
7749 G_CALLBACK(completion_set_focus_to_subject),
7751 gtk_widget_show_all(vbox);
7753 /* pane between attach clist and text */
7754 paned = gtk_vpaned_new();
7755 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7756 gtk_paned_add1(GTK_PANED(paned), notebook);
7757 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7758 gtk_widget_show_all(paned);
7761 if (prefs_common.textfont) {
7762 PangoFontDescription *font_desc;
7764 font_desc = pango_font_description_from_string
7765 (prefs_common.textfont);
7767 gtk_widget_modify_font(text, font_desc);
7768 pango_font_description_free(font_desc);
7772 gtk_action_group_add_actions(action_group, compose_popup_entries,
7773 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7775 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7776 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7777 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7778 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7779 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7781 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7783 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7784 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7785 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7787 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7789 undostruct = undo_init(text);
7790 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7793 address_completion_start(window);
7795 compose->window = window;
7796 compose->vbox = vbox;
7797 compose->menubar = menubar;
7798 compose->handlebox = handlebox;
7800 compose->vbox2 = vbox2;
7802 compose->paned = paned;
7804 compose->attach_label = attach_lab2;
7806 compose->notebook = notebook;
7807 compose->edit_vbox = edit_vbox;
7808 compose->ruler_hbox = ruler_hbox;
7809 compose->ruler = ruler;
7810 compose->scrolledwin = scrolledwin;
7811 compose->text = text;
7813 compose->focused_editable = NULL;
7815 compose->popupmenu = popupmenu;
7817 compose->tmpl_menu = tmpl_menu;
7819 compose->mode = mode;
7820 compose->rmode = mode;
7822 compose->targetinfo = NULL;
7823 compose->replyinfo = NULL;
7824 compose->fwdinfo = NULL;
7826 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7827 g_str_equal, (GDestroyNotify) g_free, NULL);
7829 compose->replyto = NULL;
7831 compose->bcc = NULL;
7832 compose->followup_to = NULL;
7834 compose->ml_post = NULL;
7836 compose->inreplyto = NULL;
7837 compose->references = NULL;
7838 compose->msgid = NULL;
7839 compose->boundary = NULL;
7841 compose->autowrap = prefs_common.autowrap;
7842 compose->autoindent = prefs_common.auto_indent;
7843 compose->use_signing = FALSE;
7844 compose->use_encryption = FALSE;
7845 compose->privacy_system = NULL;
7847 compose->modified = FALSE;
7849 compose->return_receipt = FALSE;
7851 compose->to_list = NULL;
7852 compose->newsgroup_list = NULL;
7854 compose->undostruct = undostruct;
7856 compose->sig_str = NULL;
7858 compose->exteditor_file = NULL;
7859 compose->exteditor_pid = -1;
7860 compose->exteditor_tag = -1;
7861 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7864 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7865 if (mode != COMPOSE_REDIRECT) {
7866 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7867 strcmp(prefs_common.dictionary, "")) {
7868 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7869 prefs_common.alt_dictionary,
7870 conv_get_locale_charset_str(),
7871 prefs_common.misspelled_col,
7872 prefs_common.check_while_typing,
7873 prefs_common.recheck_when_changing_dict,
7874 prefs_common.use_alternate,
7875 prefs_common.use_both_dicts,
7876 GTK_TEXT_VIEW(text),
7877 GTK_WINDOW(compose->window),
7878 compose_dict_changed,
7879 compose_spell_menu_changed,
7882 alertpanel_error(_("Spell checker could not "
7884 gtkaspell_checkers_strerror());
7885 gtkaspell_checkers_reset_error();
7887 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7891 compose->gtkaspell = gtkaspell;
7892 compose_spell_menu_changed(compose);
7893 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7896 compose_select_account(compose, account, TRUE);
7898 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7899 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7901 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7902 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7904 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7905 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7907 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7908 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7910 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7911 if (account->protocol != A_NNTP)
7912 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7913 prefs_common_translated_header_name("To:"));
7915 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7916 prefs_common_translated_header_name("Newsgroups:"));
7918 #ifndef USE_NEW_ADDRBOOK
7919 addressbook_set_target_compose(compose);
7921 if (mode != COMPOSE_REDIRECT)
7922 compose_set_template_menu(compose);
7924 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7927 compose_list = g_list_append(compose_list, compose);
7929 if (!prefs_common.show_ruler)
7930 gtk_widget_hide(ruler_hbox);
7932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7935 compose->priority = PRIORITY_NORMAL;
7936 compose_update_priority_menu_item(compose);
7938 compose_set_out_encoding(compose);
7941 compose_update_actions_menu(compose);
7943 /* Privacy Systems menu */
7944 compose_update_privacy_systems_menu(compose);
7946 activate_privacy_system(compose, account, TRUE);
7947 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7949 gtk_widget_realize(window);
7951 gtk_widget_show(window);
7957 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7962 GtkWidget *optmenubox;
7965 GtkWidget *from_name = NULL;
7966 #if !(GTK_CHECK_VERSION(2,12,0))
7967 GtkTooltips *tips = compose->tooltips;
7970 gint num = 0, def_menu = 0;
7972 accounts = account_get_list();
7973 cm_return_val_if_fail(accounts != NULL, NULL);
7975 optmenubox = gtk_event_box_new();
7976 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7977 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7979 hbox = gtk_hbox_new(FALSE, 6);
7980 from_name = gtk_entry_new();
7982 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7983 G_CALLBACK(compose_grab_focus_cb), compose);
7985 for (; accounts != NULL; accounts = accounts->next, num++) {
7986 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7987 gchar *name, *from = NULL;
7989 if (ac == compose->account) def_menu = num;
7991 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7994 if (ac == compose->account) {
7995 if (ac->name && *ac->name) {
7997 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7998 from = g_strdup_printf("%s <%s>",
8000 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8002 from = g_strdup_printf("%s",
8004 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8007 COMBOBOX_ADD(menu, name, ac->account_id);
8012 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8014 g_signal_connect(G_OBJECT(optmenu), "changed",
8015 G_CALLBACK(account_activated),
8017 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8018 G_CALLBACK(compose_entry_popup_extend),
8021 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8022 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8024 CLAWS_SET_TIP(optmenubox,
8025 _("Account to use for this email"));
8026 CLAWS_SET_TIP(from_name,
8027 _("Sender address to be used"));
8029 compose->account_combo = optmenu;
8030 compose->from_name = from_name;
8035 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8037 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8038 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8039 Compose *compose = (Compose *) data;
8041 compose->priority = value;
8045 static void compose_reply_change_mode(Compose *compose,
8048 gboolean was_modified = compose->modified;
8050 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8052 cm_return_if_fail(compose->replyinfo != NULL);
8054 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8056 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8058 if (action == COMPOSE_REPLY_TO_ALL)
8060 if (action == COMPOSE_REPLY_TO_SENDER)
8062 if (action == COMPOSE_REPLY_TO_LIST)
8065 compose_remove_header_entries(compose);
8066 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8067 if (compose->account->set_autocc && compose->account->auto_cc)
8068 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8070 if (compose->account->set_autobcc && compose->account->auto_bcc)
8071 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8073 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8074 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8075 compose_show_first_last_header(compose, TRUE);
8076 compose->modified = was_modified;
8077 compose_set_title(compose);
8080 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8082 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8083 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8084 Compose *compose = (Compose *) data;
8087 compose_reply_change_mode(compose, value);
8090 static void compose_update_priority_menu_item(Compose * compose)
8092 GtkWidget *menuitem = NULL;
8093 switch (compose->priority) {
8094 case PRIORITY_HIGHEST:
8095 menuitem = gtk_ui_manager_get_widget
8096 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8099 menuitem = gtk_ui_manager_get_widget
8100 (compose->ui_manager, "/Menu/Options/Priority/High");
8102 case PRIORITY_NORMAL:
8103 menuitem = gtk_ui_manager_get_widget
8104 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8107 menuitem = gtk_ui_manager_get_widget
8108 (compose->ui_manager, "/Menu/Options/Priority/Low");
8110 case PRIORITY_LOWEST:
8111 menuitem = gtk_ui_manager_get_widget
8112 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8115 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8118 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8120 Compose *compose = (Compose *) data;
8122 gboolean can_sign = FALSE, can_encrypt = FALSE;
8124 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8126 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8129 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8130 g_free(compose->privacy_system);
8131 compose->privacy_system = NULL;
8132 if (systemid != NULL) {
8133 compose->privacy_system = g_strdup(systemid);
8135 can_sign = privacy_system_can_sign(systemid);
8136 can_encrypt = privacy_system_can_encrypt(systemid);
8139 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8141 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8142 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8145 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8147 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8148 GtkWidget *menuitem = NULL;
8149 GList *children, *amenu;
8150 gboolean can_sign = FALSE, can_encrypt = FALSE;
8151 gboolean found = FALSE;
8153 if (compose->privacy_system != NULL) {
8155 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8156 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8157 cm_return_if_fail(menuitem != NULL);
8159 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8162 while (amenu != NULL) {
8163 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8164 if (systemid != NULL) {
8165 if (strcmp(systemid, compose->privacy_system) == 0 &&
8166 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8167 menuitem = GTK_WIDGET(amenu->data);
8169 can_sign = privacy_system_can_sign(systemid);
8170 can_encrypt = privacy_system_can_encrypt(systemid);
8174 } else if (strlen(compose->privacy_system) == 0 &&
8175 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8176 menuitem = GTK_WIDGET(amenu->data);
8179 can_encrypt = FALSE;
8184 amenu = amenu->next;
8186 g_list_free(children);
8187 if (menuitem != NULL)
8188 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8190 if (warn && !found && strlen(compose->privacy_system)) {
8191 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8192 "will not be able to sign or encrypt this message."),
8193 compose->privacy_system);
8197 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8198 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8201 static void compose_set_out_encoding(Compose *compose)
8203 CharSet out_encoding;
8204 const gchar *branch = NULL;
8205 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8207 switch(out_encoding) {
8208 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8209 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8210 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8211 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8212 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8213 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8214 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8215 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8216 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8217 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8218 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8219 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8220 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8221 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8222 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8223 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8224 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8225 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8226 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8227 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8228 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8229 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8230 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8231 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8232 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8233 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8234 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8235 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8236 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8237 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8238 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8239 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8240 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8242 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8245 static void compose_set_template_menu(Compose *compose)
8247 GSList *tmpl_list, *cur;
8251 tmpl_list = template_get_config();
8253 menu = gtk_menu_new();
8255 gtk_menu_set_accel_group (GTK_MENU (menu),
8256 gtk_ui_manager_get_accel_group(compose->ui_manager));
8257 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8258 Template *tmpl = (Template *)cur->data;
8259 gchar *accel_path = NULL;
8260 item = gtk_menu_item_new_with_label(tmpl->name);
8261 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8262 g_signal_connect(G_OBJECT(item), "activate",
8263 G_CALLBACK(compose_template_activate_cb),
8265 g_object_set_data(G_OBJECT(item), "template", tmpl);
8266 gtk_widget_show(item);
8267 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8268 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8272 gtk_widget_show(menu);
8273 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8276 void compose_update_actions_menu(Compose *compose)
8278 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8281 static void compose_update_privacy_systems_menu(Compose *compose)
8283 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8284 GSList *systems, *cur;
8286 GtkWidget *system_none;
8288 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8289 GtkWidget *privacy_menu = gtk_menu_new();
8291 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8292 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8294 g_signal_connect(G_OBJECT(system_none), "activate",
8295 G_CALLBACK(compose_set_privacy_system_cb), compose);
8297 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8298 gtk_widget_show(system_none);
8300 systems = privacy_get_system_ids();
8301 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8302 gchar *systemid = cur->data;
8304 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8305 widget = gtk_radio_menu_item_new_with_label(group,
8306 privacy_system_get_name(systemid));
8307 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8308 g_strdup(systemid), g_free);
8309 g_signal_connect(G_OBJECT(widget), "activate",
8310 G_CALLBACK(compose_set_privacy_system_cb), compose);
8312 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8313 gtk_widget_show(widget);
8316 g_slist_free(systems);
8317 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8318 gtk_widget_show_all(privacy_menu);
8319 gtk_widget_show_all(privacy_menuitem);
8322 void compose_reflect_prefs_all(void)
8327 for (cur = compose_list; cur != NULL; cur = cur->next) {
8328 compose = (Compose *)cur->data;
8329 compose_set_template_menu(compose);
8333 void compose_reflect_prefs_pixmap_theme(void)
8338 for (cur = compose_list; cur != NULL; cur = cur->next) {
8339 compose = (Compose *)cur->data;
8340 toolbar_update(TOOLBAR_COMPOSE, compose);
8344 static const gchar *compose_quote_char_from_context(Compose *compose)
8346 const gchar *qmark = NULL;
8348 cm_return_val_if_fail(compose != NULL, NULL);
8350 switch (compose->mode) {
8351 /* use forward-specific quote char */
8352 case COMPOSE_FORWARD:
8353 case COMPOSE_FORWARD_AS_ATTACH:
8354 case COMPOSE_FORWARD_INLINE:
8355 if (compose->folder && compose->folder->prefs &&
8356 compose->folder->prefs->forward_with_format)
8357 qmark = compose->folder->prefs->forward_quotemark;
8358 else if (compose->account->forward_with_format)
8359 qmark = compose->account->forward_quotemark;
8361 qmark = prefs_common.fw_quotemark;
8364 /* use reply-specific quote char in all other modes */
8366 if (compose->folder && compose->folder->prefs &&
8367 compose->folder->prefs->reply_with_format)
8368 qmark = compose->folder->prefs->reply_quotemark;
8369 else if (compose->account->reply_with_format)
8370 qmark = compose->account->reply_quotemark;
8372 qmark = prefs_common.quotemark;
8376 if (qmark == NULL || *qmark == '\0')
8382 static void compose_template_apply(Compose *compose, Template *tmpl,
8386 GtkTextBuffer *buffer;
8390 gchar *parsed_str = NULL;
8391 gint cursor_pos = 0;
8392 const gchar *err_msg = _("The body of the template has an error at line %d.");
8395 /* process the body */
8397 text = GTK_TEXT_VIEW(compose->text);
8398 buffer = gtk_text_view_get_buffer(text);
8401 qmark = compose_quote_char_from_context(compose);
8403 if (compose->replyinfo != NULL) {
8406 gtk_text_buffer_set_text(buffer, "", -1);
8407 mark = gtk_text_buffer_get_insert(buffer);
8408 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8410 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8411 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8413 } else if (compose->fwdinfo != NULL) {
8416 gtk_text_buffer_set_text(buffer, "", -1);
8417 mark = gtk_text_buffer_get_insert(buffer);
8418 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8420 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8421 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8424 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8426 GtkTextIter start, end;
8429 gtk_text_buffer_get_start_iter(buffer, &start);
8430 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8431 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8433 /* clear the buffer now */
8435 gtk_text_buffer_set_text(buffer, "", -1);
8437 parsed_str = compose_quote_fmt(compose, dummyinfo,
8438 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8439 procmsg_msginfo_free( dummyinfo );
8445 gtk_text_buffer_set_text(buffer, "", -1);
8446 mark = gtk_text_buffer_get_insert(buffer);
8447 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8450 if (replace && parsed_str && compose->account->auto_sig)
8451 compose_insert_sig(compose, FALSE);
8453 if (replace && parsed_str) {
8454 gtk_text_buffer_get_start_iter(buffer, &iter);
8455 gtk_text_buffer_place_cursor(buffer, &iter);
8459 cursor_pos = quote_fmt_get_cursor_pos();
8460 compose->set_cursor_pos = cursor_pos;
8461 if (cursor_pos == -1)
8463 gtk_text_buffer_get_start_iter(buffer, &iter);
8464 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8465 gtk_text_buffer_place_cursor(buffer, &iter);
8468 /* process the other fields */
8470 compose_template_apply_fields(compose, tmpl);
8471 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8472 quote_fmt_reset_vartable();
8473 compose_changed_cb(NULL, compose);
8476 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8477 gtkaspell_highlight_all(compose->gtkaspell);
8481 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8483 MsgInfo* dummyinfo = NULL;
8484 MsgInfo *msginfo = NULL;
8487 if (compose->replyinfo != NULL)
8488 msginfo = compose->replyinfo;
8489 else if (compose->fwdinfo != NULL)
8490 msginfo = compose->fwdinfo;
8492 dummyinfo = compose_msginfo_new_from_compose(compose);
8493 msginfo = dummyinfo;
8496 if (tmpl->from && *tmpl->from != '\0') {
8498 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8499 compose->gtkaspell);
8501 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8503 quote_fmt_scan_string(tmpl->from);
8506 buf = quote_fmt_get_buffer();
8508 alertpanel_error(_("Template From format error."));
8510 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8514 if (tmpl->to && *tmpl->to != '\0') {
8516 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8517 compose->gtkaspell);
8519 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8521 quote_fmt_scan_string(tmpl->to);
8524 buf = quote_fmt_get_buffer();
8526 alertpanel_error(_("Template To format error."));
8528 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8532 if (tmpl->cc && *tmpl->cc != '\0') {
8534 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8535 compose->gtkaspell);
8537 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8539 quote_fmt_scan_string(tmpl->cc);
8542 buf = quote_fmt_get_buffer();
8544 alertpanel_error(_("Template Cc format error."));
8546 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8550 if (tmpl->bcc && *tmpl->bcc != '\0') {
8552 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8553 compose->gtkaspell);
8555 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8557 quote_fmt_scan_string(tmpl->bcc);
8560 buf = quote_fmt_get_buffer();
8562 alertpanel_error(_("Template Bcc format error."));
8564 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8568 /* process the subject */
8569 if (tmpl->subject && *tmpl->subject != '\0') {
8571 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8572 compose->gtkaspell);
8574 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8576 quote_fmt_scan_string(tmpl->subject);
8579 buf = quote_fmt_get_buffer();
8581 alertpanel_error(_("Template subject format error."));
8583 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8587 procmsg_msginfo_free( dummyinfo );
8590 static void compose_destroy(Compose *compose)
8592 GtkAllocation allocation;
8593 GtkTextBuffer *buffer;
8594 GtkClipboard *clipboard;
8596 compose_list = g_list_remove(compose_list, compose);
8598 if (compose->updating) {
8599 debug_print("danger, not destroying anything now\n");
8600 compose->deferred_destroy = TRUE;
8603 /* NOTE: address_completion_end() does nothing with the window
8604 * however this may change. */
8605 address_completion_end(compose->window);
8607 slist_free_strings_full(compose->to_list);
8608 slist_free_strings_full(compose->newsgroup_list);
8609 slist_free_strings_full(compose->header_list);
8611 slist_free_strings_full(extra_headers);
8612 extra_headers = NULL;
8614 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8616 g_hash_table_destroy(compose->email_hashtable);
8618 procmsg_msginfo_free(compose->targetinfo);
8619 procmsg_msginfo_free(compose->replyinfo);
8620 procmsg_msginfo_free(compose->fwdinfo);
8622 g_free(compose->replyto);
8623 g_free(compose->cc);
8624 g_free(compose->bcc);
8625 g_free(compose->newsgroups);
8626 g_free(compose->followup_to);
8628 g_free(compose->ml_post);
8630 g_free(compose->inreplyto);
8631 g_free(compose->references);
8632 g_free(compose->msgid);
8633 g_free(compose->boundary);
8635 g_free(compose->redirect_filename);
8636 if (compose->undostruct)
8637 undo_destroy(compose->undostruct);
8639 g_free(compose->sig_str);
8641 g_free(compose->exteditor_file);
8643 g_free(compose->orig_charset);
8645 g_free(compose->privacy_system);
8647 #ifndef USE_NEW_ADDRBOOK
8648 if (addressbook_get_target_compose() == compose)
8649 addressbook_set_target_compose(NULL);
8652 if (compose->gtkaspell) {
8653 gtkaspell_delete(compose->gtkaspell);
8654 compose->gtkaspell = NULL;
8658 if (!compose->batch) {
8659 gtk_widget_get_allocation(compose->window, &allocation);
8660 prefs_common.compose_width = allocation.width;
8661 prefs_common.compose_height = allocation.height;
8664 if (!gtk_widget_get_parent(compose->paned))
8665 gtk_widget_destroy(compose->paned);
8666 gtk_widget_destroy(compose->popupmenu);
8668 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8669 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8670 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8672 gtk_widget_destroy(compose->window);
8673 toolbar_destroy(compose->toolbar);
8674 g_free(compose->toolbar);
8675 cm_mutex_free(compose->mutex);
8679 static void compose_attach_info_free(AttachInfo *ainfo)
8681 g_free(ainfo->file);
8682 g_free(ainfo->content_type);
8683 g_free(ainfo->name);
8684 g_free(ainfo->charset);
8688 static void compose_attach_update_label(Compose *compose)
8693 GtkTreeModel *model;
8698 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8699 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8700 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8704 while(gtk_tree_model_iter_next(model, &iter))
8707 text = g_strdup_printf("(%d)", i);
8708 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8712 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8714 Compose *compose = (Compose *)data;
8715 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8716 GtkTreeSelection *selection;
8718 GtkTreeModel *model;
8720 selection = gtk_tree_view_get_selection(tree_view);
8721 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8726 for (cur = sel; cur != NULL; cur = cur->next) {
8727 GtkTreePath *path = cur->data;
8728 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8731 gtk_tree_path_free(path);
8734 for (cur = sel; cur != NULL; cur = cur->next) {
8735 GtkTreeRowReference *ref = cur->data;
8736 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8739 if (gtk_tree_model_get_iter(model, &iter, path))
8740 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8742 gtk_tree_path_free(path);
8743 gtk_tree_row_reference_free(ref);
8747 compose_attach_update_label(compose);
8750 static struct _AttachProperty
8753 GtkWidget *mimetype_entry;
8754 GtkWidget *encoding_optmenu;
8755 GtkWidget *path_entry;
8756 GtkWidget *filename_entry;
8758 GtkWidget *cancel_btn;
8761 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8763 gtk_tree_path_free((GtkTreePath *)ptr);
8766 static void compose_attach_property(GtkAction *action, gpointer data)
8768 Compose *compose = (Compose *)data;
8769 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8771 GtkComboBox *optmenu;
8772 GtkTreeSelection *selection;
8774 GtkTreeModel *model;
8777 static gboolean cancelled;
8779 /* only if one selected */
8780 selection = gtk_tree_view_get_selection(tree_view);
8781 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8784 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8788 path = (GtkTreePath *) sel->data;
8789 gtk_tree_model_get_iter(model, &iter, path);
8790 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8793 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8799 if (!attach_prop.window)
8800 compose_attach_property_create(&cancelled);
8801 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8802 gtk_widget_grab_focus(attach_prop.ok_btn);
8803 gtk_widget_show(attach_prop.window);
8804 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8805 GTK_WINDOW(compose->window));
8807 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8808 if (ainfo->encoding == ENC_UNKNOWN)
8809 combobox_select_by_data(optmenu, ENC_BASE64);
8811 combobox_select_by_data(optmenu, ainfo->encoding);
8813 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8814 ainfo->content_type ? ainfo->content_type : "");
8815 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8816 ainfo->file ? ainfo->file : "");
8817 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8818 ainfo->name ? ainfo->name : "");
8821 const gchar *entry_text;
8823 gchar *cnttype = NULL;
8830 gtk_widget_hide(attach_prop.window);
8831 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8836 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8837 if (*entry_text != '\0') {
8840 text = g_strstrip(g_strdup(entry_text));
8841 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8842 cnttype = g_strdup(text);
8845 alertpanel_error(_("Invalid MIME type."));
8851 ainfo->encoding = combobox_get_active_data(optmenu);
8853 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8854 if (*entry_text != '\0') {
8855 if (is_file_exist(entry_text) &&
8856 (size = get_file_size(entry_text)) > 0)
8857 file = g_strdup(entry_text);
8860 (_("File doesn't exist or is empty."));
8866 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8867 if (*entry_text != '\0') {
8868 g_free(ainfo->name);
8869 ainfo->name = g_strdup(entry_text);
8873 g_free(ainfo->content_type);
8874 ainfo->content_type = cnttype;
8877 g_free(ainfo->file);
8881 ainfo->size = (goffset)size;
8883 /* update tree store */
8884 text = to_human_readable(ainfo->size);
8885 gtk_tree_model_get_iter(model, &iter, path);
8886 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8887 COL_MIMETYPE, ainfo->content_type,
8889 COL_NAME, ainfo->name,
8890 COL_CHARSET, ainfo->charset,
8896 gtk_tree_path_free(path);
8899 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8901 label = gtk_label_new(str); \
8902 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8903 GTK_FILL, 0, 0, 0); \
8904 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8906 entry = gtk_entry_new(); \
8907 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8908 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8911 static void compose_attach_property_create(gboolean *cancelled)
8917 GtkWidget *mimetype_entry;
8920 GtkListStore *optmenu_menu;
8921 GtkWidget *path_entry;
8922 GtkWidget *filename_entry;
8925 GtkWidget *cancel_btn;
8926 GList *mime_type_list, *strlist;
8929 debug_print("Creating attach_property window...\n");
8931 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8932 gtk_widget_set_size_request(window, 480, -1);
8933 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8934 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8935 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8936 g_signal_connect(G_OBJECT(window), "delete_event",
8937 G_CALLBACK(attach_property_delete_event),
8939 g_signal_connect(G_OBJECT(window), "key_press_event",
8940 G_CALLBACK(attach_property_key_pressed),
8943 vbox = gtk_vbox_new(FALSE, 8);
8944 gtk_container_add(GTK_CONTAINER(window), vbox);
8946 table = gtk_table_new(4, 2, FALSE);
8947 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8948 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8949 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8951 label = gtk_label_new(_("MIME type"));
8952 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8954 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8955 #if !GTK_CHECK_VERSION(2, 24, 0)
8956 mimetype_entry = gtk_combo_box_entry_new_text();
8958 mimetype_entry = gtk_combo_box_text_new_with_entry();
8960 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8961 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8963 /* stuff with list */
8964 mime_type_list = procmime_get_mime_type_list();
8966 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8967 MimeType *type = (MimeType *) mime_type_list->data;
8970 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8972 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8975 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8976 (GCompareFunc)strcmp2);
8979 for (mime_type_list = strlist; mime_type_list != NULL;
8980 mime_type_list = mime_type_list->next) {
8981 #if !GTK_CHECK_VERSION(2, 24, 0)
8982 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8984 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8986 g_free(mime_type_list->data);
8988 g_list_free(strlist);
8989 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8990 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8992 label = gtk_label_new(_("Encoding"));
8993 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8995 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8997 hbox = gtk_hbox_new(FALSE, 0);
8998 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8999 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9001 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9002 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9004 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9005 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9006 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9007 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9008 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9010 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9012 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9013 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9015 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9016 &ok_btn, GTK_STOCK_OK,
9018 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9019 gtk_widget_grab_default(ok_btn);
9021 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9022 G_CALLBACK(attach_property_ok),
9024 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9025 G_CALLBACK(attach_property_cancel),
9028 gtk_widget_show_all(vbox);
9030 attach_prop.window = window;
9031 attach_prop.mimetype_entry = mimetype_entry;
9032 attach_prop.encoding_optmenu = optmenu;
9033 attach_prop.path_entry = path_entry;
9034 attach_prop.filename_entry = filename_entry;
9035 attach_prop.ok_btn = ok_btn;
9036 attach_prop.cancel_btn = cancel_btn;
9039 #undef SET_LABEL_AND_ENTRY
9041 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9047 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9053 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9054 gboolean *cancelled)
9062 static gboolean attach_property_key_pressed(GtkWidget *widget,
9064 gboolean *cancelled)
9066 if (event && event->keyval == GDK_KEY_Escape) {
9070 if (event && event->keyval == GDK_KEY_Return) {
9078 static void compose_exec_ext_editor(Compose *compose)
9085 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9086 G_DIR_SEPARATOR, compose);
9088 if (pipe(pipe_fds) < 0) {
9094 if ((pid = fork()) < 0) {
9101 /* close the write side of the pipe */
9104 compose->exteditor_file = g_strdup(tmp);
9105 compose->exteditor_pid = pid;
9107 compose_set_ext_editor_sensitive(compose, FALSE);
9110 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9112 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9114 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9118 } else { /* process-monitoring process */
9124 /* close the read side of the pipe */
9127 if (compose_write_body_to_file(compose, tmp) < 0) {
9128 fd_write_all(pipe_fds[1], "2\n", 2);
9132 pid_ed = compose_exec_ext_editor_real(tmp);
9134 fd_write_all(pipe_fds[1], "1\n", 2);
9138 /* wait until editor is terminated */
9139 waitpid(pid_ed, NULL, 0);
9141 fd_write_all(pipe_fds[1], "0\n", 2);
9148 #endif /* G_OS_UNIX */
9152 static gint compose_exec_ext_editor_real(const gchar *file)
9159 cm_return_val_if_fail(file != NULL, -1);
9161 if ((pid = fork()) < 0) {
9166 if (pid != 0) return pid;
9168 /* grandchild process */
9170 if (setpgid(0, getppid()))
9173 if (prefs_common_get_ext_editor_cmd() &&
9174 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9175 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9176 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9178 if (prefs_common_get_ext_editor_cmd())
9179 g_warning("External editor command-line is invalid: '%s'\n",
9180 prefs_common_get_ext_editor_cmd());
9181 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9184 cmdline = strsplit_with_quote(buf, " ", 1024);
9185 execvp(cmdline[0], cmdline);
9188 g_strfreev(cmdline);
9193 static gboolean compose_ext_editor_kill(Compose *compose)
9195 pid_t pgid = compose->exteditor_pid * -1;
9198 ret = kill(pgid, 0);
9200 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9204 msg = g_strdup_printf
9205 (_("The external editor is still working.\n"
9206 "Force terminating the process?\n"
9207 "process group id: %d"), -pgid);
9208 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9209 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9213 if (val == G_ALERTALTERNATE) {
9214 g_source_remove(compose->exteditor_tag);
9215 g_io_channel_shutdown(compose->exteditor_ch,
9217 g_io_channel_unref(compose->exteditor_ch);
9219 if (kill(pgid, SIGTERM) < 0) perror("kill");
9220 waitpid(compose->exteditor_pid, NULL, 0);
9222 g_warning("Terminated process group id: %d", -pgid);
9223 g_warning("Temporary file: %s",
9224 compose->exteditor_file);
9226 compose_set_ext_editor_sensitive(compose, TRUE);
9228 g_free(compose->exteditor_file);
9229 compose->exteditor_file = NULL;
9230 compose->exteditor_pid = -1;
9231 compose->exteditor_ch = NULL;
9232 compose->exteditor_tag = -1;
9240 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9244 Compose *compose = (Compose *)data;
9247 debug_print("Compose: input from monitoring process\n");
9249 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9251 g_io_channel_shutdown(source, FALSE, NULL);
9252 g_io_channel_unref(source);
9254 waitpid(compose->exteditor_pid, NULL, 0);
9256 if (buf[0] == '0') { /* success */
9257 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9258 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9260 gtk_text_buffer_set_text(buffer, "", -1);
9261 compose_insert_file(compose, compose->exteditor_file);
9262 compose_changed_cb(NULL, compose);
9263 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9265 if (claws_unlink(compose->exteditor_file) < 0)
9266 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9267 } else if (buf[0] == '1') { /* failed */
9268 g_warning("Couldn't exec external editor\n");
9269 if (claws_unlink(compose->exteditor_file) < 0)
9270 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9271 } else if (buf[0] == '2') {
9272 g_warning("Couldn't write to file\n");
9273 } else if (buf[0] == '3') {
9274 g_warning("Pipe read failed\n");
9277 compose_set_ext_editor_sensitive(compose, TRUE);
9279 g_free(compose->exteditor_file);
9280 compose->exteditor_file = NULL;
9281 compose->exteditor_pid = -1;
9282 compose->exteditor_ch = NULL;
9283 compose->exteditor_tag = -1;
9288 static void compose_set_ext_editor_sensitive(Compose *compose,
9291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9292 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9293 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9294 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9295 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9296 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9297 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9298 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9300 gtk_widget_set_sensitive(compose->text, sensitive);
9301 if (compose->toolbar->send_btn)
9302 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9303 if (compose->toolbar->sendl_btn)
9304 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9305 if (compose->toolbar->draft_btn)
9306 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9307 if (compose->toolbar->insert_btn)
9308 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9309 if (compose->toolbar->sig_btn)
9310 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9311 if (compose->toolbar->exteditor_btn)
9312 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9313 if (compose->toolbar->linewrap_current_btn)
9314 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9315 if (compose->toolbar->linewrap_all_btn)
9316 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9318 #endif /* G_OS_UNIX */
9321 * compose_undo_state_changed:
9323 * Change the sensivity of the menuentries undo and redo
9325 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9326 gint redo_state, gpointer data)
9328 Compose *compose = (Compose *)data;
9330 switch (undo_state) {
9331 case UNDO_STATE_TRUE:
9332 if (!undostruct->undo_state) {
9333 undostruct->undo_state = TRUE;
9334 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9337 case UNDO_STATE_FALSE:
9338 if (undostruct->undo_state) {
9339 undostruct->undo_state = FALSE;
9340 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9343 case UNDO_STATE_UNCHANGED:
9345 case UNDO_STATE_REFRESH:
9346 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9349 g_warning("Undo state not recognized");
9353 switch (redo_state) {
9354 case UNDO_STATE_TRUE:
9355 if (!undostruct->redo_state) {
9356 undostruct->redo_state = TRUE;
9357 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9360 case UNDO_STATE_FALSE:
9361 if (undostruct->redo_state) {
9362 undostruct->redo_state = FALSE;
9363 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9366 case UNDO_STATE_UNCHANGED:
9368 case UNDO_STATE_REFRESH:
9369 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9372 g_warning("Redo state not recognized");
9377 /* callback functions */
9379 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9380 GtkAllocation *allocation,
9383 prefs_common.compose_notebook_height = allocation->height;
9386 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9387 * includes "non-client" (windows-izm) in calculation, so this calculation
9388 * may not be accurate.
9390 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9391 GtkAllocation *allocation,
9392 GtkSHRuler *shruler)
9394 if (prefs_common.show_ruler) {
9395 gint char_width = 0, char_height = 0;
9396 gint line_width_in_chars;
9398 gtkut_get_font_size(GTK_WIDGET(widget),
9399 &char_width, &char_height);
9400 line_width_in_chars =
9401 (allocation->width - allocation->x) / char_width;
9403 /* got the maximum */
9404 gtk_shruler_set_range(GTK_SHRULER(shruler),
9405 0.0, line_width_in_chars, 0);
9414 ComposePrefType type;
9415 gboolean entry_marked;
9418 static void account_activated(GtkComboBox *optmenu, gpointer data)
9420 Compose *compose = (Compose *)data;
9423 gchar *folderidentifier;
9424 gint account_id = 0;
9427 GSList *list, *saved_list = NULL;
9428 HeaderEntryState *state;
9429 GtkRcStyle *style = NULL;
9430 #if !GTK_CHECK_VERSION(3, 0, 0)
9431 static GdkColor yellow;
9432 static gboolean color_set = FALSE;
9434 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9437 /* Get ID of active account in the combo box */
9438 menu = gtk_combo_box_get_model(optmenu);
9439 gtk_combo_box_get_active_iter(optmenu, &iter);
9440 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9442 ac = account_find_from_id(account_id);
9443 cm_return_if_fail(ac != NULL);
9445 if (ac != compose->account) {
9446 compose_select_account(compose, ac, FALSE);
9448 for (list = compose->header_list; list; list = list->next) {
9449 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9451 if (hentry->type == PREF_ACCOUNT || !list->next) {
9452 compose_destroy_headerentry(compose, hentry);
9456 state = g_malloc0(sizeof(HeaderEntryState));
9457 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9458 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9459 state->entry = gtk_editable_get_chars(
9460 GTK_EDITABLE(hentry->entry), 0, -1);
9461 state->type = hentry->type;
9463 #if !GTK_CHECK_VERSION(3, 0, 0)
9465 gdk_color_parse("#f5f6be", &yellow);
9466 color_set = gdk_colormap_alloc_color(
9467 gdk_colormap_get_system(),
9468 &yellow, FALSE, TRUE);
9472 style = gtk_widget_get_modifier_style(hentry->entry);
9473 state->entry_marked = gdk_color_equal(&yellow,
9474 &style->base[GTK_STATE_NORMAL]);
9476 saved_list = g_slist_append(saved_list, state);
9477 compose_destroy_headerentry(compose, hentry);
9480 compose->header_last = NULL;
9481 g_slist_free(compose->header_list);
9482 compose->header_list = NULL;
9483 compose->header_nextrow = 1;
9484 compose_create_header_entry(compose);
9486 if (ac->set_autocc && ac->auto_cc)
9487 compose_entry_append(compose, ac->auto_cc,
9488 COMPOSE_CC, PREF_ACCOUNT);
9490 if (ac->set_autobcc && ac->auto_bcc)
9491 compose_entry_append(compose, ac->auto_bcc,
9492 COMPOSE_BCC, PREF_ACCOUNT);
9494 if (ac->set_autoreplyto && ac->auto_replyto)
9495 compose_entry_append(compose, ac->auto_replyto,
9496 COMPOSE_REPLYTO, PREF_ACCOUNT);
9498 for (list = saved_list; list; list = list->next) {
9499 state = (HeaderEntryState *) list->data;
9501 compose_add_header_entry(compose, state->header,
9502 state->entry, state->type);
9503 if (state->entry_marked)
9504 compose_entry_mark_default_to(compose, state->entry);
9506 g_free(state->header);
9507 g_free(state->entry);
9510 g_slist_free(saved_list);
9512 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9513 (ac->protocol == A_NNTP) ?
9514 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9517 /* Set message save folder */
9518 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9519 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9521 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9522 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9524 compose_set_save_to(compose, NULL);
9525 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9526 folderidentifier = folder_item_get_identifier(account_get_special_folder
9527 (compose->account, F_OUTBOX));
9528 compose_set_save_to(compose, folderidentifier);
9529 g_free(folderidentifier);
9533 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9534 GtkTreeViewColumn *column, Compose *compose)
9536 compose_attach_property(NULL, compose);
9539 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9542 Compose *compose = (Compose *)data;
9543 GtkTreeSelection *attach_selection;
9544 gint attach_nr_selected;
9546 if (!event) return FALSE;
9548 if (event->button == 3) {
9549 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9550 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9552 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9553 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9555 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9556 NULL, NULL, event->button, event->time);
9563 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9566 Compose *compose = (Compose *)data;
9568 if (!event) return FALSE;
9570 switch (event->keyval) {
9571 case GDK_KEY_Delete:
9572 compose_attach_remove_selected(NULL, compose);
9578 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9580 toolbar_comp_set_sensitive(compose, allow);
9581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9584 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9586 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9587 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9590 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9594 static void compose_send_cb(GtkAction *action, gpointer data)
9596 Compose *compose = (Compose *)data;
9598 if (prefs_common.work_offline &&
9599 !inc_offline_should_override(TRUE,
9600 _("Claws Mail needs network access in order "
9601 "to send this email.")))
9604 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9605 g_source_remove(compose->draft_timeout_tag);
9606 compose->draft_timeout_tag = -1;
9609 compose_send(compose);
9612 static void compose_send_later_cb(GtkAction *action, gpointer data)
9614 Compose *compose = (Compose *)data;
9618 compose_allow_user_actions(compose, FALSE);
9619 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9620 compose_allow_user_actions(compose, TRUE);
9624 compose_close(compose);
9625 } else if (val == -1) {
9626 alertpanel_error(_("Could not queue message."));
9627 } else if (val == -2) {
9628 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9629 } else if (val == -3) {
9630 if (privacy_peek_error())
9631 alertpanel_error(_("Could not queue message for sending:\n\n"
9632 "Signature failed: %s"), privacy_get_error());
9633 } else if (val == -4) {
9634 alertpanel_error(_("Could not queue message for sending:\n\n"
9635 "Charset conversion failed."));
9636 } else if (val == -5) {
9637 alertpanel_error(_("Could not queue message for sending:\n\n"
9638 "Couldn't get recipient encryption key."));
9639 } else if (val == -6) {
9642 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9645 #define DRAFTED_AT_EXIT "drafted_at_exit"
9646 static void compose_register_draft(MsgInfo *info)
9648 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9649 DRAFTED_AT_EXIT, NULL);
9650 FILE *fp = g_fopen(filepath, "ab");
9653 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9661 gboolean compose_draft (gpointer data, guint action)
9663 Compose *compose = (Compose *)data;
9668 MsgFlags flag = {0, 0};
9669 static gboolean lock = FALSE;
9670 MsgInfo *newmsginfo;
9672 gboolean target_locked = FALSE;
9673 gboolean err = FALSE;
9675 if (lock) return FALSE;
9677 if (compose->sending)
9680 draft = account_get_special_folder(compose->account, F_DRAFT);
9681 cm_return_val_if_fail(draft != NULL, FALSE);
9683 if (!g_mutex_trylock(compose->mutex)) {
9684 /* we don't want to lock the mutex once it's available,
9685 * because as the only other part of compose.c locking
9686 * it is compose_close - which means once unlocked,
9687 * the compose struct will be freed */
9688 debug_print("couldn't lock mutex, probably sending\n");
9694 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9695 G_DIR_SEPARATOR, compose);
9696 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9697 FILE_OP_ERROR(tmp, "fopen");
9701 /* chmod for security */
9702 if (change_file_mode_rw(fp, tmp) < 0) {
9703 FILE_OP_ERROR(tmp, "chmod");
9704 g_warning("can't change file mode\n");
9707 /* Save draft infos */
9708 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9709 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9711 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9712 gchar *savefolderid;
9714 savefolderid = compose_get_save_to(compose);
9715 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9716 g_free(savefolderid);
9718 if (compose->return_receipt) {
9719 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9721 if (compose->privacy_system) {
9722 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9723 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9724 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9727 /* Message-ID of message replying to */
9728 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9731 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9732 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9735 /* Message-ID of message forwarding to */
9736 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9739 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9740 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9744 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9745 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9747 sheaders = compose_get_manual_headers_info(compose);
9748 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9751 /* end of headers */
9752 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9759 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9763 if (fclose(fp) == EOF) {
9767 if (compose->targetinfo) {
9768 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9769 flag.perm_flags = target_locked?MSG_LOCKED:0;
9771 flag.tmp_flags = MSG_DRAFT;
9773 folder_item_scan(draft);
9774 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9775 MsgInfo *tmpinfo = NULL;
9776 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9777 if (compose->msgid) {
9778 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9781 msgnum = tmpinfo->msgnum;
9782 procmsg_msginfo_free(tmpinfo);
9783 debug_print("got draft msgnum %d from scanning\n", msgnum);
9785 debug_print("didn't get draft msgnum after scanning\n");
9788 debug_print("got draft msgnum %d from adding\n", msgnum);
9794 if (action != COMPOSE_AUTO_SAVE) {
9795 if (action != COMPOSE_DRAFT_FOR_EXIT)
9796 alertpanel_error(_("Could not save draft."));
9799 gtkut_window_popup(compose->window);
9800 val = alertpanel_full(_("Could not save draft"),
9801 _("Could not save draft.\n"
9802 "Do you want to cancel exit or discard this email?"),
9803 _("_Cancel exit"), _("_Discard email"), NULL,
9804 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9805 if (val == G_ALERTALTERNATE) {
9807 g_mutex_unlock(compose->mutex); /* must be done before closing */
9808 compose_close(compose);
9812 g_mutex_unlock(compose->mutex); /* must be done before closing */
9821 if (compose->mode == COMPOSE_REEDIT) {
9822 compose_remove_reedit_target(compose, TRUE);
9825 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9828 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9830 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9832 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9833 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9834 procmsg_msginfo_set_flags(newmsginfo, 0,
9835 MSG_HAS_ATTACHMENT);
9837 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9838 compose_register_draft(newmsginfo);
9840 procmsg_msginfo_free(newmsginfo);
9843 folder_item_scan(draft);
9845 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9847 g_mutex_unlock(compose->mutex); /* must be done before closing */
9848 compose_close(compose);
9854 path = folder_item_fetch_msg(draft, msgnum);
9856 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9859 if (g_stat(path, &s) < 0) {
9860 FILE_OP_ERROR(path, "stat");
9866 procmsg_msginfo_free(compose->targetinfo);
9867 compose->targetinfo = procmsg_msginfo_new();
9868 compose->targetinfo->msgnum = msgnum;
9869 compose->targetinfo->size = (goffset)s.st_size;
9870 compose->targetinfo->mtime = s.st_mtime;
9871 compose->targetinfo->folder = draft;
9873 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9874 compose->mode = COMPOSE_REEDIT;
9876 if (action == COMPOSE_AUTO_SAVE) {
9877 compose->autosaved_draft = compose->targetinfo;
9879 compose->modified = FALSE;
9880 compose_set_title(compose);
9884 g_mutex_unlock(compose->mutex);
9888 void compose_clear_exit_drafts(void)
9890 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9891 DRAFTED_AT_EXIT, NULL);
9892 if (is_file_exist(filepath))
9893 claws_unlink(filepath);
9898 void compose_reopen_exit_drafts(void)
9900 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9901 DRAFTED_AT_EXIT, NULL);
9902 FILE *fp = g_fopen(filepath, "rb");
9906 while (fgets(buf, sizeof(buf), fp)) {
9907 gchar **parts = g_strsplit(buf, "\t", 2);
9908 const gchar *folder = parts[0];
9909 int msgnum = parts[1] ? atoi(parts[1]):-1;
9911 if (folder && *folder && msgnum > -1) {
9912 FolderItem *item = folder_find_item_from_identifier(folder);
9913 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9915 compose_reedit(info, FALSE);
9922 compose_clear_exit_drafts();
9925 static void compose_save_cb(GtkAction *action, gpointer data)
9927 Compose *compose = (Compose *)data;
9928 compose_draft(compose, COMPOSE_KEEP_EDITING);
9929 compose->rmode = COMPOSE_REEDIT;
9932 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9934 if (compose && file_list) {
9937 for ( tmp = file_list; tmp; tmp = tmp->next) {
9938 gchar *file = (gchar *) tmp->data;
9939 gchar *utf8_filename = conv_filename_to_utf8(file);
9940 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9941 compose_changed_cb(NULL, compose);
9946 g_free(utf8_filename);
9951 static void compose_attach_cb(GtkAction *action, gpointer data)
9953 Compose *compose = (Compose *)data;
9956 if (compose->redirect_filename != NULL)
9959 /* Set focus_window properly, in case we were called via popup menu,
9960 * which unsets it (via focus_out_event callback on compose window). */
9961 manage_window_focus_in(compose->window, NULL, NULL);
9963 file_list = filesel_select_multiple_files_open(_("Select file"));
9966 compose_attach_from_list(compose, file_list, TRUE);
9967 g_list_free(file_list);
9971 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9973 Compose *compose = (Compose *)data;
9975 gint files_inserted = 0;
9977 file_list = filesel_select_multiple_files_open(_("Select file"));
9982 for ( tmp = file_list; tmp; tmp = tmp->next) {
9983 gchar *file = (gchar *) tmp->data;
9984 gchar *filedup = g_strdup(file);
9985 gchar *shortfile = g_path_get_basename(filedup);
9986 ComposeInsertResult res;
9987 /* insert the file if the file is short or if the user confirmed that
9988 he/she wants to insert the large file */
9989 res = compose_insert_file(compose, file);
9990 if (res == COMPOSE_INSERT_READ_ERROR) {
9991 alertpanel_error(_("File '%s' could not be read."), shortfile);
9992 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9993 alertpanel_error(_("File '%s' contained invalid characters\n"
9994 "for the current encoding, insertion may be incorrect."),
9996 } else if (res == COMPOSE_INSERT_SUCCESS)
10003 g_list_free(file_list);
10007 if (files_inserted > 0 && compose->gtkaspell &&
10008 compose->gtkaspell->check_while_typing)
10009 gtkaspell_highlight_all(compose->gtkaspell);
10013 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10015 Compose *compose = (Compose *)data;
10017 compose_insert_sig(compose, FALSE);
10020 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10022 Compose *compose = (Compose *)data;
10024 compose_insert_sig(compose, TRUE);
10027 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10031 Compose *compose = (Compose *)data;
10033 gtkut_widget_get_uposition(widget, &x, &y);
10034 if (!compose->batch) {
10035 prefs_common.compose_x = x;
10036 prefs_common.compose_y = y;
10038 if (compose->sending || compose->updating)
10040 compose_close_cb(NULL, compose);
10044 void compose_close_toolbar(Compose *compose)
10046 compose_close_cb(NULL, compose);
10049 static void compose_close_cb(GtkAction *action, gpointer data)
10051 Compose *compose = (Compose *)data;
10055 if (compose->exteditor_tag != -1) {
10056 if (!compose_ext_editor_kill(compose))
10061 if (compose->modified) {
10062 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10063 if (!g_mutex_trylock(compose->mutex)) {
10064 /* we don't want to lock the mutex once it's available,
10065 * because as the only other part of compose.c locking
10066 * it is compose_close - which means once unlocked,
10067 * the compose struct will be freed */
10068 debug_print("couldn't lock mutex, probably sending\n");
10072 val = alertpanel(_("Discard message"),
10073 _("This message has been modified. Discard it?"),
10074 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10076 val = alertpanel(_("Save changes"),
10077 _("This message has been modified. Save the latest changes?"),
10078 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10080 g_mutex_unlock(compose->mutex);
10082 case G_ALERTDEFAULT:
10083 if (prefs_common.autosave && !reedit)
10084 compose_remove_draft(compose);
10086 case G_ALERTALTERNATE:
10087 compose_draft(data, COMPOSE_QUIT_EDITING);
10094 compose_close(compose);
10097 static void compose_print_cb(GtkAction *action, gpointer data)
10099 Compose *compose = (Compose *) data;
10101 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10102 if (compose->targetinfo)
10103 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10106 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10108 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10109 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10110 Compose *compose = (Compose *) data;
10113 compose->out_encoding = (CharSet)value;
10116 static void compose_address_cb(GtkAction *action, gpointer data)
10118 Compose *compose = (Compose *)data;
10120 #ifndef USE_NEW_ADDRBOOK
10121 addressbook_open(compose);
10123 GError* error = NULL;
10124 addressbook_connect_signals(compose);
10125 addressbook_dbus_open(TRUE, &error);
10127 g_warning("%s", error->message);
10128 g_error_free(error);
10133 static void about_show_cb(GtkAction *action, gpointer data)
10138 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10140 Compose *compose = (Compose *)data;
10145 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10146 cm_return_if_fail(tmpl != NULL);
10148 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10150 val = alertpanel(_("Apply template"), msg,
10151 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10154 if (val == G_ALERTDEFAULT)
10155 compose_template_apply(compose, tmpl, TRUE);
10156 else if (val == G_ALERTALTERNATE)
10157 compose_template_apply(compose, tmpl, FALSE);
10160 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10162 Compose *compose = (Compose *)data;
10164 compose_exec_ext_editor(compose);
10167 static void compose_undo_cb(GtkAction *action, gpointer data)
10169 Compose *compose = (Compose *)data;
10170 gboolean prev_autowrap = compose->autowrap;
10172 compose->autowrap = FALSE;
10173 undo_undo(compose->undostruct);
10174 compose->autowrap = prev_autowrap;
10177 static void compose_redo_cb(GtkAction *action, gpointer data)
10179 Compose *compose = (Compose *)data;
10180 gboolean prev_autowrap = compose->autowrap;
10182 compose->autowrap = FALSE;
10183 undo_redo(compose->undostruct);
10184 compose->autowrap = prev_autowrap;
10187 static void entry_cut_clipboard(GtkWidget *entry)
10189 if (GTK_IS_EDITABLE(entry))
10190 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10191 else if (GTK_IS_TEXT_VIEW(entry))
10192 gtk_text_buffer_cut_clipboard(
10193 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10194 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10198 static void entry_copy_clipboard(GtkWidget *entry)
10200 if (GTK_IS_EDITABLE(entry))
10201 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10202 else if (GTK_IS_TEXT_VIEW(entry))
10203 gtk_text_buffer_copy_clipboard(
10204 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10205 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10208 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10209 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10211 if (GTK_IS_TEXT_VIEW(entry)) {
10212 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10213 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10214 GtkTextIter start_iter, end_iter;
10216 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10218 if (contents == NULL)
10221 /* we shouldn't delete the selection when middle-click-pasting, or we
10222 * can't mid-click-paste our own selection */
10223 if (clip != GDK_SELECTION_PRIMARY) {
10224 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10225 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10228 if (insert_place == NULL) {
10229 /* if insert_place isn't specified, insert at the cursor.
10230 * used for Ctrl-V pasting */
10231 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10232 start = gtk_text_iter_get_offset(&start_iter);
10233 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10235 /* if insert_place is specified, paste here.
10236 * used for mid-click-pasting */
10237 start = gtk_text_iter_get_offset(insert_place);
10238 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10239 if (prefs_common.primary_paste_unselects)
10240 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10244 /* paste unwrapped: mark the paste so it's not wrapped later */
10245 end = start + strlen(contents);
10246 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10247 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10248 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10249 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10250 /* rewrap paragraph now (after a mid-click-paste) */
10251 mark_start = gtk_text_buffer_get_insert(buffer);
10252 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10253 gtk_text_iter_backward_char(&start_iter);
10254 compose_beautify_paragraph(compose, &start_iter, TRUE);
10256 } else if (GTK_IS_EDITABLE(entry))
10257 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10259 compose->modified = TRUE;
10262 static void entry_allsel(GtkWidget *entry)
10264 if (GTK_IS_EDITABLE(entry))
10265 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10266 else if (GTK_IS_TEXT_VIEW(entry)) {
10267 GtkTextIter startiter, enditer;
10268 GtkTextBuffer *textbuf;
10270 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10271 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10272 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10274 gtk_text_buffer_move_mark_by_name(textbuf,
10275 "selection_bound", &startiter);
10276 gtk_text_buffer_move_mark_by_name(textbuf,
10277 "insert", &enditer);
10281 static void compose_cut_cb(GtkAction *action, gpointer data)
10283 Compose *compose = (Compose *)data;
10284 if (compose->focused_editable
10285 #ifndef GENERIC_UMPC
10286 && gtk_widget_has_focus(compose->focused_editable)
10289 entry_cut_clipboard(compose->focused_editable);
10292 static void compose_copy_cb(GtkAction *action, gpointer data)
10294 Compose *compose = (Compose *)data;
10295 if (compose->focused_editable
10296 #ifndef GENERIC_UMPC
10297 && gtk_widget_has_focus(compose->focused_editable)
10300 entry_copy_clipboard(compose->focused_editable);
10303 static void compose_paste_cb(GtkAction *action, gpointer data)
10305 Compose *compose = (Compose *)data;
10306 gint prev_autowrap;
10307 GtkTextBuffer *buffer;
10309 if (compose->focused_editable &&
10310 #ifndef GENERIC_UMPC
10311 gtk_widget_has_focus(compose->focused_editable)
10314 entry_paste_clipboard(compose, compose->focused_editable,
10315 prefs_common.linewrap_pastes,
10316 GDK_SELECTION_CLIPBOARD, NULL);
10321 #ifndef GENERIC_UMPC
10322 gtk_widget_has_focus(compose->text) &&
10324 compose->gtkaspell &&
10325 compose->gtkaspell->check_while_typing)
10326 gtkaspell_highlight_all(compose->gtkaspell);
10330 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10332 Compose *compose = (Compose *)data;
10333 gint wrap_quote = prefs_common.linewrap_quote;
10334 if (compose->focused_editable
10335 #ifndef GENERIC_UMPC
10336 && gtk_widget_has_focus(compose->focused_editable)
10339 /* let text_insert() (called directly or at a later time
10340 * after the gtk_editable_paste_clipboard) know that
10341 * text is to be inserted as a quotation. implemented
10342 * by using a simple refcount... */
10343 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10344 G_OBJECT(compose->focused_editable),
10345 "paste_as_quotation"));
10346 g_object_set_data(G_OBJECT(compose->focused_editable),
10347 "paste_as_quotation",
10348 GINT_TO_POINTER(paste_as_quotation + 1));
10349 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10350 entry_paste_clipboard(compose, compose->focused_editable,
10351 prefs_common.linewrap_pastes,
10352 GDK_SELECTION_CLIPBOARD, NULL);
10353 prefs_common.linewrap_quote = wrap_quote;
10357 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10359 Compose *compose = (Compose *)data;
10360 gint prev_autowrap;
10361 GtkTextBuffer *buffer;
10363 if (compose->focused_editable
10364 #ifndef GENERIC_UMPC
10365 && gtk_widget_has_focus(compose->focused_editable)
10368 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10369 GDK_SELECTION_CLIPBOARD, NULL);
10374 #ifndef GENERIC_UMPC
10375 gtk_widget_has_focus(compose->text) &&
10377 compose->gtkaspell &&
10378 compose->gtkaspell->check_while_typing)
10379 gtkaspell_highlight_all(compose->gtkaspell);
10383 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10385 Compose *compose = (Compose *)data;
10386 gint prev_autowrap;
10387 GtkTextBuffer *buffer;
10389 if (compose->focused_editable
10390 #ifndef GENERIC_UMPC
10391 && gtk_widget_has_focus(compose->focused_editable)
10394 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10395 GDK_SELECTION_CLIPBOARD, NULL);
10400 #ifndef GENERIC_UMPC
10401 gtk_widget_has_focus(compose->text) &&
10403 compose->gtkaspell &&
10404 compose->gtkaspell->check_while_typing)
10405 gtkaspell_highlight_all(compose->gtkaspell);
10409 static void compose_allsel_cb(GtkAction *action, gpointer data)
10411 Compose *compose = (Compose *)data;
10412 if (compose->focused_editable
10413 #ifndef GENERIC_UMPC
10414 && gtk_widget_has_focus(compose->focused_editable)
10417 entry_allsel(compose->focused_editable);
10420 static void textview_move_beginning_of_line (GtkTextView *text)
10422 GtkTextBuffer *buffer;
10426 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10428 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10429 mark = gtk_text_buffer_get_insert(buffer);
10430 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10431 gtk_text_iter_set_line_offset(&ins, 0);
10432 gtk_text_buffer_place_cursor(buffer, &ins);
10435 static void textview_move_forward_character (GtkTextView *text)
10437 GtkTextBuffer *buffer;
10441 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10443 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10444 mark = gtk_text_buffer_get_insert(buffer);
10445 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10446 if (gtk_text_iter_forward_cursor_position(&ins))
10447 gtk_text_buffer_place_cursor(buffer, &ins);
10450 static void textview_move_backward_character (GtkTextView *text)
10452 GtkTextBuffer *buffer;
10456 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10458 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10459 mark = gtk_text_buffer_get_insert(buffer);
10460 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10461 if (gtk_text_iter_backward_cursor_position(&ins))
10462 gtk_text_buffer_place_cursor(buffer, &ins);
10465 static void textview_move_forward_word (GtkTextView *text)
10467 GtkTextBuffer *buffer;
10472 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10474 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10475 mark = gtk_text_buffer_get_insert(buffer);
10476 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10477 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10478 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10479 gtk_text_iter_backward_word_start(&ins);
10480 gtk_text_buffer_place_cursor(buffer, &ins);
10484 static void textview_move_backward_word (GtkTextView *text)
10486 GtkTextBuffer *buffer;
10490 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10492 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10493 mark = gtk_text_buffer_get_insert(buffer);
10494 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10495 if (gtk_text_iter_backward_word_starts(&ins, 1))
10496 gtk_text_buffer_place_cursor(buffer, &ins);
10499 static void textview_move_end_of_line (GtkTextView *text)
10501 GtkTextBuffer *buffer;
10505 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10507 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10508 mark = gtk_text_buffer_get_insert(buffer);
10509 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10510 if (gtk_text_iter_forward_to_line_end(&ins))
10511 gtk_text_buffer_place_cursor(buffer, &ins);
10514 static void textview_move_next_line (GtkTextView *text)
10516 GtkTextBuffer *buffer;
10521 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10523 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10524 mark = gtk_text_buffer_get_insert(buffer);
10525 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10526 offset = gtk_text_iter_get_line_offset(&ins);
10527 if (gtk_text_iter_forward_line(&ins)) {
10528 gtk_text_iter_set_line_offset(&ins, offset);
10529 gtk_text_buffer_place_cursor(buffer, &ins);
10533 static void textview_move_previous_line (GtkTextView *text)
10535 GtkTextBuffer *buffer;
10540 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10542 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10543 mark = gtk_text_buffer_get_insert(buffer);
10544 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10545 offset = gtk_text_iter_get_line_offset(&ins);
10546 if (gtk_text_iter_backward_line(&ins)) {
10547 gtk_text_iter_set_line_offset(&ins, offset);
10548 gtk_text_buffer_place_cursor(buffer, &ins);
10552 static void textview_delete_forward_character (GtkTextView *text)
10554 GtkTextBuffer *buffer;
10556 GtkTextIter ins, end_iter;
10558 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10560 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10561 mark = gtk_text_buffer_get_insert(buffer);
10562 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10564 if (gtk_text_iter_forward_char(&end_iter)) {
10565 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10569 static void textview_delete_backward_character (GtkTextView *text)
10571 GtkTextBuffer *buffer;
10573 GtkTextIter ins, end_iter;
10575 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10577 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10578 mark = gtk_text_buffer_get_insert(buffer);
10579 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10581 if (gtk_text_iter_backward_char(&end_iter)) {
10582 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10586 static void textview_delete_forward_word (GtkTextView *text)
10588 GtkTextBuffer *buffer;
10590 GtkTextIter ins, end_iter;
10592 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10594 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10595 mark = gtk_text_buffer_get_insert(buffer);
10596 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10598 if (gtk_text_iter_forward_word_end(&end_iter)) {
10599 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10603 static void textview_delete_backward_word (GtkTextView *text)
10605 GtkTextBuffer *buffer;
10607 GtkTextIter ins, end_iter;
10609 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10611 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10612 mark = gtk_text_buffer_get_insert(buffer);
10613 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10615 if (gtk_text_iter_backward_word_start(&end_iter)) {
10616 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10620 static void textview_delete_line (GtkTextView *text)
10622 GtkTextBuffer *buffer;
10624 GtkTextIter ins, start_iter, end_iter;
10626 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10628 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10629 mark = gtk_text_buffer_get_insert(buffer);
10630 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10633 gtk_text_iter_set_line_offset(&start_iter, 0);
10636 if (gtk_text_iter_ends_line(&end_iter)){
10637 if (!gtk_text_iter_forward_char(&end_iter))
10638 gtk_text_iter_backward_char(&start_iter);
10641 gtk_text_iter_forward_to_line_end(&end_iter);
10642 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10645 static void textview_delete_to_line_end (GtkTextView *text)
10647 GtkTextBuffer *buffer;
10649 GtkTextIter ins, end_iter;
10651 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10653 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10654 mark = gtk_text_buffer_get_insert(buffer);
10655 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10657 if (gtk_text_iter_ends_line(&end_iter))
10658 gtk_text_iter_forward_char(&end_iter);
10660 gtk_text_iter_forward_to_line_end(&end_iter);
10661 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10664 #define DO_ACTION(name, act) { \
10665 if(!strcmp(name, a_name)) { \
10669 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10671 const gchar *a_name = gtk_action_get_name(action);
10672 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10673 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10674 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10675 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10676 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10677 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10678 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10679 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10680 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10681 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10682 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10683 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10684 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10685 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10689 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10691 Compose *compose = (Compose *)data;
10692 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10693 ComposeCallAdvancedAction action = -1;
10695 action = compose_call_advanced_action_from_path(gaction);
10698 void (*do_action) (GtkTextView *text);
10699 } action_table[] = {
10700 {textview_move_beginning_of_line},
10701 {textview_move_forward_character},
10702 {textview_move_backward_character},
10703 {textview_move_forward_word},
10704 {textview_move_backward_word},
10705 {textview_move_end_of_line},
10706 {textview_move_next_line},
10707 {textview_move_previous_line},
10708 {textview_delete_forward_character},
10709 {textview_delete_backward_character},
10710 {textview_delete_forward_word},
10711 {textview_delete_backward_word},
10712 {textview_delete_line},
10713 {textview_delete_to_line_end}
10716 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10718 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10719 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10720 if (action_table[action].do_action)
10721 action_table[action].do_action(text);
10723 g_warning("Not implemented yet.");
10727 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10729 GtkAllocation allocation;
10733 if (GTK_IS_EDITABLE(widget)) {
10734 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10735 gtk_editable_set_position(GTK_EDITABLE(widget),
10738 if ((parent = gtk_widget_get_parent(widget))
10739 && (parent = gtk_widget_get_parent(parent))
10740 && (parent = gtk_widget_get_parent(parent))) {
10741 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10742 gtk_widget_get_allocation(widget, &allocation);
10743 gint y = allocation.y;
10744 gint height = allocation.height;
10745 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10746 (GTK_SCROLLED_WINDOW(parent));
10748 gfloat value = gtk_adjustment_get_value(shown);
10749 gfloat upper = gtk_adjustment_get_upper(shown);
10750 gfloat page_size = gtk_adjustment_get_page_size(shown);
10751 if (y < (int)value) {
10752 gtk_adjustment_set_value(shown, y - 1);
10754 if ((y + height) > ((int)value + (int)page_size)) {
10755 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10756 gtk_adjustment_set_value(shown,
10757 y + height - (int)page_size - 1);
10759 gtk_adjustment_set_value(shown,
10760 (int)upper - (int)page_size - 1);
10767 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10768 compose->focused_editable = widget;
10770 #ifdef GENERIC_UMPC
10771 if (GTK_IS_TEXT_VIEW(widget)
10772 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10773 g_object_ref(compose->notebook);
10774 g_object_ref(compose->edit_vbox);
10775 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10776 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10777 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10778 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10779 g_object_unref(compose->notebook);
10780 g_object_unref(compose->edit_vbox);
10781 g_signal_handlers_block_by_func(G_OBJECT(widget),
10782 G_CALLBACK(compose_grab_focus_cb),
10784 gtk_widget_grab_focus(widget);
10785 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10786 G_CALLBACK(compose_grab_focus_cb),
10788 } else if (!GTK_IS_TEXT_VIEW(widget)
10789 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10790 g_object_ref(compose->notebook);
10791 g_object_ref(compose->edit_vbox);
10792 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10793 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10794 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10795 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10796 g_object_unref(compose->notebook);
10797 g_object_unref(compose->edit_vbox);
10798 g_signal_handlers_block_by_func(G_OBJECT(widget),
10799 G_CALLBACK(compose_grab_focus_cb),
10801 gtk_widget_grab_focus(widget);
10802 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10803 G_CALLBACK(compose_grab_focus_cb),
10809 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10811 compose->modified = TRUE;
10812 // compose_beautify_paragraph(compose, NULL, TRUE);
10813 #ifndef GENERIC_UMPC
10814 compose_set_title(compose);
10818 static void compose_wrap_cb(GtkAction *action, gpointer data)
10820 Compose *compose = (Compose *)data;
10821 compose_beautify_paragraph(compose, NULL, TRUE);
10824 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10826 Compose *compose = (Compose *)data;
10827 compose_wrap_all_full(compose, TRUE);
10830 static void compose_find_cb(GtkAction *action, gpointer data)
10832 Compose *compose = (Compose *)data;
10834 message_search_compose(compose);
10837 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10840 Compose *compose = (Compose *)data;
10841 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10842 if (compose->autowrap)
10843 compose_wrap_all_full(compose, TRUE);
10844 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10847 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10850 Compose *compose = (Compose *)data;
10851 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10854 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10856 Compose *compose = (Compose *)data;
10858 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10861 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10863 Compose *compose = (Compose *)data;
10865 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10868 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10870 g_free(compose->privacy_system);
10872 compose->privacy_system = g_strdup(account->default_privacy_system);
10873 compose_update_privacy_system_menu_item(compose, warn);
10876 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10878 Compose *compose = (Compose *)data;
10880 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10881 gtk_widget_show(compose->ruler_hbox);
10882 prefs_common.show_ruler = TRUE;
10884 gtk_widget_hide(compose->ruler_hbox);
10885 gtk_widget_queue_resize(compose->edit_vbox);
10886 prefs_common.show_ruler = FALSE;
10890 static void compose_attach_drag_received_cb (GtkWidget *widget,
10891 GdkDragContext *context,
10894 GtkSelectionData *data,
10897 gpointer user_data)
10899 Compose *compose = (Compose *)user_data;
10903 type = gtk_selection_data_get_data_type(data);
10904 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10906 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10908 ) && gtk_drag_get_source_widget(context) !=
10909 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10910 list = uri_list_extract_filenames(
10911 (const gchar *)gtk_selection_data_get_data(data));
10912 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10913 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10914 compose_attach_append
10915 (compose, (const gchar *)tmp->data,
10916 utf8_filename, NULL, NULL);
10917 g_free(utf8_filename);
10919 if (list) compose_changed_cb(NULL, compose);
10920 list_free_strings(list);
10922 } else if (gtk_drag_get_source_widget(context)
10923 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10924 /* comes from our summaryview */
10925 SummaryView * summaryview = NULL;
10926 GSList * list = NULL, *cur = NULL;
10928 if (mainwindow_get_mainwindow())
10929 summaryview = mainwindow_get_mainwindow()->summaryview;
10932 list = summary_get_selected_msg_list(summaryview);
10934 for (cur = list; cur; cur = cur->next) {
10935 MsgInfo *msginfo = (MsgInfo *)cur->data;
10936 gchar *file = NULL;
10938 file = procmsg_get_message_file_full(msginfo,
10941 compose_attach_append(compose, (const gchar *)file,
10942 (const gchar *)file, "message/rfc822", NULL);
10946 g_slist_free(list);
10950 static gboolean compose_drag_drop(GtkWidget *widget,
10951 GdkDragContext *drag_context,
10953 guint time, gpointer user_data)
10955 /* not handling this signal makes compose_insert_drag_received_cb
10960 static gboolean completion_set_focus_to_subject
10961 (GtkWidget *widget,
10962 GdkEventKey *event,
10965 cm_return_val_if_fail(compose != NULL, FALSE);
10967 /* make backtab move to subject field */
10968 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10969 gtk_widget_grab_focus(compose->subject_entry);
10975 static void compose_insert_drag_received_cb (GtkWidget *widget,
10976 GdkDragContext *drag_context,
10979 GtkSelectionData *data,
10982 gpointer user_data)
10984 Compose *compose = (Compose *)user_data;
10988 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10990 type = gtk_selection_data_get_data_type(data);
10992 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10994 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10996 AlertValue val = G_ALERTDEFAULT;
10997 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10999 list = uri_list_extract_filenames(ddata);
11000 if (list == NULL && strstr(ddata, "://")) {
11001 /* Assume a list of no files, and data has ://, is a remote link */
11002 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11003 gchar *tmpfile = get_tmp_file();
11004 str_write_to_file(tmpdata, tmpfile);
11006 compose_insert_file(compose, tmpfile);
11007 claws_unlink(tmpfile);
11009 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11010 compose_beautify_paragraph(compose, NULL, TRUE);
11013 switch (prefs_common.compose_dnd_mode) {
11014 case COMPOSE_DND_ASK:
11015 val = alertpanel_full(_("Insert or attach?"),
11016 _("Do you want to insert the contents of the file(s) "
11017 "into the message body, or attach it to the email?"),
11018 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11019 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11021 case COMPOSE_DND_INSERT:
11022 val = G_ALERTALTERNATE;
11024 case COMPOSE_DND_ATTACH:
11025 val = G_ALERTOTHER;
11028 /* unexpected case */
11029 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11032 if (val & G_ALERTDISABLE) {
11033 val &= ~G_ALERTDISABLE;
11034 /* remember what action to perform by default, only if we don't click Cancel */
11035 if (val == G_ALERTALTERNATE)
11036 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11037 else if (val == G_ALERTOTHER)
11038 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11041 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11042 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11043 list_free_strings(list);
11046 } else if (val == G_ALERTOTHER) {
11047 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11048 list_free_strings(list);
11053 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11054 compose_insert_file(compose, (const gchar *)tmp->data);
11056 list_free_strings(list);
11058 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11063 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11066 static void compose_header_drag_received_cb (GtkWidget *widget,
11067 GdkDragContext *drag_context,
11070 GtkSelectionData *data,
11073 gpointer user_data)
11075 GtkEditable *entry = (GtkEditable *)user_data;
11076 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11078 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11081 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11082 gchar *decoded=g_new(gchar, strlen(email));
11085 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11086 gtk_editable_delete_text(entry, 0, -1);
11087 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11088 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11092 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11095 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11097 Compose *compose = (Compose *)data;
11099 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11100 compose->return_receipt = TRUE;
11102 compose->return_receipt = FALSE;
11105 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11107 Compose *compose = (Compose *)data;
11109 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11110 compose->remove_references = TRUE;
11112 compose->remove_references = FALSE;
11115 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11116 ComposeHeaderEntry *headerentry)
11118 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11122 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11123 GdkEventKey *event,
11124 ComposeHeaderEntry *headerentry)
11126 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11127 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11128 !(event->state & GDK_MODIFIER_MASK) &&
11129 (event->keyval == GDK_KEY_BackSpace) &&
11130 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11131 gtk_container_remove
11132 (GTK_CONTAINER(headerentry->compose->header_table),
11133 headerentry->combo);
11134 gtk_container_remove
11135 (GTK_CONTAINER(headerentry->compose->header_table),
11136 headerentry->entry);
11137 headerentry->compose->header_list =
11138 g_slist_remove(headerentry->compose->header_list,
11140 g_free(headerentry);
11141 } else if (event->keyval == GDK_KEY_Tab) {
11142 if (headerentry->compose->header_last == headerentry) {
11143 /* Override default next focus, and give it to subject_entry
11144 * instead of notebook tabs
11146 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11147 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11154 static gboolean scroll_postpone(gpointer data)
11156 Compose *compose = (Compose *)data;
11158 cm_return_val_if_fail(!compose->batch, FALSE);
11160 GTK_EVENTS_FLUSH();
11161 compose_show_first_last_header(compose, FALSE);
11165 static void compose_headerentry_changed_cb(GtkWidget *entry,
11166 ComposeHeaderEntry *headerentry)
11168 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11169 compose_create_header_entry(headerentry->compose);
11170 g_signal_handlers_disconnect_matched
11171 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11172 0, 0, NULL, NULL, headerentry);
11174 if (!headerentry->compose->batch)
11175 g_timeout_add(0, scroll_postpone, headerentry->compose);
11179 static gboolean compose_defer_auto_save_draft(Compose *compose)
11181 compose->draft_timeout_tag = -1;
11182 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11186 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11188 GtkAdjustment *vadj;
11190 cm_return_if_fail(compose);
11191 cm_return_if_fail(!compose->batch);
11192 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11193 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11194 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11195 gtk_widget_get_parent(compose->header_table)));
11196 gtk_adjustment_set_value(vadj, (show_first ?
11197 gtk_adjustment_get_lower(vadj) :
11198 (gtk_adjustment_get_upper(vadj) -
11199 gtk_adjustment_get_page_size(vadj))));
11200 gtk_adjustment_changed(vadj);
11203 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11204 const gchar *text, gint len, Compose *compose)
11206 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11207 (G_OBJECT(compose->text), "paste_as_quotation"));
11210 cm_return_if_fail(text != NULL);
11212 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11213 G_CALLBACK(text_inserted),
11215 if (paste_as_quotation) {
11217 const gchar *qmark;
11219 GtkTextIter start_iter;
11222 len = strlen(text);
11224 new_text = g_strndup(text, len);
11226 qmark = compose_quote_char_from_context(compose);
11228 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11229 gtk_text_buffer_place_cursor(buffer, iter);
11231 pos = gtk_text_iter_get_offset(iter);
11233 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11234 _("Quote format error at line %d."));
11235 quote_fmt_reset_vartable();
11237 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11238 GINT_TO_POINTER(paste_as_quotation - 1));
11240 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11241 gtk_text_buffer_place_cursor(buffer, iter);
11242 gtk_text_buffer_delete_mark(buffer, mark);
11244 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11245 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11246 compose_beautify_paragraph(compose, &start_iter, FALSE);
11247 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11248 gtk_text_buffer_delete_mark(buffer, mark);
11250 if (strcmp(text, "\n") || compose->automatic_break
11251 || gtk_text_iter_starts_line(iter)) {
11252 GtkTextIter before_ins;
11253 gtk_text_buffer_insert(buffer, iter, text, len);
11254 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11255 before_ins = *iter;
11256 gtk_text_iter_backward_chars(&before_ins, len);
11257 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11260 /* check if the preceding is just whitespace or quote */
11261 GtkTextIter start_line;
11262 gchar *tmp = NULL, *quote = NULL;
11263 gint quote_len = 0, is_normal = 0;
11264 start_line = *iter;
11265 gtk_text_iter_set_line_offset(&start_line, 0);
11266 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11269 if (*tmp == '\0') {
11272 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11280 gtk_text_buffer_insert(buffer, iter, text, len);
11282 gtk_text_buffer_insert_with_tags_by_name(buffer,
11283 iter, text, len, "no_join", NULL);
11288 if (!paste_as_quotation) {
11289 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11290 compose_beautify_paragraph(compose, iter, FALSE);
11291 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11292 gtk_text_buffer_delete_mark(buffer, mark);
11295 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11296 G_CALLBACK(text_inserted),
11298 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11300 if (prefs_common.autosave &&
11301 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11302 compose->draft_timeout_tag != -2 /* disabled while loading */)
11303 compose->draft_timeout_tag = g_timeout_add
11304 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11308 static void compose_check_all(GtkAction *action, gpointer data)
11310 Compose *compose = (Compose *)data;
11311 if (!compose->gtkaspell)
11314 if (gtk_widget_has_focus(compose->subject_entry))
11315 claws_spell_entry_check_all(
11316 CLAWS_SPELL_ENTRY(compose->subject_entry));
11318 gtkaspell_check_all(compose->gtkaspell);
11321 static void compose_highlight_all(GtkAction *action, gpointer data)
11323 Compose *compose = (Compose *)data;
11324 if (compose->gtkaspell) {
11325 claws_spell_entry_recheck_all(
11326 CLAWS_SPELL_ENTRY(compose->subject_entry));
11327 gtkaspell_highlight_all(compose->gtkaspell);
11331 static void compose_check_backwards(GtkAction *action, gpointer data)
11333 Compose *compose = (Compose *)data;
11334 if (!compose->gtkaspell) {
11335 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11339 if (gtk_widget_has_focus(compose->subject_entry))
11340 claws_spell_entry_check_backwards(
11341 CLAWS_SPELL_ENTRY(compose->subject_entry));
11343 gtkaspell_check_backwards(compose->gtkaspell);
11346 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11348 Compose *compose = (Compose *)data;
11349 if (!compose->gtkaspell) {
11350 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11354 if (gtk_widget_has_focus(compose->subject_entry))
11355 claws_spell_entry_check_forwards_go(
11356 CLAWS_SPELL_ENTRY(compose->subject_entry));
11358 gtkaspell_check_forwards_go(compose->gtkaspell);
11363 *\brief Guess originating forward account from MsgInfo and several
11364 * "common preference" settings. Return NULL if no guess.
11366 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11368 PrefsAccount *account = NULL;
11370 cm_return_val_if_fail(msginfo, NULL);
11371 cm_return_val_if_fail(msginfo->folder, NULL);
11372 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11374 if (msginfo->folder->prefs->enable_default_account)
11375 account = account_find_from_id(msginfo->folder->prefs->default_account);
11378 account = msginfo->folder->folder->account;
11380 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11382 Xstrdup_a(to, msginfo->to, return NULL);
11383 extract_address(to);
11384 account = account_find_from_address(to, FALSE);
11387 if (!account && prefs_common.forward_account_autosel) {
11388 gchar cc[BUFFSIZE];
11389 if (!procheader_get_header_from_msginfo
11390 (msginfo, cc,sizeof cc , "Cc:")) {
11391 gchar *buf = cc + strlen("Cc:");
11392 extract_address(buf);
11393 account = account_find_from_address(buf, FALSE);
11397 if (!account && prefs_common.forward_account_autosel) {
11398 gchar deliveredto[BUFFSIZE];
11399 if (!procheader_get_header_from_msginfo
11400 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11401 gchar *buf = deliveredto + strlen("Delivered-To:");
11402 extract_address(buf);
11403 account = account_find_from_address(buf, FALSE);
11410 gboolean compose_close(Compose *compose)
11414 if (!g_mutex_trylock(compose->mutex)) {
11415 /* we have to wait for the (possibly deferred by auto-save)
11416 * drafting to be done, before destroying the compose under
11418 debug_print("waiting for drafting to finish...\n");
11419 compose_allow_user_actions(compose, FALSE);
11420 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11423 cm_return_val_if_fail(compose, FALSE);
11424 gtkut_widget_get_uposition(compose->window, &x, &y);
11425 if (!compose->batch) {
11426 prefs_common.compose_x = x;
11427 prefs_common.compose_y = y;
11429 g_mutex_unlock(compose->mutex);
11430 compose_destroy(compose);
11435 * Add entry field for each address in list.
11436 * \param compose E-Mail composition object.
11437 * \param listAddress List of (formatted) E-Mail addresses.
11439 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11442 node = listAddress;
11444 addr = ( gchar * ) node->data;
11445 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11446 node = g_list_next( node );
11450 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11451 guint action, gboolean opening_multiple)
11453 gchar *body = NULL;
11454 GSList *new_msglist = NULL;
11455 MsgInfo *tmp_msginfo = NULL;
11456 gboolean originally_enc = FALSE;
11457 gboolean originally_sig = FALSE;
11458 Compose *compose = NULL;
11459 gchar *s_system = NULL;
11461 cm_return_if_fail(msgview != NULL);
11463 cm_return_if_fail(msginfo_list != NULL);
11465 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11466 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11467 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11469 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11470 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11471 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11472 orig_msginfo, mimeinfo);
11473 if (tmp_msginfo != NULL) {
11474 new_msglist = g_slist_append(NULL, tmp_msginfo);
11476 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11477 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11478 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11480 tmp_msginfo->folder = orig_msginfo->folder;
11481 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11482 if (orig_msginfo->tags) {
11483 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11484 tmp_msginfo->folder->tags_dirty = TRUE;
11490 if (!opening_multiple)
11491 body = messageview_get_selection(msgview);
11494 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11495 procmsg_msginfo_free(tmp_msginfo);
11496 g_slist_free(new_msglist);
11498 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11500 if (compose && originally_enc) {
11501 compose_force_encryption(compose, compose->account, FALSE, s_system);
11504 if (compose && originally_sig && compose->account->default_sign_reply) {
11505 compose_force_signing(compose, compose->account, s_system);
11509 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11512 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11515 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11516 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11517 GSList *cur = msginfo_list;
11518 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11519 "messages. Opening the windows "
11520 "could take some time. Do you "
11521 "want to continue?"),
11522 g_slist_length(msginfo_list));
11523 if (g_slist_length(msginfo_list) > 9
11524 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11525 != G_ALERTALTERNATE) {
11530 /* We'll open multiple compose windows */
11531 /* let the WM place the next windows */
11532 compose_force_window_origin = FALSE;
11533 for (; cur; cur = cur->next) {
11535 tmplist.data = cur->data;
11536 tmplist.next = NULL;
11537 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11539 compose_force_window_origin = TRUE;
11541 /* forwarding multiple mails as attachments is done via a
11542 * single compose window */
11543 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11547 void compose_check_for_email_account(Compose *compose)
11549 PrefsAccount *ac = NULL, *curr = NULL;
11555 if (compose->account && compose->account->protocol == A_NNTP) {
11556 ac = account_get_cur_account();
11557 if (ac->protocol == A_NNTP) {
11558 list = account_get_list();
11560 for( ; list != NULL ; list = g_list_next(list)) {
11561 curr = (PrefsAccount *) list->data;
11562 if (curr->protocol != A_NNTP) {
11568 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11573 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11574 const gchar *address)
11576 GSList *msginfo_list = NULL;
11577 gchar *body = messageview_get_selection(msgview);
11580 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11582 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11583 compose_check_for_email_account(compose);
11584 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11585 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11586 compose_reply_set_subject(compose, msginfo);
11589 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11592 void compose_set_position(Compose *compose, gint pos)
11594 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11596 gtkut_text_view_set_position(text, pos);
11599 gboolean compose_search_string(Compose *compose,
11600 const gchar *str, gboolean case_sens)
11602 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11604 return gtkut_text_view_search_string(text, str, case_sens);
11607 gboolean compose_search_string_backward(Compose *compose,
11608 const gchar *str, gboolean case_sens)
11610 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11612 return gtkut_text_view_search_string_backward(text, str, case_sens);
11615 /* allocate a msginfo structure and populate its data from a compose data structure */
11616 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11618 MsgInfo *newmsginfo;
11620 gchar buf[BUFFSIZE];
11622 cm_return_val_if_fail( compose != NULL, NULL );
11624 newmsginfo = procmsg_msginfo_new();
11627 get_rfc822_date(buf, sizeof(buf));
11628 newmsginfo->date = g_strdup(buf);
11631 if (compose->from_name) {
11632 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11633 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11637 if (compose->subject_entry)
11638 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11640 /* to, cc, reply-to, newsgroups */
11641 for (list = compose->header_list; list; list = list->next) {
11642 gchar *header = gtk_editable_get_chars(
11644 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11645 gchar *entry = gtk_editable_get_chars(
11646 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11648 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11649 if ( newmsginfo->to == NULL ) {
11650 newmsginfo->to = g_strdup(entry);
11651 } else if (entry && *entry) {
11652 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11653 g_free(newmsginfo->to);
11654 newmsginfo->to = tmp;
11657 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11658 if ( newmsginfo->cc == NULL ) {
11659 newmsginfo->cc = g_strdup(entry);
11660 } else if (entry && *entry) {
11661 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11662 g_free(newmsginfo->cc);
11663 newmsginfo->cc = tmp;
11666 if ( strcasecmp(header,
11667 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11668 if ( newmsginfo->newsgroups == NULL ) {
11669 newmsginfo->newsgroups = g_strdup(entry);
11670 } else if (entry && *entry) {
11671 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11672 g_free(newmsginfo->newsgroups);
11673 newmsginfo->newsgroups = tmp;
11681 /* other data is unset */
11687 /* update compose's dictionaries from folder dict settings */
11688 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11689 FolderItem *folder_item)
11691 cm_return_if_fail(compose != NULL);
11693 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11694 FolderItemPrefs *prefs = folder_item->prefs;
11696 if (prefs->enable_default_dictionary)
11697 gtkaspell_change_dict(compose->gtkaspell,
11698 prefs->default_dictionary, FALSE);
11699 if (folder_item->prefs->enable_default_alt_dictionary)
11700 gtkaspell_change_alt_dict(compose->gtkaspell,
11701 prefs->default_alt_dictionary);
11702 if (prefs->enable_default_dictionary
11703 || prefs->enable_default_alt_dictionary)
11704 compose_spell_menu_changed(compose);
11709 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11711 Compose *compose = (Compose *)data;
11713 cm_return_if_fail(compose != NULL);
11715 gtk_widget_grab_focus(compose->text);