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 qbuf = escape_internal_quotes(buf, '"');
4808 from = g_strdup_printf("%s <%s>",
4809 qbuf, account->address);
4812 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4814 from = g_strdup_printf("<%s>",
4816 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4821 compose_set_title(compose);
4823 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4824 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4826 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4827 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4828 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4830 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4832 activate_privacy_system(compose, account, FALSE);
4834 if (!init && compose->mode != COMPOSE_REDIRECT) {
4835 undo_block(compose->undostruct);
4836 compose_insert_sig(compose, TRUE);
4837 undo_unblock(compose->undostruct);
4840 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4841 #if !GTK_CHECK_VERSION(2, 24, 0)
4842 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4844 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4845 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4846 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4849 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4850 if (account->protocol == A_NNTP) {
4851 if (!strcmp(header, _("To:")))
4852 combobox_select_by_text(
4853 GTK_COMBO_BOX(header_entry->combo),
4856 if (!strcmp(header, _("Newsgroups:")))
4857 combobox_select_by_text(
4858 GTK_COMBO_BOX(header_entry->combo),
4866 /* use account's dict info if set */
4867 if (compose->gtkaspell) {
4868 if (account->enable_default_dictionary)
4869 gtkaspell_change_dict(compose->gtkaspell,
4870 account->default_dictionary, FALSE);
4871 if (account->enable_default_alt_dictionary)
4872 gtkaspell_change_alt_dict(compose->gtkaspell,
4873 account->default_alt_dictionary);
4874 if (account->enable_default_dictionary
4875 || account->enable_default_alt_dictionary)
4876 compose_spell_menu_changed(compose);
4881 gboolean compose_check_for_valid_recipient(Compose *compose) {
4882 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4883 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4884 gboolean recipient_found = FALSE;
4888 /* free to and newsgroup list */
4889 slist_free_strings_full(compose->to_list);
4890 compose->to_list = NULL;
4892 slist_free_strings_full(compose->newsgroup_list);
4893 compose->newsgroup_list = NULL;
4895 /* search header entries for to and newsgroup entries */
4896 for (list = compose->header_list; list; list = list->next) {
4899 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4900 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4903 if (entry[0] != '\0') {
4904 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4905 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4906 compose->to_list = address_list_append(compose->to_list, entry);
4907 recipient_found = TRUE;
4910 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4911 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4912 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4913 recipient_found = TRUE;
4920 return recipient_found;
4923 static gboolean compose_check_for_set_recipients(Compose *compose)
4925 if (compose->account->set_autocc && compose->account->auto_cc) {
4926 gboolean found_other = FALSE;
4928 /* search header entries for to and newsgroup entries */
4929 for (list = compose->header_list; list; list = list->next) {
4932 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4933 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4936 if (strcmp(entry, compose->account->auto_cc)
4937 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4947 if (compose->batch) {
4948 gtk_widget_show_all(compose->window);
4950 aval = alertpanel(_("Send"),
4951 _("The only recipient is the default CC address. Send anyway?"),
4952 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4953 if (aval != G_ALERTALTERNATE)
4957 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4958 gboolean found_other = FALSE;
4960 /* search header entries for to and newsgroup entries */
4961 for (list = compose->header_list; list; list = list->next) {
4964 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4965 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4968 if (strcmp(entry, compose->account->auto_bcc)
4969 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4979 if (compose->batch) {
4980 gtk_widget_show_all(compose->window);
4982 aval = alertpanel(_("Send"),
4983 _("The only recipient is the default BCC address. Send anyway?"),
4984 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4985 if (aval != G_ALERTALTERNATE)
4992 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4996 if (compose_check_for_valid_recipient(compose) == FALSE) {
4997 if (compose->batch) {
4998 gtk_widget_show_all(compose->window);
5000 alertpanel_error(_("Recipient is not specified."));
5004 if (compose_check_for_set_recipients(compose) == FALSE) {
5008 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5009 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5010 if (*str == '\0' && check_everything == TRUE &&
5011 compose->mode != COMPOSE_REDIRECT) {
5013 gchar *button_label;
5016 if (compose->sending)
5017 button_label = _("+_Send");
5019 button_label = _("+_Queue");
5020 message = g_strdup_printf(_("Subject is empty. %s"),
5021 compose->sending?_("Send it anyway?"):
5022 _("Queue it anyway?"));
5024 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5025 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5026 ALERT_QUESTION, G_ALERTDEFAULT);
5028 if (aval & G_ALERTDISABLE) {
5029 aval &= ~G_ALERTDISABLE;
5030 prefs_common.warn_empty_subj = FALSE;
5032 if (aval != G_ALERTALTERNATE)
5037 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5043 gint compose_send(Compose *compose)
5046 FolderItem *folder = NULL;
5048 gchar *msgpath = NULL;
5049 gboolean discard_window = FALSE;
5050 gchar *errstr = NULL;
5051 gchar *tmsgid = NULL;
5052 MainWindow *mainwin = mainwindow_get_mainwindow();
5053 gboolean queued_removed = FALSE;
5055 if (prefs_common.send_dialog_invisible
5056 || compose->batch == TRUE)
5057 discard_window = TRUE;
5059 compose_allow_user_actions (compose, FALSE);
5060 compose->sending = TRUE;
5062 if (compose_check_entries(compose, TRUE) == FALSE) {
5063 if (compose->batch) {
5064 gtk_widget_show_all(compose->window);
5070 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5073 if (compose->batch) {
5074 gtk_widget_show_all(compose->window);
5077 alertpanel_error(_("Could not queue message for sending:\n\n"
5078 "Charset conversion failed."));
5079 } else if (val == -5) {
5080 alertpanel_error(_("Could not queue message for sending:\n\n"
5081 "Couldn't get recipient encryption key."));
5082 } else if (val == -6) {
5084 } else if (val == -3) {
5085 if (privacy_peek_error())
5086 alertpanel_error(_("Could not queue message for sending:\n\n"
5087 "Signature failed: %s"), privacy_get_error());
5088 } else if (val == -2 && errno != 0) {
5089 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5091 alertpanel_error(_("Could not queue message for sending."));
5096 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5097 if (discard_window) {
5098 compose->sending = FALSE;
5099 compose_close(compose);
5100 /* No more compose access in the normal codepath
5101 * after this point! */
5106 alertpanel_error(_("The message was queued but could not be "
5107 "sent.\nUse \"Send queued messages\" from "
5108 "the main window to retry."));
5109 if (!discard_window) {
5116 if (msgpath == NULL) {
5117 msgpath = folder_item_fetch_msg(folder, msgnum);
5118 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5121 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5122 claws_unlink(msgpath);
5125 if (!discard_window) {
5127 if (!queued_removed)
5128 folder_item_remove_msg(folder, msgnum);
5129 folder_item_scan(folder);
5131 /* make sure we delete that */
5132 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5134 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5135 folder_item_remove_msg(folder, tmp->msgnum);
5136 procmsg_msginfo_free(tmp);
5143 if (!queued_removed)
5144 folder_item_remove_msg(folder, msgnum);
5145 folder_item_scan(folder);
5147 /* make sure we delete that */
5148 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5150 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5151 folder_item_remove_msg(folder, tmp->msgnum);
5152 procmsg_msginfo_free(tmp);
5155 if (!discard_window) {
5156 compose->sending = FALSE;
5157 compose_allow_user_actions (compose, TRUE);
5158 compose_close(compose);
5162 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5163 "the main window to retry."), errstr);
5166 alertpanel_error_log(_("The message was queued but could not be "
5167 "sent.\nUse \"Send queued messages\" from "
5168 "the main window to retry."));
5170 if (!discard_window) {
5179 toolbar_main_set_sensitive(mainwin);
5180 main_window_set_menu_sensitive(mainwin);
5186 compose_allow_user_actions (compose, TRUE);
5187 compose->sending = FALSE;
5188 compose->modified = TRUE;
5189 toolbar_main_set_sensitive(mainwin);
5190 main_window_set_menu_sensitive(mainwin);
5195 static gboolean compose_use_attach(Compose *compose)
5197 GtkTreeModel *model = gtk_tree_view_get_model
5198 (GTK_TREE_VIEW(compose->attach_clist));
5199 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5202 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5205 gchar buf[BUFFSIZE];
5207 gboolean first_to_address;
5208 gboolean first_cc_address;
5210 ComposeHeaderEntry *headerentry;
5211 const gchar *headerentryname;
5212 const gchar *cc_hdr;
5213 const gchar *to_hdr;
5214 gboolean err = FALSE;
5216 debug_print("Writing redirect header\n");
5218 cc_hdr = prefs_common_translated_header_name("Cc:");
5219 to_hdr = prefs_common_translated_header_name("To:");
5221 first_to_address = TRUE;
5222 for (list = compose->header_list; list; list = list->next) {
5223 headerentry = ((ComposeHeaderEntry *)list->data);
5224 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5226 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5227 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5228 Xstrdup_a(str, entstr, return -1);
5230 if (str[0] != '\0') {
5231 compose_convert_header
5232 (compose, buf, sizeof(buf), str,
5233 strlen("Resent-To") + 2, TRUE);
5235 if (first_to_address) {
5236 err |= (fprintf(fp, "Resent-To: ") < 0);
5237 first_to_address = FALSE;
5239 err |= (fprintf(fp, ",") < 0);
5241 err |= (fprintf(fp, "%s", buf) < 0);
5245 if (!first_to_address) {
5246 err |= (fprintf(fp, "\n") < 0);
5249 first_cc_address = TRUE;
5250 for (list = compose->header_list; list; list = list->next) {
5251 headerentry = ((ComposeHeaderEntry *)list->data);
5252 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5254 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5255 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5256 Xstrdup_a(str, strg, return -1);
5258 if (str[0] != '\0') {
5259 compose_convert_header
5260 (compose, buf, sizeof(buf), str,
5261 strlen("Resent-Cc") + 2, TRUE);
5263 if (first_cc_address) {
5264 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5265 first_cc_address = FALSE;
5267 err |= (fprintf(fp, ",") < 0);
5269 err |= (fprintf(fp, "%s", buf) < 0);
5273 if (!first_cc_address) {
5274 err |= (fprintf(fp, "\n") < 0);
5277 return (err ? -1:0);
5280 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5282 gchar buf[BUFFSIZE];
5284 const gchar *entstr;
5285 /* struct utsname utsbuf; */
5286 gboolean err = FALSE;
5288 cm_return_val_if_fail(fp != NULL, -1);
5289 cm_return_val_if_fail(compose->account != NULL, -1);
5290 cm_return_val_if_fail(compose->account->address != NULL, -1);
5293 get_rfc822_date(buf, sizeof(buf));
5294 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5297 if (compose->account->name && *compose->account->name) {
5298 compose_convert_header
5299 (compose, buf, sizeof(buf), compose->account->name,
5300 strlen("From: "), TRUE);
5301 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5302 buf, compose->account->address) < 0);
5304 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5307 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5308 if (*entstr != '\0') {
5309 Xstrdup_a(str, entstr, return -1);
5312 compose_convert_header(compose, buf, sizeof(buf), str,
5313 strlen("Subject: "), FALSE);
5314 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5318 /* Resent-Message-ID */
5319 if (compose->account->set_domain && compose->account->domain) {
5320 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5321 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5322 g_snprintf(buf, sizeof(buf), "%s",
5323 strchr(compose->account->address, '@') ?
5324 strchr(compose->account->address, '@')+1 :
5325 compose->account->address);
5327 g_snprintf(buf, sizeof(buf), "%s", "");
5330 if (compose->account->gen_msgid) {
5332 if (compose->account->msgid_with_addr) {
5333 addr = compose->account->address;
5335 generate_msgid(buf, sizeof(buf), addr);
5336 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5337 compose->msgid = g_strdup(buf);
5339 compose->msgid = NULL;
5342 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5345 /* separator between header and body */
5346 err |= (fputs("\n", fp) == EOF);
5348 return (err ? -1:0);
5351 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5355 gchar buf[BUFFSIZE];
5357 gboolean skip = FALSE;
5358 gboolean err = FALSE;
5359 gchar *not_included[]={
5360 "Return-Path:", "Delivered-To:", "Received:",
5361 "Subject:", "X-UIDL:", "AF:",
5362 "NF:", "PS:", "SRH:",
5363 "SFN:", "DSR:", "MID:",
5364 "CFG:", "PT:", "S:",
5365 "RQ:", "SSV:", "NSV:",
5366 "SSH:", "R:", "MAID:",
5367 "NAID:", "RMID:", "FMID:",
5368 "SCF:", "RRCPT:", "NG:",
5369 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5370 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5371 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5372 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5373 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5376 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5377 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5381 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5383 for (i = 0; not_included[i] != NULL; i++) {
5384 if (g_ascii_strncasecmp(buf, not_included[i],
5385 strlen(not_included[i])) == 0) {
5392 if (fputs(buf, fdest) == -1)
5395 if (!prefs_common.redirect_keep_from) {
5396 if (g_ascii_strncasecmp(buf, "From:",
5397 strlen("From:")) == 0) {
5398 err |= (fputs(" (by way of ", fdest) == EOF);
5399 if (compose->account->name
5400 && *compose->account->name) {
5401 compose_convert_header
5402 (compose, buf, sizeof(buf),
5403 compose->account->name,
5406 err |= (fprintf(fdest, "%s <%s>",
5408 compose->account->address) < 0);
5410 err |= (fprintf(fdest, "%s",
5411 compose->account->address) < 0);
5412 err |= (fputs(")", fdest) == EOF);
5416 if (fputs("\n", fdest) == -1)
5423 if (compose_redirect_write_headers(compose, fdest))
5426 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5427 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5440 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5442 GtkTextBuffer *buffer;
5443 GtkTextIter start, end;
5446 const gchar *out_codeset;
5447 EncodingType encoding = ENC_UNKNOWN;
5448 MimeInfo *mimemsg, *mimetext;
5450 const gchar *src_codeset = CS_INTERNAL;
5451 gchar *from_addr = NULL;
5452 gchar *from_name = NULL;
5454 if (action == COMPOSE_WRITE_FOR_SEND)
5455 attach_parts = TRUE;
5457 /* create message MimeInfo */
5458 mimemsg = procmime_mimeinfo_new();
5459 mimemsg->type = MIMETYPE_MESSAGE;
5460 mimemsg->subtype = g_strdup("rfc822");
5461 mimemsg->content = MIMECONTENT_MEM;
5462 mimemsg->tmp = TRUE; /* must free content later */
5463 mimemsg->data.mem = compose_get_header(compose);
5465 /* Create text part MimeInfo */
5466 /* get all composed text */
5467 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5468 gtk_text_buffer_get_start_iter(buffer, &start);
5469 gtk_text_buffer_get_end_iter(buffer, &end);
5470 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5472 out_codeset = conv_get_charset_str(compose->out_encoding);
5474 if (!out_codeset && is_ascii_str(chars)) {
5475 out_codeset = CS_US_ASCII;
5476 } else if (prefs_common.outgoing_fallback_to_ascii &&
5477 is_ascii_str(chars)) {
5478 out_codeset = CS_US_ASCII;
5479 encoding = ENC_7BIT;
5483 gchar *test_conv_global_out = NULL;
5484 gchar *test_conv_reply = NULL;
5486 /* automatic mode. be automatic. */
5487 codeconv_set_strict(TRUE);
5489 out_codeset = conv_get_outgoing_charset_str();
5491 debug_print("trying to convert to %s\n", out_codeset);
5492 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5495 if (!test_conv_global_out && compose->orig_charset
5496 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5497 out_codeset = compose->orig_charset;
5498 debug_print("failure; trying to convert to %s\n", out_codeset);
5499 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5502 if (!test_conv_global_out && !test_conv_reply) {
5504 out_codeset = CS_INTERNAL;
5505 debug_print("failure; finally using %s\n", out_codeset);
5507 g_free(test_conv_global_out);
5508 g_free(test_conv_reply);
5509 codeconv_set_strict(FALSE);
5512 if (encoding == ENC_UNKNOWN) {
5513 if (prefs_common.encoding_method == CTE_BASE64)
5514 encoding = ENC_BASE64;
5515 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5516 encoding = ENC_QUOTED_PRINTABLE;
5517 else if (prefs_common.encoding_method == CTE_8BIT)
5518 encoding = ENC_8BIT;
5520 encoding = procmime_get_encoding_for_charset(out_codeset);
5523 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5524 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5526 if (action == COMPOSE_WRITE_FOR_SEND) {
5527 codeconv_set_strict(TRUE);
5528 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5529 codeconv_set_strict(FALSE);
5535 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5536 "to the specified %s charset.\n"
5537 "Send it as %s?"), out_codeset, src_codeset);
5538 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5539 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5542 if (aval != G_ALERTALTERNATE) {
5547 out_codeset = src_codeset;
5553 out_codeset = src_codeset;
5558 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5559 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5560 strstr(buf, "\nFrom ") != NULL) {
5561 encoding = ENC_QUOTED_PRINTABLE;
5565 mimetext = procmime_mimeinfo_new();
5566 mimetext->content = MIMECONTENT_MEM;
5567 mimetext->tmp = TRUE; /* must free content later */
5568 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5569 * and free the data, which we need later. */
5570 mimetext->data.mem = g_strdup(buf);
5571 mimetext->type = MIMETYPE_TEXT;
5572 mimetext->subtype = g_strdup("plain");
5573 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5574 g_strdup(out_codeset));
5576 /* protect trailing spaces when signing message */
5577 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5578 privacy_system_can_sign(compose->privacy_system)) {
5579 encoding = ENC_QUOTED_PRINTABLE;
5582 debug_print("main text: %zd bytes encoded as %s in %d\n",
5583 strlen(buf), out_codeset, encoding);
5585 /* check for line length limit */
5586 if (action == COMPOSE_WRITE_FOR_SEND &&
5587 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5588 check_line_length(buf, 1000, &line) < 0) {
5592 msg = g_strdup_printf
5593 (_("Line %d exceeds the line length limit (998 bytes).\n"
5594 "The contents of the message might be broken on the way to the delivery.\n"
5596 "Send it anyway?"), line + 1);
5597 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5599 if (aval != G_ALERTALTERNATE) {
5605 if (encoding != ENC_UNKNOWN)
5606 procmime_encode_content(mimetext, encoding);
5608 /* append attachment parts */
5609 if (compose_use_attach(compose) && attach_parts) {
5610 MimeInfo *mimempart;
5611 gchar *boundary = NULL;
5612 mimempart = procmime_mimeinfo_new();
5613 mimempart->content = MIMECONTENT_EMPTY;
5614 mimempart->type = MIMETYPE_MULTIPART;
5615 mimempart->subtype = g_strdup("mixed");
5619 boundary = generate_mime_boundary(NULL);
5620 } while (strstr(buf, boundary) != NULL);
5622 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5625 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5627 g_node_append(mimempart->node, mimetext->node);
5628 g_node_append(mimemsg->node, mimempart->node);
5630 if (compose_add_attachments(compose, mimempart) < 0)
5633 g_node_append(mimemsg->node, mimetext->node);
5637 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5638 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5639 /* extract name and address */
5640 if (strstr(spec, " <") && strstr(spec, ">")) {
5641 from_addr = g_strdup(strrchr(spec, '<')+1);
5642 *(strrchr(from_addr, '>')) = '\0';
5643 from_name = g_strdup(spec);
5644 *(strrchr(from_name, '<')) = '\0';
5651 /* sign message if sending */
5652 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5653 privacy_system_can_sign(compose->privacy_system))
5654 if (!privacy_sign(compose->privacy_system, mimemsg,
5655 compose->account, from_addr)) {
5662 procmime_write_mimeinfo(mimemsg, fp);
5664 procmime_mimeinfo_free_all(mimemsg);
5669 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5671 GtkTextBuffer *buffer;
5672 GtkTextIter start, end;
5677 if ((fp = g_fopen(file, "wb")) == NULL) {
5678 FILE_OP_ERROR(file, "fopen");
5682 /* chmod for security */
5683 if (change_file_mode_rw(fp, file) < 0) {
5684 FILE_OP_ERROR(file, "chmod");
5685 g_warning("can't change file mode\n");
5688 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5689 gtk_text_buffer_get_start_iter(buffer, &start);
5690 gtk_text_buffer_get_end_iter(buffer, &end);
5691 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5693 chars = conv_codeset_strdup
5694 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5697 if (!chars) return -1;
5700 len = strlen(chars);
5701 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5702 FILE_OP_ERROR(file, "fwrite");
5711 if (fclose(fp) == EOF) {
5712 FILE_OP_ERROR(file, "fclose");
5719 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5722 MsgInfo *msginfo = compose->targetinfo;
5724 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5725 if (!msginfo) return -1;
5727 if (!force && MSG_IS_LOCKED(msginfo->flags))
5730 item = msginfo->folder;
5731 cm_return_val_if_fail(item != NULL, -1);
5733 if (procmsg_msg_exist(msginfo) &&
5734 (folder_has_parent_of_type(item, F_QUEUE) ||
5735 folder_has_parent_of_type(item, F_DRAFT)
5736 || msginfo == compose->autosaved_draft)) {
5737 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5738 g_warning("can't remove the old message\n");
5741 debug_print("removed reedit target %d\n", msginfo->msgnum);
5748 static void compose_remove_draft(Compose *compose)
5751 MsgInfo *msginfo = compose->targetinfo;
5752 drafts = account_get_special_folder(compose->account, F_DRAFT);
5754 if (procmsg_msg_exist(msginfo)) {
5755 folder_item_remove_msg(drafts, msginfo->msgnum);
5760 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5761 gboolean remove_reedit_target)
5763 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5766 static gboolean compose_warn_encryption(Compose *compose)
5768 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5769 AlertValue val = G_ALERTALTERNATE;
5771 if (warning == NULL)
5774 val = alertpanel_full(_("Encryption warning"), warning,
5775 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5776 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5777 if (val & G_ALERTDISABLE) {
5778 val &= ~G_ALERTDISABLE;
5779 if (val == G_ALERTALTERNATE)
5780 privacy_inhibit_encrypt_warning(compose->privacy_system,
5784 if (val == G_ALERTALTERNATE) {
5791 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5792 gchar **msgpath, gboolean check_subject,
5793 gboolean remove_reedit_target)
5800 PrefsAccount *mailac = NULL, *newsac = NULL;
5801 gboolean err = FALSE;
5803 debug_print("queueing message...\n");
5804 cm_return_val_if_fail(compose->account != NULL, -1);
5806 if (compose_check_entries(compose, check_subject) == FALSE) {
5807 if (compose->batch) {
5808 gtk_widget_show_all(compose->window);
5813 if (!compose->to_list && !compose->newsgroup_list) {
5814 g_warning("can't get recipient list.");
5818 if (compose->to_list) {
5819 if (compose->account->protocol != A_NNTP)
5820 mailac = compose->account;
5821 else if (cur_account && cur_account->protocol != A_NNTP)
5822 mailac = cur_account;
5823 else if (!(mailac = compose_current_mail_account())) {
5824 alertpanel_error(_("No account for sending mails available!"));
5829 if (compose->newsgroup_list) {
5830 if (compose->account->protocol == A_NNTP)
5831 newsac = compose->account;
5833 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5838 /* write queue header */
5839 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5840 G_DIR_SEPARATOR, compose, (guint) rand());
5841 debug_print("queuing to %s\n", tmp);
5842 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5843 FILE_OP_ERROR(tmp, "fopen");
5848 if (change_file_mode_rw(fp, tmp) < 0) {
5849 FILE_OP_ERROR(tmp, "chmod");
5850 g_warning("can't change file mode\n");
5853 /* queueing variables */
5854 err |= (fprintf(fp, "AF:\n") < 0);
5855 err |= (fprintf(fp, "NF:0\n") < 0);
5856 err |= (fprintf(fp, "PS:10\n") < 0);
5857 err |= (fprintf(fp, "SRH:1\n") < 0);
5858 err |= (fprintf(fp, "SFN:\n") < 0);
5859 err |= (fprintf(fp, "DSR:\n") < 0);
5861 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5863 err |= (fprintf(fp, "MID:\n") < 0);
5864 err |= (fprintf(fp, "CFG:\n") < 0);
5865 err |= (fprintf(fp, "PT:0\n") < 0);
5866 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5867 err |= (fprintf(fp, "RQ:\n") < 0);
5869 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5871 err |= (fprintf(fp, "SSV:\n") < 0);
5873 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5875 err |= (fprintf(fp, "NSV:\n") < 0);
5876 err |= (fprintf(fp, "SSH:\n") < 0);
5877 /* write recepient list */
5878 if (compose->to_list) {
5879 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5880 for (cur = compose->to_list->next; cur != NULL;
5882 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5883 err |= (fprintf(fp, "\n") < 0);
5885 /* write newsgroup list */
5886 if (compose->newsgroup_list) {
5887 err |= (fprintf(fp, "NG:") < 0);
5888 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5889 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5890 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5891 err |= (fprintf(fp, "\n") < 0);
5893 /* Sylpheed account IDs */
5895 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5897 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5900 if (compose->privacy_system != NULL) {
5901 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5902 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5903 if (compose->use_encryption) {
5905 if (!compose_warn_encryption(compose)) {
5911 if (mailac && mailac->encrypt_to_self) {
5912 GSList *tmp_list = g_slist_copy(compose->to_list);
5913 tmp_list = g_slist_append(tmp_list, compose->account->address);
5914 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5915 g_slist_free(tmp_list);
5917 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5919 if (encdata != NULL) {
5920 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5921 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5922 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5924 } /* else we finally dont want to encrypt */
5926 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5927 /* and if encdata was null, it means there's been a problem in
5930 g_warning("failed to write queue message");
5940 /* Save copy folder */
5941 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5942 gchar *savefolderid;
5944 savefolderid = compose_get_save_to(compose);
5945 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5946 g_free(savefolderid);
5948 /* Save copy folder */
5949 if (compose->return_receipt) {
5950 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5952 /* Message-ID of message replying to */
5953 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5956 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5957 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5960 /* Message-ID of message forwarding to */
5961 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5964 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5965 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5969 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5970 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5972 /* end of headers */
5973 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5975 if (compose->redirect_filename != NULL) {
5976 if (compose_redirect_write_to_file(compose, fp) < 0) {
5984 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5988 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5992 g_warning("failed to write queue message\n");
5998 if (fclose(fp) == EOF) {
5999 FILE_OP_ERROR(tmp, "fclose");
6005 if (item && *item) {
6008 queue = account_get_special_folder(compose->account, F_QUEUE);
6011 g_warning("can't find queue folder\n");
6016 folder_item_scan(queue);
6017 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6018 g_warning("can't queue the message\n");
6024 if (msgpath == NULL) {
6030 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6031 compose_remove_reedit_target(compose, FALSE);
6034 if ((msgnum != NULL) && (item != NULL)) {
6042 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6045 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6047 struct stat statbuf;
6048 gchar *type, *subtype;
6049 GtkTreeModel *model;
6052 model = gtk_tree_view_get_model(tree_view);
6054 if (!gtk_tree_model_get_iter_first(model, &iter))
6057 gtk_tree_model_get(model, &iter,
6061 if (!is_file_exist(ainfo->file)) {
6062 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6063 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6064 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6066 if (val == G_ALERTDEFAULT) {
6071 mimepart = procmime_mimeinfo_new();
6072 mimepart->content = MIMECONTENT_FILE;
6073 mimepart->data.filename = g_strdup(ainfo->file);
6074 mimepart->tmp = FALSE; /* or we destroy our attachment */
6075 mimepart->offset = 0;
6077 g_stat(ainfo->file, &statbuf);
6078 mimepart->length = statbuf.st_size;
6080 type = g_strdup(ainfo->content_type);
6082 if (!strchr(type, '/')) {
6084 type = g_strdup("application/octet-stream");
6087 subtype = strchr(type, '/') + 1;
6088 *(subtype - 1) = '\0';
6089 mimepart->type = procmime_get_media_type(type);
6090 mimepart->subtype = g_strdup(subtype);
6093 if (mimepart->type == MIMETYPE_MESSAGE &&
6094 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6095 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6096 } else if (mimepart->type == MIMETYPE_TEXT) {
6097 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6098 /* Text parts with no name come from multipart/alternative
6099 * forwards. Make sure the recipient won't look at the
6100 * original HTML part by mistake. */
6101 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6102 ainfo->name = g_strdup_printf(_("Original %s part"),
6106 g_hash_table_insert(mimepart->typeparameters,
6107 g_strdup("charset"), g_strdup(ainfo->charset));
6109 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6110 if (mimepart->type == MIMETYPE_APPLICATION &&
6111 !strcmp2(mimepart->subtype, "octet-stream"))
6112 g_hash_table_insert(mimepart->typeparameters,
6113 g_strdup("name"), g_strdup(ainfo->name));
6114 g_hash_table_insert(mimepart->dispositionparameters,
6115 g_strdup("filename"), g_strdup(ainfo->name));
6116 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6119 if (mimepart->type == MIMETYPE_MESSAGE
6120 || mimepart->type == MIMETYPE_MULTIPART)
6121 ainfo->encoding = ENC_BINARY;
6122 else if (compose->use_signing) {
6123 if (ainfo->encoding == ENC_7BIT)
6124 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6125 else if (ainfo->encoding == ENC_8BIT)
6126 ainfo->encoding = ENC_BASE64;
6131 procmime_encode_content(mimepart, ainfo->encoding);
6133 g_node_append(parent->node, mimepart->node);
6134 } while (gtk_tree_model_iter_next(model, &iter));
6139 static gchar *compose_quote_list_of_addresses(gchar *str)
6141 GSList *list = NULL, *item = NULL;
6142 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6144 list = address_list_append_with_comments(list, str);
6145 for (item = list; item != NULL; item = item->next) {
6146 gchar *spec = item->data;
6147 gchar *endofname = strstr(spec, " <");
6148 if (endofname != NULL) {
6151 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6152 qqname = escape_internal_quotes(qname, '"');
6154 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6155 gchar *addr = g_strdup(endofname);
6156 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6157 faddr = g_strconcat(name, addr, NULL);
6160 debug_print("new auto-quoted address: '%s'", faddr);
6164 result = g_strdup((faddr != NULL)? faddr: spec);
6166 result = g_strconcat(result,
6168 (faddr != NULL)? faddr: spec,
6171 if (faddr != NULL) {
6176 slist_free_strings_full(list);
6181 #define IS_IN_CUSTOM_HEADER(header) \
6182 (compose->account->add_customhdr && \
6183 custom_header_find(compose->account->customhdr_list, header) != NULL)
6185 static void compose_add_headerfield_from_headerlist(Compose *compose,
6187 const gchar *fieldname,
6188 const gchar *seperator)
6190 gchar *str, *fieldname_w_colon;
6191 gboolean add_field = FALSE;
6193 ComposeHeaderEntry *headerentry;
6194 const gchar *headerentryname;
6195 const gchar *trans_fieldname;
6198 if (IS_IN_CUSTOM_HEADER(fieldname))
6201 debug_print("Adding %s-fields\n", fieldname);
6203 fieldstr = g_string_sized_new(64);
6205 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6206 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6208 for (list = compose->header_list; list; list = list->next) {
6209 headerentry = ((ComposeHeaderEntry *)list->data);
6210 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6212 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6213 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6215 str = compose_quote_list_of_addresses(ustr);
6217 if (str != NULL && str[0] != '\0') {
6219 g_string_append(fieldstr, seperator);
6220 g_string_append(fieldstr, str);
6229 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6230 compose_convert_header
6231 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6232 strlen(fieldname) + 2, TRUE);
6233 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6237 g_free(fieldname_w_colon);
6238 g_string_free(fieldstr, TRUE);
6243 static gchar *compose_get_manual_headers_info(Compose *compose)
6245 GString *sh_header = g_string_new(" ");
6247 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6249 for (list = compose->header_list; list; list = list->next) {
6250 ComposeHeaderEntry *headerentry;
6253 gchar *headername_wcolon;
6254 const gchar *headername_trans;
6256 gboolean standard_header = FALSE;
6258 headerentry = ((ComposeHeaderEntry *)list->data);
6260 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6262 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6267 if (!strstr(tmp, ":")) {
6268 headername_wcolon = g_strconcat(tmp, ":", NULL);
6269 headername = g_strdup(tmp);
6271 headername_wcolon = g_strdup(tmp);
6272 headername = g_strdup(strtok(tmp, ":"));
6276 string = std_headers;
6277 while (*string != NULL) {
6278 headername_trans = prefs_common_translated_header_name(*string);
6279 if (!strcmp(headername_trans, headername_wcolon))
6280 standard_header = TRUE;
6283 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6284 g_string_append_printf(sh_header, "%s ", headername);
6286 g_free(headername_wcolon);
6288 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6289 return g_string_free(sh_header, FALSE);
6292 static gchar *compose_get_header(Compose *compose)
6294 gchar buf[BUFFSIZE];
6295 const gchar *entry_str;
6299 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6301 gchar *from_name = NULL, *from_address = NULL;
6304 cm_return_val_if_fail(compose->account != NULL, NULL);
6305 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6307 header = g_string_sized_new(64);
6310 get_rfc822_date(buf, sizeof(buf));
6311 g_string_append_printf(header, "Date: %s\n", buf);
6315 if (compose->account->name && *compose->account->name) {
6317 QUOTE_IF_REQUIRED(buf, compose->account->name);
6318 tmp = g_strdup_printf("%s <%s>",
6319 buf, compose->account->address);
6321 tmp = g_strdup_printf("%s",
6322 compose->account->address);
6324 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6325 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6327 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6328 from_address = g_strdup(compose->account->address);
6330 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6331 /* extract name and address */
6332 if (strstr(spec, " <") && strstr(spec, ">")) {
6333 from_address = g_strdup(strrchr(spec, '<')+1);
6334 *(strrchr(from_address, '>')) = '\0';
6335 from_name = g_strdup(spec);
6336 *(strrchr(from_name, '<')) = '\0';
6339 from_address = g_strdup(spec);
6346 if (from_name && *from_name) {
6348 compose_convert_header
6349 (compose, buf, sizeof(buf), from_name,
6350 strlen("From: "), TRUE);
6351 QUOTE_IF_REQUIRED(name, buf);
6352 qname = escape_internal_quotes(name, '"');
6354 g_string_append_printf(header, "From: %s <%s>\n",
6355 qname, from_address);
6359 g_string_append_printf(header, "From: %s\n", from_address);
6362 g_free(from_address);
6365 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6368 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6371 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6375 * If this account is a NNTP account remove Bcc header from
6376 * message body since it otherwise will be publicly shown
6378 if (compose->account->protocol != A_NNTP)
6379 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6382 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6384 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6387 compose_convert_header(compose, buf, sizeof(buf), str,
6388 strlen("Subject: "), FALSE);
6389 g_string_append_printf(header, "Subject: %s\n", buf);
6395 if (compose->account->set_domain && compose->account->domain) {
6396 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6397 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6398 g_snprintf(buf, sizeof(buf), "%s",
6399 strchr(compose->account->address, '@') ?
6400 strchr(compose->account->address, '@')+1 :
6401 compose->account->address);
6403 g_snprintf(buf, sizeof(buf), "%s", "");
6406 if (compose->account->gen_msgid) {
6408 if (compose->account->msgid_with_addr) {
6409 addr = compose->account->address;
6411 generate_msgid(buf, sizeof(buf), addr);
6412 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6413 compose->msgid = g_strdup(buf);
6415 compose->msgid = NULL;
6418 if (compose->remove_references == FALSE) {
6420 if (compose->inreplyto && compose->to_list)
6421 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6424 if (compose->references)
6425 g_string_append_printf(header, "References: %s\n", compose->references);
6429 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6432 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6435 if (compose->account->organization &&
6436 strlen(compose->account->organization) &&
6437 !IS_IN_CUSTOM_HEADER("Organization")) {
6438 compose_convert_header(compose, buf, sizeof(buf),
6439 compose->account->organization,
6440 strlen("Organization: "), FALSE);
6441 g_string_append_printf(header, "Organization: %s\n", buf);
6444 /* Program version and system info */
6445 if (compose->account->gen_xmailer &&
6446 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6447 !compose->newsgroup_list) {
6448 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6450 gtk_major_version, gtk_minor_version, gtk_micro_version,
6453 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6454 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6456 gtk_major_version, gtk_minor_version, gtk_micro_version,
6460 /* custom headers */
6461 if (compose->account->add_customhdr) {
6464 for (cur = compose->account->customhdr_list; cur != NULL;
6466 CustomHeader *chdr = (CustomHeader *)cur->data;
6468 if (custom_header_is_allowed(chdr->name)
6469 && chdr->value != NULL
6470 && *(chdr->value) != '\0') {
6471 compose_convert_header
6472 (compose, buf, sizeof(buf),
6474 strlen(chdr->name) + 2, FALSE);
6475 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6480 /* Automatic Faces and X-Faces */
6481 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6482 g_string_append_printf(header, "X-Face: %s\n", buf);
6484 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6485 g_string_append_printf(header, "X-Face: %s\n", buf);
6487 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6488 g_string_append_printf(header, "Face: %s\n", buf);
6490 else if (get_default_face (buf, sizeof(buf)) == 0) {
6491 g_string_append_printf(header, "Face: %s\n", buf);
6495 switch (compose->priority) {
6496 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6497 "X-Priority: 1 (Highest)\n");
6499 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6500 "X-Priority: 2 (High)\n");
6502 case PRIORITY_NORMAL: break;
6503 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6504 "X-Priority: 4 (Low)\n");
6506 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6507 "X-Priority: 5 (Lowest)\n");
6509 default: debug_print("compose: priority unknown : %d\n",
6513 /* Request Return Receipt */
6514 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6515 if (compose->return_receipt) {
6516 if (compose->account->name
6517 && *compose->account->name) {
6518 compose_convert_header(compose, buf, sizeof(buf),
6519 compose->account->name,
6520 strlen("Disposition-Notification-To: "),
6522 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6524 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6528 /* get special headers */
6529 for (list = compose->header_list; list; list = list->next) {
6530 ComposeHeaderEntry *headerentry;
6533 gchar *headername_wcolon;
6534 const gchar *headername_trans;
6537 gboolean standard_header = FALSE;
6539 headerentry = ((ComposeHeaderEntry *)list->data);
6541 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6543 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6548 if (!strstr(tmp, ":")) {
6549 headername_wcolon = g_strconcat(tmp, ":", NULL);
6550 headername = g_strdup(tmp);
6552 headername_wcolon = g_strdup(tmp);
6553 headername = g_strdup(strtok(tmp, ":"));
6557 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6558 Xstrdup_a(headervalue, entry_str, return NULL);
6559 subst_char(headervalue, '\r', ' ');
6560 subst_char(headervalue, '\n', ' ');
6561 string = std_headers;
6562 while (*string != NULL) {
6563 headername_trans = prefs_common_translated_header_name(*string);
6564 if (!strcmp(headername_trans, headername_wcolon))
6565 standard_header = TRUE;
6568 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6569 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6572 g_free(headername_wcolon);
6576 g_string_free(header, FALSE);
6581 #undef IS_IN_CUSTOM_HEADER
6583 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6584 gint header_len, gboolean addr_field)
6586 gchar *tmpstr = NULL;
6587 const gchar *out_codeset = NULL;
6589 cm_return_if_fail(src != NULL);
6590 cm_return_if_fail(dest != NULL);
6592 if (len < 1) return;
6594 tmpstr = g_strdup(src);
6596 subst_char(tmpstr, '\n', ' ');
6597 subst_char(tmpstr, '\r', ' ');
6600 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6601 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6602 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6607 codeconv_set_strict(TRUE);
6608 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6609 conv_get_charset_str(compose->out_encoding));
6610 codeconv_set_strict(FALSE);
6612 if (!dest || *dest == '\0') {
6613 gchar *test_conv_global_out = NULL;
6614 gchar *test_conv_reply = NULL;
6616 /* automatic mode. be automatic. */
6617 codeconv_set_strict(TRUE);
6619 out_codeset = conv_get_outgoing_charset_str();
6621 debug_print("trying to convert to %s\n", out_codeset);
6622 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6625 if (!test_conv_global_out && compose->orig_charset
6626 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6627 out_codeset = compose->orig_charset;
6628 debug_print("failure; trying to convert to %s\n", out_codeset);
6629 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6632 if (!test_conv_global_out && !test_conv_reply) {
6634 out_codeset = CS_INTERNAL;
6635 debug_print("finally using %s\n", out_codeset);
6637 g_free(test_conv_global_out);
6638 g_free(test_conv_reply);
6639 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6641 codeconv_set_strict(FALSE);
6646 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6650 cm_return_if_fail(user_data != NULL);
6652 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6653 g_strstrip(address);
6654 if (*address != '\0') {
6655 gchar *name = procheader_get_fromname(address);
6656 extract_address(address);
6657 #ifndef USE_NEW_ADDRBOOK
6658 addressbook_add_contact(name, address, NULL, NULL);
6660 debug_print("%s: %s\n", name, address);
6661 if (addressadd_selection(name, address, NULL, NULL)) {
6662 debug_print( "addressbook_add_contact - added\n" );
6669 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6671 GtkWidget *menuitem;
6674 cm_return_if_fail(menu != NULL);
6675 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6677 menuitem = gtk_separator_menu_item_new();
6678 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6679 gtk_widget_show(menuitem);
6681 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6682 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6684 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6685 g_strstrip(address);
6686 if (*address == '\0') {
6687 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6690 g_signal_connect(G_OBJECT(menuitem), "activate",
6691 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6692 gtk_widget_show(menuitem);
6695 void compose_add_extra_header(gchar *header, GtkListStore *model)
6698 if (strcmp(header, "")) {
6699 COMBOBOX_ADD(model, header, COMPOSE_TO);
6703 void compose_add_extra_header_entries(GtkListStore *model)
6707 gchar buf[BUFFSIZE];
6710 if (extra_headers == NULL) {
6711 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6712 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6713 debug_print("extra headers file not found\n");
6714 goto extra_headers_done;
6716 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6717 lastc = strlen(buf) - 1; /* remove trailing control chars */
6718 while (lastc >= 0 && buf[lastc] != ':')
6719 buf[lastc--] = '\0';
6720 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6721 buf[lastc] = '\0'; /* remove trailing : for comparison */
6722 if (custom_header_is_allowed(buf)) {
6724 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6727 g_message("disallowed extra header line: %s\n", buf);
6731 g_message("invalid extra header line: %s\n", buf);
6737 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6738 extra_headers = g_slist_reverse(extra_headers);
6740 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6743 static void compose_create_header_entry(Compose *compose)
6745 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6752 const gchar *header = NULL;
6753 ComposeHeaderEntry *headerentry;
6754 gboolean standard_header = FALSE;
6755 GtkListStore *model;
6757 #if !(GTK_CHECK_VERSION(2,12,0))
6758 GtkTooltips *tips = compose->tooltips;
6761 headerentry = g_new0(ComposeHeaderEntry, 1);
6763 /* Combo box model */
6764 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6765 #if !GTK_CHECK_VERSION(2, 24, 0)
6766 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6768 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6770 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6772 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6774 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6775 COMPOSE_NEWSGROUPS);
6776 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6778 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6779 COMPOSE_FOLLOWUPTO);
6780 compose_add_extra_header_entries(model);
6783 #if GTK_CHECK_VERSION(2, 24, 0)
6784 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6785 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6786 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6787 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6788 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6790 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6791 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6792 G_CALLBACK(compose_grab_focus_cb), compose);
6793 gtk_widget_show(combo);
6796 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6797 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6800 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6801 compose->header_nextrow, compose->header_nextrow+1,
6802 GTK_SHRINK, GTK_FILL, 0, 0);
6803 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6804 const gchar *last_header_entry = gtk_entry_get_text(
6805 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6807 while (*string != NULL) {
6808 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6809 standard_header = TRUE;
6812 if (standard_header)
6813 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6815 if (!compose->header_last || !standard_header) {
6816 switch(compose->account->protocol) {
6818 header = prefs_common_translated_header_name("Newsgroups:");
6821 header = prefs_common_translated_header_name("To:");
6826 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6828 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6829 G_CALLBACK(compose_grab_focus_cb), compose);
6831 /* Entry field with cleanup button */
6832 button = gtk_button_new();
6833 gtk_button_set_image(GTK_BUTTON(button),
6834 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6835 gtk_widget_show(button);
6836 CLAWS_SET_TIP(button,
6837 _("Delete entry contents"));
6838 entry = gtk_entry_new();
6839 gtk_widget_show(entry);
6840 CLAWS_SET_TIP(entry,
6841 _("Use <tab> to autocomplete from addressbook"));
6842 hbox = gtk_hbox_new (FALSE, 0);
6843 gtk_widget_show(hbox);
6844 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6845 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6846 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6847 compose->header_nextrow, compose->header_nextrow+1,
6848 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6850 g_signal_connect(G_OBJECT(entry), "key-press-event",
6851 G_CALLBACK(compose_headerentry_key_press_event_cb),
6853 g_signal_connect(G_OBJECT(entry), "changed",
6854 G_CALLBACK(compose_headerentry_changed_cb),
6856 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6857 G_CALLBACK(compose_grab_focus_cb), compose);
6859 g_signal_connect(G_OBJECT(button), "clicked",
6860 G_CALLBACK(compose_headerentry_button_clicked_cb),
6864 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6865 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6866 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6867 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6868 G_CALLBACK(compose_header_drag_received_cb),
6870 g_signal_connect(G_OBJECT(entry), "drag-drop",
6871 G_CALLBACK(compose_drag_drop),
6873 g_signal_connect(G_OBJECT(entry), "populate-popup",
6874 G_CALLBACK(compose_entry_popup_extend),
6877 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6879 headerentry->compose = compose;
6880 headerentry->combo = combo;
6881 headerentry->entry = entry;
6882 headerentry->button = button;
6883 headerentry->hbox = hbox;
6884 headerentry->headernum = compose->header_nextrow;
6885 headerentry->type = PREF_NONE;
6887 compose->header_nextrow++;
6888 compose->header_last = headerentry;
6889 compose->header_list =
6890 g_slist_append(compose->header_list,
6894 static void compose_add_header_entry(Compose *compose, const gchar *header,
6895 gchar *text, ComposePrefType pref_type)
6897 ComposeHeaderEntry *last_header = compose->header_last;
6898 gchar *tmp = g_strdup(text), *email;
6899 gboolean replyto_hdr;
6901 replyto_hdr = (!strcasecmp(header,
6902 prefs_common_translated_header_name("Reply-To:")) ||
6904 prefs_common_translated_header_name("Followup-To:")) ||
6906 prefs_common_translated_header_name("In-Reply-To:")));
6908 extract_address(tmp);
6909 email = g_utf8_strdown(tmp, -1);
6911 if (replyto_hdr == FALSE &&
6912 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6914 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6915 header, text, (gint) pref_type);
6921 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6922 gtk_entry_set_text(GTK_ENTRY(
6923 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6925 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6926 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6927 last_header->type = pref_type;
6929 if (replyto_hdr == FALSE)
6930 g_hash_table_insert(compose->email_hashtable, email,
6931 GUINT_TO_POINTER(1));
6938 static void compose_destroy_headerentry(Compose *compose,
6939 ComposeHeaderEntry *headerentry)
6941 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6944 extract_address(text);
6945 email = g_utf8_strdown(text, -1);
6946 g_hash_table_remove(compose->email_hashtable, email);
6950 gtk_widget_destroy(headerentry->combo);
6951 gtk_widget_destroy(headerentry->entry);
6952 gtk_widget_destroy(headerentry->button);
6953 gtk_widget_destroy(headerentry->hbox);
6954 g_free(headerentry);
6957 static void compose_remove_header_entries(Compose *compose)
6960 for (list = compose->header_list; list; list = list->next)
6961 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6963 compose->header_last = NULL;
6964 g_slist_free(compose->header_list);
6965 compose->header_list = NULL;
6966 compose->header_nextrow = 1;
6967 compose_create_header_entry(compose);
6970 static GtkWidget *compose_create_header(Compose *compose)
6972 GtkWidget *from_optmenu_hbox;
6973 GtkWidget *header_scrolledwin_main;
6974 GtkWidget *header_table_main;
6975 GtkWidget *header_scrolledwin;
6976 GtkWidget *header_table;
6978 /* parent with account selection and from header */
6979 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6980 gtk_widget_show(header_scrolledwin_main);
6981 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6983 header_table_main = gtk_table_new(2, 2, FALSE);
6984 gtk_widget_show(header_table_main);
6985 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6986 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6987 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6989 from_optmenu_hbox = compose_account_option_menu_create(compose);
6990 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6991 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6993 /* child with header labels and entries */
6994 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6995 gtk_widget_show(header_scrolledwin);
6996 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6998 header_table = gtk_table_new(2, 2, FALSE);
6999 gtk_widget_show(header_table);
7000 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7001 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7002 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7004 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7005 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7007 compose->header_table = header_table;
7008 compose->header_list = NULL;
7009 compose->header_nextrow = 0;
7011 compose_create_header_entry(compose);
7013 compose->table = NULL;
7015 return header_scrolledwin_main;
7018 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7020 Compose *compose = (Compose *)data;
7021 GdkEventButton event;
7024 event.time = gtk_get_current_event_time();
7026 return attach_button_pressed(compose->attach_clist, &event, compose);
7029 static GtkWidget *compose_create_attach(Compose *compose)
7031 GtkWidget *attach_scrwin;
7032 GtkWidget *attach_clist;
7034 GtkListStore *store;
7035 GtkCellRenderer *renderer;
7036 GtkTreeViewColumn *column;
7037 GtkTreeSelection *selection;
7039 /* attachment list */
7040 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7041 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7042 GTK_POLICY_AUTOMATIC,
7043 GTK_POLICY_AUTOMATIC);
7044 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7046 store = gtk_list_store_new(N_ATTACH_COLS,
7052 G_TYPE_AUTO_POINTER,
7054 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7055 (GTK_TREE_MODEL(store)));
7056 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7057 g_object_unref(store);
7059 renderer = gtk_cell_renderer_text_new();
7060 column = gtk_tree_view_column_new_with_attributes
7061 (_("Mime type"), renderer, "text",
7062 COL_MIMETYPE, NULL);
7063 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7065 renderer = gtk_cell_renderer_text_new();
7066 column = gtk_tree_view_column_new_with_attributes
7067 (_("Size"), renderer, "text",
7069 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7071 renderer = gtk_cell_renderer_text_new();
7072 column = gtk_tree_view_column_new_with_attributes
7073 (_("Name"), renderer, "text",
7075 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7077 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7078 prefs_common.use_stripes_everywhere);
7079 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7080 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7082 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7083 G_CALLBACK(attach_selected), compose);
7084 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7085 G_CALLBACK(attach_button_pressed), compose);
7086 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7087 G_CALLBACK(popup_attach_button_pressed), compose);
7088 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7089 G_CALLBACK(attach_key_pressed), compose);
7092 gtk_drag_dest_set(attach_clist,
7093 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7094 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7095 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7096 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7097 G_CALLBACK(compose_attach_drag_received_cb),
7099 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7100 G_CALLBACK(compose_drag_drop),
7103 compose->attach_scrwin = attach_scrwin;
7104 compose->attach_clist = attach_clist;
7106 return attach_scrwin;
7109 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7110 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7112 static GtkWidget *compose_create_others(Compose *compose)
7115 GtkWidget *savemsg_checkbtn;
7116 GtkWidget *savemsg_combo;
7117 GtkWidget *savemsg_select;
7120 gchar *folderidentifier;
7122 /* Table for settings */
7123 table = gtk_table_new(3, 1, FALSE);
7124 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7125 gtk_widget_show(table);
7126 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7129 /* Save Message to folder */
7130 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7131 gtk_widget_show(savemsg_checkbtn);
7132 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7133 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7134 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7136 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7137 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7139 #if !GTK_CHECK_VERSION(2, 24, 0)
7140 savemsg_combo = gtk_combo_box_entry_new_text();
7142 savemsg_combo = gtk_combo_box_text_new_with_entry();
7144 compose->savemsg_checkbtn = savemsg_checkbtn;
7145 compose->savemsg_combo = savemsg_combo;
7146 gtk_widget_show(savemsg_combo);
7148 if (prefs_common.compose_save_to_history)
7149 #if !GTK_CHECK_VERSION(2, 24, 0)
7150 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7151 prefs_common.compose_save_to_history);
7153 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7154 prefs_common.compose_save_to_history);
7156 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7157 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7158 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7159 G_CALLBACK(compose_grab_focus_cb), compose);
7160 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7161 folderidentifier = folder_item_get_identifier(account_get_special_folder
7162 (compose->account, F_OUTBOX));
7163 compose_set_save_to(compose, folderidentifier);
7164 g_free(folderidentifier);
7167 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7168 gtk_widget_show(savemsg_select);
7169 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7170 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7171 G_CALLBACK(compose_savemsg_select_cb),
7177 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7179 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7180 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7183 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7188 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7191 path = folder_item_get_identifier(dest);
7193 compose_set_save_to(compose, path);
7197 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7198 GdkAtom clip, GtkTextIter *insert_place);
7201 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7205 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7207 if (event->button == 3) {
7209 GtkTextIter sel_start, sel_end;
7210 gboolean stuff_selected;
7212 /* move the cursor to allow GtkAspell to check the word
7213 * under the mouse */
7214 if (event->x && event->y) {
7215 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7216 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7218 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7221 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7222 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7225 stuff_selected = gtk_text_buffer_get_selection_bounds(
7227 &sel_start, &sel_end);
7229 gtk_text_buffer_place_cursor (buffer, &iter);
7230 /* reselect stuff */
7232 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7233 gtk_text_buffer_select_range(buffer,
7234 &sel_start, &sel_end);
7236 return FALSE; /* pass the event so that the right-click goes through */
7239 if (event->button == 2) {
7244 /* get the middle-click position to paste at the correct place */
7245 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7246 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7248 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7251 entry_paste_clipboard(compose, text,
7252 prefs_common.linewrap_pastes,
7253 GDK_SELECTION_PRIMARY, &iter);
7261 static void compose_spell_menu_changed(void *data)
7263 Compose *compose = (Compose *)data;
7265 GtkWidget *menuitem;
7266 GtkWidget *parent_item;
7267 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7270 if (compose->gtkaspell == NULL)
7273 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7274 "/Menu/Spelling/Options");
7276 /* setting the submenu removes /Spelling/Options from the factory
7277 * so we need to save it */
7279 if (parent_item == NULL) {
7280 parent_item = compose->aspell_options_menu;
7281 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7283 compose->aspell_options_menu = parent_item;
7285 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7287 spell_menu = g_slist_reverse(spell_menu);
7288 for (items = spell_menu;
7289 items; items = items->next) {
7290 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7291 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7292 gtk_widget_show(GTK_WIDGET(menuitem));
7294 g_slist_free(spell_menu);
7296 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7297 gtk_widget_show(parent_item);
7300 static void compose_dict_changed(void *data)
7302 Compose *compose = (Compose *) data;
7304 if(compose->gtkaspell &&
7305 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7308 gtkaspell_highlight_all(compose->gtkaspell);
7309 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7313 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7315 Compose *compose = (Compose *)data;
7316 GdkEventButton event;
7319 event.time = gtk_get_current_event_time();
7323 return text_clicked(compose->text, &event, compose);
7326 static gboolean compose_force_window_origin = TRUE;
7327 static Compose *compose_create(PrefsAccount *account,
7336 GtkWidget *handlebox;
7338 GtkWidget *notebook;
7340 GtkWidget *attach_hbox;
7341 GtkWidget *attach_lab1;
7342 GtkWidget *attach_lab2;
7347 GtkWidget *subject_hbox;
7348 GtkWidget *subject_frame;
7349 GtkWidget *subject_entry;
7353 GtkWidget *edit_vbox;
7354 GtkWidget *ruler_hbox;
7356 GtkWidget *scrolledwin;
7358 GtkTextBuffer *buffer;
7359 GtkClipboard *clipboard;
7361 UndoMain *undostruct;
7363 GtkWidget *popupmenu;
7364 GtkWidget *tmpl_menu;
7365 GtkActionGroup *action_group = NULL;
7368 GtkAspell * gtkaspell = NULL;
7371 static GdkGeometry geometry;
7373 cm_return_val_if_fail(account != NULL, NULL);
7375 debug_print("Creating compose window...\n");
7376 compose = g_new0(Compose, 1);
7378 compose->batch = batch;
7379 compose->account = account;
7380 compose->folder = folder;
7382 compose->mutex = cm_mutex_new();
7383 compose->set_cursor_pos = -1;
7385 #if !(GTK_CHECK_VERSION(2,12,0))
7386 compose->tooltips = tips;
7389 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7391 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7392 gtk_widget_set_size_request(window, prefs_common.compose_width,
7393 prefs_common.compose_height);
7395 if (!geometry.max_width) {
7396 geometry.max_width = gdk_screen_width();
7397 geometry.max_height = gdk_screen_height();
7400 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7401 &geometry, GDK_HINT_MAX_SIZE);
7402 if (!geometry.min_width) {
7403 geometry.min_width = 600;
7404 geometry.min_height = 440;
7406 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7407 &geometry, GDK_HINT_MIN_SIZE);
7409 #ifndef GENERIC_UMPC
7410 if (compose_force_window_origin)
7411 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7412 prefs_common.compose_y);
7414 g_signal_connect(G_OBJECT(window), "delete_event",
7415 G_CALLBACK(compose_delete_cb), compose);
7416 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7417 gtk_widget_realize(window);
7419 gtkut_widget_set_composer_icon(window);
7421 vbox = gtk_vbox_new(FALSE, 0);
7422 gtk_container_add(GTK_CONTAINER(window), vbox);
7424 compose->ui_manager = gtk_ui_manager_new();
7425 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7426 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7427 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7428 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7429 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7430 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7431 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7432 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7433 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7434 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7438 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7439 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7444 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7445 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7451 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7452 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7453 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7481 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7484 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7489 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7492 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7497 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7498 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7499 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7500 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7553 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)
7554 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)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7560 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)
7561 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)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7566 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)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7570 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)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7576 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)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7579 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7582 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)
7583 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)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7596 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)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7614 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7615 gtk_widget_show_all(menubar);
7617 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7618 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7620 if (prefs_common.toolbar_detachable) {
7621 handlebox = gtk_handle_box_new();
7623 handlebox = gtk_hbox_new(FALSE, 0);
7625 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7627 gtk_widget_realize(handlebox);
7628 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7631 vbox2 = gtk_vbox_new(FALSE, 2);
7632 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7633 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7636 notebook = gtk_notebook_new();
7637 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7638 gtk_widget_show(notebook);
7640 /* header labels and entries */
7641 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7642 compose_create_header(compose),
7643 gtk_label_new_with_mnemonic(_("Hea_der")));
7644 /* attachment list */
7645 attach_hbox = gtk_hbox_new(FALSE, 0);
7646 gtk_widget_show(attach_hbox);
7648 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7649 gtk_widget_show(attach_lab1);
7650 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7652 attach_lab2 = gtk_label_new("");
7653 gtk_widget_show(attach_lab2);
7654 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7656 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7657 compose_create_attach(compose),
7660 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7661 compose_create_others(compose),
7662 gtk_label_new_with_mnemonic(_("Othe_rs")));
7665 subject_hbox = gtk_hbox_new(FALSE, 0);
7666 gtk_widget_show(subject_hbox);
7668 subject_frame = gtk_frame_new(NULL);
7669 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7670 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7671 gtk_widget_show(subject_frame);
7673 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7674 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7675 gtk_widget_show(subject);
7677 label = gtk_label_new(_("Subject:"));
7678 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7679 gtk_widget_show(label);
7682 subject_entry = claws_spell_entry_new();
7684 subject_entry = gtk_entry_new();
7686 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7687 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7688 G_CALLBACK(compose_grab_focus_cb), compose);
7689 gtk_widget_show(subject_entry);
7690 compose->subject_entry = subject_entry;
7691 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7693 edit_vbox = gtk_vbox_new(FALSE, 0);
7695 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7698 ruler_hbox = gtk_hbox_new(FALSE, 0);
7699 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7701 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7702 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7703 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7707 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7708 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7709 GTK_POLICY_AUTOMATIC,
7710 GTK_POLICY_AUTOMATIC);
7711 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7713 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7715 text = gtk_text_view_new();
7716 if (prefs_common.show_compose_margin) {
7717 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7718 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7720 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7721 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7722 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7723 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7724 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7726 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7727 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7728 G_CALLBACK(compose_notebook_size_alloc), compose);
7729 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7730 G_CALLBACK(compose_edit_size_alloc),
7732 g_signal_connect(G_OBJECT(buffer), "changed",
7733 G_CALLBACK(compose_changed_cb), compose);
7734 g_signal_connect(G_OBJECT(text), "grab_focus",
7735 G_CALLBACK(compose_grab_focus_cb), compose);
7736 g_signal_connect(G_OBJECT(buffer), "insert_text",
7737 G_CALLBACK(text_inserted), compose);
7738 g_signal_connect(G_OBJECT(text), "button_press_event",
7739 G_CALLBACK(text_clicked), compose);
7740 g_signal_connect(G_OBJECT(text), "popup-menu",
7741 G_CALLBACK(compose_popup_menu), compose);
7742 g_signal_connect(G_OBJECT(subject_entry), "changed",
7743 G_CALLBACK(compose_changed_cb), compose);
7744 g_signal_connect(G_OBJECT(subject_entry), "activate",
7745 G_CALLBACK(compose_subject_entry_activated), compose);
7748 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7749 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7750 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7751 g_signal_connect(G_OBJECT(text), "drag_data_received",
7752 G_CALLBACK(compose_insert_drag_received_cb),
7754 g_signal_connect(G_OBJECT(text), "drag-drop",
7755 G_CALLBACK(compose_drag_drop),
7757 g_signal_connect(G_OBJECT(text), "key-press-event",
7758 G_CALLBACK(completion_set_focus_to_subject),
7760 gtk_widget_show_all(vbox);
7762 /* pane between attach clist and text */
7763 paned = gtk_vpaned_new();
7764 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7765 gtk_paned_add1(GTK_PANED(paned), notebook);
7766 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7767 gtk_widget_show_all(paned);
7770 if (prefs_common.textfont) {
7771 PangoFontDescription *font_desc;
7773 font_desc = pango_font_description_from_string
7774 (prefs_common.textfont);
7776 gtk_widget_modify_font(text, font_desc);
7777 pango_font_description_free(font_desc);
7781 gtk_action_group_add_actions(action_group, compose_popup_entries,
7782 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7783 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7784 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7785 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7786 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7787 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7788 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7790 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7792 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7793 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7794 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7796 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7798 undostruct = undo_init(text);
7799 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7802 address_completion_start(window);
7804 compose->window = window;
7805 compose->vbox = vbox;
7806 compose->menubar = menubar;
7807 compose->handlebox = handlebox;
7809 compose->vbox2 = vbox2;
7811 compose->paned = paned;
7813 compose->attach_label = attach_lab2;
7815 compose->notebook = notebook;
7816 compose->edit_vbox = edit_vbox;
7817 compose->ruler_hbox = ruler_hbox;
7818 compose->ruler = ruler;
7819 compose->scrolledwin = scrolledwin;
7820 compose->text = text;
7822 compose->focused_editable = NULL;
7824 compose->popupmenu = popupmenu;
7826 compose->tmpl_menu = tmpl_menu;
7828 compose->mode = mode;
7829 compose->rmode = mode;
7831 compose->targetinfo = NULL;
7832 compose->replyinfo = NULL;
7833 compose->fwdinfo = NULL;
7835 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7836 g_str_equal, (GDestroyNotify) g_free, NULL);
7838 compose->replyto = NULL;
7840 compose->bcc = NULL;
7841 compose->followup_to = NULL;
7843 compose->ml_post = NULL;
7845 compose->inreplyto = NULL;
7846 compose->references = NULL;
7847 compose->msgid = NULL;
7848 compose->boundary = NULL;
7850 compose->autowrap = prefs_common.autowrap;
7851 compose->autoindent = prefs_common.auto_indent;
7852 compose->use_signing = FALSE;
7853 compose->use_encryption = FALSE;
7854 compose->privacy_system = NULL;
7856 compose->modified = FALSE;
7858 compose->return_receipt = FALSE;
7860 compose->to_list = NULL;
7861 compose->newsgroup_list = NULL;
7863 compose->undostruct = undostruct;
7865 compose->sig_str = NULL;
7867 compose->exteditor_file = NULL;
7868 compose->exteditor_pid = -1;
7869 compose->exteditor_tag = -1;
7870 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7873 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7874 if (mode != COMPOSE_REDIRECT) {
7875 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7876 strcmp(prefs_common.dictionary, "")) {
7877 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7878 prefs_common.alt_dictionary,
7879 conv_get_locale_charset_str(),
7880 prefs_common.misspelled_col,
7881 prefs_common.check_while_typing,
7882 prefs_common.recheck_when_changing_dict,
7883 prefs_common.use_alternate,
7884 prefs_common.use_both_dicts,
7885 GTK_TEXT_VIEW(text),
7886 GTK_WINDOW(compose->window),
7887 compose_dict_changed,
7888 compose_spell_menu_changed,
7891 alertpanel_error(_("Spell checker could not "
7893 gtkaspell_checkers_strerror());
7894 gtkaspell_checkers_reset_error();
7896 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7900 compose->gtkaspell = gtkaspell;
7901 compose_spell_menu_changed(compose);
7902 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7905 compose_select_account(compose, account, TRUE);
7907 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7908 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7910 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7911 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7913 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7914 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7916 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7917 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7920 if (account->protocol != A_NNTP)
7921 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7922 prefs_common_translated_header_name("To:"));
7924 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7925 prefs_common_translated_header_name("Newsgroups:"));
7927 #ifndef USE_NEW_ADDRBOOK
7928 addressbook_set_target_compose(compose);
7930 if (mode != COMPOSE_REDIRECT)
7931 compose_set_template_menu(compose);
7933 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7936 compose_list = g_list_append(compose_list, compose);
7938 if (!prefs_common.show_ruler)
7939 gtk_widget_hide(ruler_hbox);
7941 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7944 compose->priority = PRIORITY_NORMAL;
7945 compose_update_priority_menu_item(compose);
7947 compose_set_out_encoding(compose);
7950 compose_update_actions_menu(compose);
7952 /* Privacy Systems menu */
7953 compose_update_privacy_systems_menu(compose);
7955 activate_privacy_system(compose, account, TRUE);
7956 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7958 gtk_widget_realize(window);
7960 gtk_widget_show(window);
7966 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7971 GtkWidget *optmenubox;
7974 GtkWidget *from_name = NULL;
7975 #if !(GTK_CHECK_VERSION(2,12,0))
7976 GtkTooltips *tips = compose->tooltips;
7979 gint num = 0, def_menu = 0;
7981 accounts = account_get_list();
7982 cm_return_val_if_fail(accounts != NULL, NULL);
7984 optmenubox = gtk_event_box_new();
7985 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7986 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7988 hbox = gtk_hbox_new(FALSE, 6);
7989 from_name = gtk_entry_new();
7991 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7992 G_CALLBACK(compose_grab_focus_cb), compose);
7994 for (; accounts != NULL; accounts = accounts->next, num++) {
7995 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7996 gchar *name, *from = NULL;
7998 if (ac == compose->account) def_menu = num;
8000 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8003 if (ac == compose->account) {
8004 if (ac->name && *ac->name) {
8006 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8007 from = g_strdup_printf("%s <%s>",
8009 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8011 from = g_strdup_printf("%s",
8013 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8016 COMBOBOX_ADD(menu, name, ac->account_id);
8021 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8023 g_signal_connect(G_OBJECT(optmenu), "changed",
8024 G_CALLBACK(account_activated),
8026 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8027 G_CALLBACK(compose_entry_popup_extend),
8030 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8031 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8033 CLAWS_SET_TIP(optmenubox,
8034 _("Account to use for this email"));
8035 CLAWS_SET_TIP(from_name,
8036 _("Sender address to be used"));
8038 compose->account_combo = optmenu;
8039 compose->from_name = from_name;
8044 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8046 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8047 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8048 Compose *compose = (Compose *) data;
8050 compose->priority = value;
8054 static void compose_reply_change_mode(Compose *compose,
8057 gboolean was_modified = compose->modified;
8059 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8061 cm_return_if_fail(compose->replyinfo != NULL);
8063 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8065 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8067 if (action == COMPOSE_REPLY_TO_ALL)
8069 if (action == COMPOSE_REPLY_TO_SENDER)
8071 if (action == COMPOSE_REPLY_TO_LIST)
8074 compose_remove_header_entries(compose);
8075 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8076 if (compose->account->set_autocc && compose->account->auto_cc)
8077 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8079 if (compose->account->set_autobcc && compose->account->auto_bcc)
8080 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8082 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8083 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8084 compose_show_first_last_header(compose, TRUE);
8085 compose->modified = was_modified;
8086 compose_set_title(compose);
8089 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8091 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8092 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8093 Compose *compose = (Compose *) data;
8096 compose_reply_change_mode(compose, value);
8099 static void compose_update_priority_menu_item(Compose * compose)
8101 GtkWidget *menuitem = NULL;
8102 switch (compose->priority) {
8103 case PRIORITY_HIGHEST:
8104 menuitem = gtk_ui_manager_get_widget
8105 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8108 menuitem = gtk_ui_manager_get_widget
8109 (compose->ui_manager, "/Menu/Options/Priority/High");
8111 case PRIORITY_NORMAL:
8112 menuitem = gtk_ui_manager_get_widget
8113 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8116 menuitem = gtk_ui_manager_get_widget
8117 (compose->ui_manager, "/Menu/Options/Priority/Low");
8119 case PRIORITY_LOWEST:
8120 menuitem = gtk_ui_manager_get_widget
8121 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8124 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8127 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8129 Compose *compose = (Compose *) data;
8131 gboolean can_sign = FALSE, can_encrypt = FALSE;
8133 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8135 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8138 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8139 g_free(compose->privacy_system);
8140 compose->privacy_system = NULL;
8141 if (systemid != NULL) {
8142 compose->privacy_system = g_strdup(systemid);
8144 can_sign = privacy_system_can_sign(systemid);
8145 can_encrypt = privacy_system_can_encrypt(systemid);
8148 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8150 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8151 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8154 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8156 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8157 GtkWidget *menuitem = NULL;
8158 GList *children, *amenu;
8159 gboolean can_sign = FALSE, can_encrypt = FALSE;
8160 gboolean found = FALSE;
8162 if (compose->privacy_system != NULL) {
8164 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8165 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8166 cm_return_if_fail(menuitem != NULL);
8168 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8171 while (amenu != NULL) {
8172 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8173 if (systemid != NULL) {
8174 if (strcmp(systemid, compose->privacy_system) == 0 &&
8175 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8176 menuitem = GTK_WIDGET(amenu->data);
8178 can_sign = privacy_system_can_sign(systemid);
8179 can_encrypt = privacy_system_can_encrypt(systemid);
8183 } else if (strlen(compose->privacy_system) == 0 &&
8184 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8185 menuitem = GTK_WIDGET(amenu->data);
8188 can_encrypt = FALSE;
8193 amenu = amenu->next;
8195 g_list_free(children);
8196 if (menuitem != NULL)
8197 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8199 if (warn && !found && strlen(compose->privacy_system)) {
8200 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8201 "will not be able to sign or encrypt this message."),
8202 compose->privacy_system);
8206 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8207 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8210 static void compose_set_out_encoding(Compose *compose)
8212 CharSet out_encoding;
8213 const gchar *branch = NULL;
8214 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8216 switch(out_encoding) {
8217 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8218 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8219 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8220 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8221 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8222 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8223 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8224 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8225 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8226 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8227 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8228 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8229 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8230 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8231 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8232 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8233 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8234 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8235 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8236 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8237 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8238 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8239 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8240 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8241 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8242 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8243 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8244 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8245 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8246 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8247 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8248 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8249 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8251 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8254 static void compose_set_template_menu(Compose *compose)
8256 GSList *tmpl_list, *cur;
8260 tmpl_list = template_get_config();
8262 menu = gtk_menu_new();
8264 gtk_menu_set_accel_group (GTK_MENU (menu),
8265 gtk_ui_manager_get_accel_group(compose->ui_manager));
8266 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8267 Template *tmpl = (Template *)cur->data;
8268 gchar *accel_path = NULL;
8269 item = gtk_menu_item_new_with_label(tmpl->name);
8270 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8271 g_signal_connect(G_OBJECT(item), "activate",
8272 G_CALLBACK(compose_template_activate_cb),
8274 g_object_set_data(G_OBJECT(item), "template", tmpl);
8275 gtk_widget_show(item);
8276 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8277 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8281 gtk_widget_show(menu);
8282 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8285 void compose_update_actions_menu(Compose *compose)
8287 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8290 static void compose_update_privacy_systems_menu(Compose *compose)
8292 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8293 GSList *systems, *cur;
8295 GtkWidget *system_none;
8297 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8298 GtkWidget *privacy_menu = gtk_menu_new();
8300 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8301 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8303 g_signal_connect(G_OBJECT(system_none), "activate",
8304 G_CALLBACK(compose_set_privacy_system_cb), compose);
8306 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8307 gtk_widget_show(system_none);
8309 systems = privacy_get_system_ids();
8310 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8311 gchar *systemid = cur->data;
8313 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8314 widget = gtk_radio_menu_item_new_with_label(group,
8315 privacy_system_get_name(systemid));
8316 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8317 g_strdup(systemid), g_free);
8318 g_signal_connect(G_OBJECT(widget), "activate",
8319 G_CALLBACK(compose_set_privacy_system_cb), compose);
8321 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8322 gtk_widget_show(widget);
8325 g_slist_free(systems);
8326 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8327 gtk_widget_show_all(privacy_menu);
8328 gtk_widget_show_all(privacy_menuitem);
8331 void compose_reflect_prefs_all(void)
8336 for (cur = compose_list; cur != NULL; cur = cur->next) {
8337 compose = (Compose *)cur->data;
8338 compose_set_template_menu(compose);
8342 void compose_reflect_prefs_pixmap_theme(void)
8347 for (cur = compose_list; cur != NULL; cur = cur->next) {
8348 compose = (Compose *)cur->data;
8349 toolbar_update(TOOLBAR_COMPOSE, compose);
8353 static const gchar *compose_quote_char_from_context(Compose *compose)
8355 const gchar *qmark = NULL;
8357 cm_return_val_if_fail(compose != NULL, NULL);
8359 switch (compose->mode) {
8360 /* use forward-specific quote char */
8361 case COMPOSE_FORWARD:
8362 case COMPOSE_FORWARD_AS_ATTACH:
8363 case COMPOSE_FORWARD_INLINE:
8364 if (compose->folder && compose->folder->prefs &&
8365 compose->folder->prefs->forward_with_format)
8366 qmark = compose->folder->prefs->forward_quotemark;
8367 else if (compose->account->forward_with_format)
8368 qmark = compose->account->forward_quotemark;
8370 qmark = prefs_common.fw_quotemark;
8373 /* use reply-specific quote char in all other modes */
8375 if (compose->folder && compose->folder->prefs &&
8376 compose->folder->prefs->reply_with_format)
8377 qmark = compose->folder->prefs->reply_quotemark;
8378 else if (compose->account->reply_with_format)
8379 qmark = compose->account->reply_quotemark;
8381 qmark = prefs_common.quotemark;
8385 if (qmark == NULL || *qmark == '\0')
8391 static void compose_template_apply(Compose *compose, Template *tmpl,
8395 GtkTextBuffer *buffer;
8399 gchar *parsed_str = NULL;
8400 gint cursor_pos = 0;
8401 const gchar *err_msg = _("The body of the template has an error at line %d.");
8404 /* process the body */
8406 text = GTK_TEXT_VIEW(compose->text);
8407 buffer = gtk_text_view_get_buffer(text);
8410 qmark = compose_quote_char_from_context(compose);
8412 if (compose->replyinfo != NULL) {
8415 gtk_text_buffer_set_text(buffer, "", -1);
8416 mark = gtk_text_buffer_get_insert(buffer);
8417 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8419 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8420 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8422 } else if (compose->fwdinfo != NULL) {
8425 gtk_text_buffer_set_text(buffer, "", -1);
8426 mark = gtk_text_buffer_get_insert(buffer);
8427 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8429 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8430 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8433 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8435 GtkTextIter start, end;
8438 gtk_text_buffer_get_start_iter(buffer, &start);
8439 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8440 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8442 /* clear the buffer now */
8444 gtk_text_buffer_set_text(buffer, "", -1);
8446 parsed_str = compose_quote_fmt(compose, dummyinfo,
8447 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8448 procmsg_msginfo_free( dummyinfo );
8454 gtk_text_buffer_set_text(buffer, "", -1);
8455 mark = gtk_text_buffer_get_insert(buffer);
8456 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8459 if (replace && parsed_str && compose->account->auto_sig)
8460 compose_insert_sig(compose, FALSE);
8462 if (replace && parsed_str) {
8463 gtk_text_buffer_get_start_iter(buffer, &iter);
8464 gtk_text_buffer_place_cursor(buffer, &iter);
8468 cursor_pos = quote_fmt_get_cursor_pos();
8469 compose->set_cursor_pos = cursor_pos;
8470 if (cursor_pos == -1)
8472 gtk_text_buffer_get_start_iter(buffer, &iter);
8473 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8474 gtk_text_buffer_place_cursor(buffer, &iter);
8477 /* process the other fields */
8479 compose_template_apply_fields(compose, tmpl);
8480 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8481 quote_fmt_reset_vartable();
8482 compose_changed_cb(NULL, compose);
8485 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8486 gtkaspell_highlight_all(compose->gtkaspell);
8490 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8492 MsgInfo* dummyinfo = NULL;
8493 MsgInfo *msginfo = NULL;
8496 if (compose->replyinfo != NULL)
8497 msginfo = compose->replyinfo;
8498 else if (compose->fwdinfo != NULL)
8499 msginfo = compose->fwdinfo;
8501 dummyinfo = compose_msginfo_new_from_compose(compose);
8502 msginfo = dummyinfo;
8505 if (tmpl->from && *tmpl->from != '\0') {
8507 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8508 compose->gtkaspell);
8510 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8512 quote_fmt_scan_string(tmpl->from);
8515 buf = quote_fmt_get_buffer();
8517 alertpanel_error(_("Template From format error."));
8519 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8523 if (tmpl->to && *tmpl->to != '\0') {
8525 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8526 compose->gtkaspell);
8528 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8530 quote_fmt_scan_string(tmpl->to);
8533 buf = quote_fmt_get_buffer();
8535 alertpanel_error(_("Template To format error."));
8537 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8541 if (tmpl->cc && *tmpl->cc != '\0') {
8543 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8544 compose->gtkaspell);
8546 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8548 quote_fmt_scan_string(tmpl->cc);
8551 buf = quote_fmt_get_buffer();
8553 alertpanel_error(_("Template Cc format error."));
8555 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8559 if (tmpl->bcc && *tmpl->bcc != '\0') {
8561 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8562 compose->gtkaspell);
8564 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8566 quote_fmt_scan_string(tmpl->bcc);
8569 buf = quote_fmt_get_buffer();
8571 alertpanel_error(_("Template Bcc format error."));
8573 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8577 /* process the subject */
8578 if (tmpl->subject && *tmpl->subject != '\0') {
8580 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8581 compose->gtkaspell);
8583 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8585 quote_fmt_scan_string(tmpl->subject);
8588 buf = quote_fmt_get_buffer();
8590 alertpanel_error(_("Template subject format error."));
8592 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8596 procmsg_msginfo_free( dummyinfo );
8599 static void compose_destroy(Compose *compose)
8601 GtkAllocation allocation;
8602 GtkTextBuffer *buffer;
8603 GtkClipboard *clipboard;
8605 compose_list = g_list_remove(compose_list, compose);
8607 if (compose->updating) {
8608 debug_print("danger, not destroying anything now\n");
8609 compose->deferred_destroy = TRUE;
8612 /* NOTE: address_completion_end() does nothing with the window
8613 * however this may change. */
8614 address_completion_end(compose->window);
8616 slist_free_strings_full(compose->to_list);
8617 slist_free_strings_full(compose->newsgroup_list);
8618 slist_free_strings_full(compose->header_list);
8620 slist_free_strings_full(extra_headers);
8621 extra_headers = NULL;
8623 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8625 g_hash_table_destroy(compose->email_hashtable);
8627 procmsg_msginfo_free(compose->targetinfo);
8628 procmsg_msginfo_free(compose->replyinfo);
8629 procmsg_msginfo_free(compose->fwdinfo);
8631 g_free(compose->replyto);
8632 g_free(compose->cc);
8633 g_free(compose->bcc);
8634 g_free(compose->newsgroups);
8635 g_free(compose->followup_to);
8637 g_free(compose->ml_post);
8639 g_free(compose->inreplyto);
8640 g_free(compose->references);
8641 g_free(compose->msgid);
8642 g_free(compose->boundary);
8644 g_free(compose->redirect_filename);
8645 if (compose->undostruct)
8646 undo_destroy(compose->undostruct);
8648 g_free(compose->sig_str);
8650 g_free(compose->exteditor_file);
8652 g_free(compose->orig_charset);
8654 g_free(compose->privacy_system);
8656 #ifndef USE_NEW_ADDRBOOK
8657 if (addressbook_get_target_compose() == compose)
8658 addressbook_set_target_compose(NULL);
8661 if (compose->gtkaspell) {
8662 gtkaspell_delete(compose->gtkaspell);
8663 compose->gtkaspell = NULL;
8667 if (!compose->batch) {
8668 gtk_widget_get_allocation(compose->window, &allocation);
8669 prefs_common.compose_width = allocation.width;
8670 prefs_common.compose_height = allocation.height;
8673 if (!gtk_widget_get_parent(compose->paned))
8674 gtk_widget_destroy(compose->paned);
8675 gtk_widget_destroy(compose->popupmenu);
8677 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8678 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8679 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8681 gtk_widget_destroy(compose->window);
8682 toolbar_destroy(compose->toolbar);
8683 g_free(compose->toolbar);
8684 cm_mutex_free(compose->mutex);
8688 static void compose_attach_info_free(AttachInfo *ainfo)
8690 g_free(ainfo->file);
8691 g_free(ainfo->content_type);
8692 g_free(ainfo->name);
8693 g_free(ainfo->charset);
8697 static void compose_attach_update_label(Compose *compose)
8702 GtkTreeModel *model;
8707 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8708 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8709 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8713 while(gtk_tree_model_iter_next(model, &iter))
8716 text = g_strdup_printf("(%d)", i);
8717 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8721 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8723 Compose *compose = (Compose *)data;
8724 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8725 GtkTreeSelection *selection;
8727 GtkTreeModel *model;
8729 selection = gtk_tree_view_get_selection(tree_view);
8730 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8735 for (cur = sel; cur != NULL; cur = cur->next) {
8736 GtkTreePath *path = cur->data;
8737 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8740 gtk_tree_path_free(path);
8743 for (cur = sel; cur != NULL; cur = cur->next) {
8744 GtkTreeRowReference *ref = cur->data;
8745 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8748 if (gtk_tree_model_get_iter(model, &iter, path))
8749 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8751 gtk_tree_path_free(path);
8752 gtk_tree_row_reference_free(ref);
8756 compose_attach_update_label(compose);
8759 static struct _AttachProperty
8762 GtkWidget *mimetype_entry;
8763 GtkWidget *encoding_optmenu;
8764 GtkWidget *path_entry;
8765 GtkWidget *filename_entry;
8767 GtkWidget *cancel_btn;
8770 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8772 gtk_tree_path_free((GtkTreePath *)ptr);
8775 static void compose_attach_property(GtkAction *action, gpointer data)
8777 Compose *compose = (Compose *)data;
8778 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8780 GtkComboBox *optmenu;
8781 GtkTreeSelection *selection;
8783 GtkTreeModel *model;
8786 static gboolean cancelled;
8788 /* only if one selected */
8789 selection = gtk_tree_view_get_selection(tree_view);
8790 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8793 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8797 path = (GtkTreePath *) sel->data;
8798 gtk_tree_model_get_iter(model, &iter, path);
8799 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8802 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8808 if (!attach_prop.window)
8809 compose_attach_property_create(&cancelled);
8810 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8811 gtk_widget_grab_focus(attach_prop.ok_btn);
8812 gtk_widget_show(attach_prop.window);
8813 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8814 GTK_WINDOW(compose->window));
8816 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8817 if (ainfo->encoding == ENC_UNKNOWN)
8818 combobox_select_by_data(optmenu, ENC_BASE64);
8820 combobox_select_by_data(optmenu, ainfo->encoding);
8822 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8823 ainfo->content_type ? ainfo->content_type : "");
8824 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8825 ainfo->file ? ainfo->file : "");
8826 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8827 ainfo->name ? ainfo->name : "");
8830 const gchar *entry_text;
8832 gchar *cnttype = NULL;
8839 gtk_widget_hide(attach_prop.window);
8840 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8845 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8846 if (*entry_text != '\0') {
8849 text = g_strstrip(g_strdup(entry_text));
8850 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8851 cnttype = g_strdup(text);
8854 alertpanel_error(_("Invalid MIME type."));
8860 ainfo->encoding = combobox_get_active_data(optmenu);
8862 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8863 if (*entry_text != '\0') {
8864 if (is_file_exist(entry_text) &&
8865 (size = get_file_size(entry_text)) > 0)
8866 file = g_strdup(entry_text);
8869 (_("File doesn't exist or is empty."));
8875 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8876 if (*entry_text != '\0') {
8877 g_free(ainfo->name);
8878 ainfo->name = g_strdup(entry_text);
8882 g_free(ainfo->content_type);
8883 ainfo->content_type = cnttype;
8886 g_free(ainfo->file);
8890 ainfo->size = (goffset)size;
8892 /* update tree store */
8893 text = to_human_readable(ainfo->size);
8894 gtk_tree_model_get_iter(model, &iter, path);
8895 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8896 COL_MIMETYPE, ainfo->content_type,
8898 COL_NAME, ainfo->name,
8899 COL_CHARSET, ainfo->charset,
8905 gtk_tree_path_free(path);
8908 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8910 label = gtk_label_new(str); \
8911 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8912 GTK_FILL, 0, 0, 0); \
8913 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8915 entry = gtk_entry_new(); \
8916 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8917 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8920 static void compose_attach_property_create(gboolean *cancelled)
8926 GtkWidget *mimetype_entry;
8929 GtkListStore *optmenu_menu;
8930 GtkWidget *path_entry;
8931 GtkWidget *filename_entry;
8934 GtkWidget *cancel_btn;
8935 GList *mime_type_list, *strlist;
8938 debug_print("Creating attach_property window...\n");
8940 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8941 gtk_widget_set_size_request(window, 480, -1);
8942 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8943 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8944 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8945 g_signal_connect(G_OBJECT(window), "delete_event",
8946 G_CALLBACK(attach_property_delete_event),
8948 g_signal_connect(G_OBJECT(window), "key_press_event",
8949 G_CALLBACK(attach_property_key_pressed),
8952 vbox = gtk_vbox_new(FALSE, 8);
8953 gtk_container_add(GTK_CONTAINER(window), vbox);
8955 table = gtk_table_new(4, 2, FALSE);
8956 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8957 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8958 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8960 label = gtk_label_new(_("MIME type"));
8961 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8963 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8964 #if !GTK_CHECK_VERSION(2, 24, 0)
8965 mimetype_entry = gtk_combo_box_entry_new_text();
8967 mimetype_entry = gtk_combo_box_text_new_with_entry();
8969 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8970 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8972 /* stuff with list */
8973 mime_type_list = procmime_get_mime_type_list();
8975 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8976 MimeType *type = (MimeType *) mime_type_list->data;
8979 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8981 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8984 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8985 (GCompareFunc)strcmp2);
8988 for (mime_type_list = strlist; mime_type_list != NULL;
8989 mime_type_list = mime_type_list->next) {
8990 #if !GTK_CHECK_VERSION(2, 24, 0)
8991 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8993 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8995 g_free(mime_type_list->data);
8997 g_list_free(strlist);
8998 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8999 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9001 label = gtk_label_new(_("Encoding"));
9002 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9004 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9006 hbox = gtk_hbox_new(FALSE, 0);
9007 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9008 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9010 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9011 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9013 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9014 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9015 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9016 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9017 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9019 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9021 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9022 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9024 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9025 &ok_btn, GTK_STOCK_OK,
9027 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9028 gtk_widget_grab_default(ok_btn);
9030 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9031 G_CALLBACK(attach_property_ok),
9033 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9034 G_CALLBACK(attach_property_cancel),
9037 gtk_widget_show_all(vbox);
9039 attach_prop.window = window;
9040 attach_prop.mimetype_entry = mimetype_entry;
9041 attach_prop.encoding_optmenu = optmenu;
9042 attach_prop.path_entry = path_entry;
9043 attach_prop.filename_entry = filename_entry;
9044 attach_prop.ok_btn = ok_btn;
9045 attach_prop.cancel_btn = cancel_btn;
9048 #undef SET_LABEL_AND_ENTRY
9050 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9056 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9062 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9063 gboolean *cancelled)
9071 static gboolean attach_property_key_pressed(GtkWidget *widget,
9073 gboolean *cancelled)
9075 if (event && event->keyval == GDK_KEY_Escape) {
9079 if (event && event->keyval == GDK_KEY_Return) {
9087 static void compose_exec_ext_editor(Compose *compose)
9094 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9095 G_DIR_SEPARATOR, compose);
9097 if (pipe(pipe_fds) < 0) {
9103 if ((pid = fork()) < 0) {
9110 /* close the write side of the pipe */
9113 compose->exteditor_file = g_strdup(tmp);
9114 compose->exteditor_pid = pid;
9116 compose_set_ext_editor_sensitive(compose, FALSE);
9119 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9121 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9123 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9127 } else { /* process-monitoring process */
9133 /* close the read side of the pipe */
9136 if (compose_write_body_to_file(compose, tmp) < 0) {
9137 fd_write_all(pipe_fds[1], "2\n", 2);
9141 pid_ed = compose_exec_ext_editor_real(tmp);
9143 fd_write_all(pipe_fds[1], "1\n", 2);
9147 /* wait until editor is terminated */
9148 waitpid(pid_ed, NULL, 0);
9150 fd_write_all(pipe_fds[1], "0\n", 2);
9157 #endif /* G_OS_UNIX */
9161 static gint compose_exec_ext_editor_real(const gchar *file)
9168 cm_return_val_if_fail(file != NULL, -1);
9170 if ((pid = fork()) < 0) {
9175 if (pid != 0) return pid;
9177 /* grandchild process */
9179 if (setpgid(0, getppid()))
9182 if (prefs_common_get_ext_editor_cmd() &&
9183 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9184 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9185 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9187 if (prefs_common_get_ext_editor_cmd())
9188 g_warning("External editor command-line is invalid: '%s'\n",
9189 prefs_common_get_ext_editor_cmd());
9190 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9193 cmdline = strsplit_with_quote(buf, " ", 1024);
9194 execvp(cmdline[0], cmdline);
9197 g_strfreev(cmdline);
9202 static gboolean compose_ext_editor_kill(Compose *compose)
9204 pid_t pgid = compose->exteditor_pid * -1;
9207 ret = kill(pgid, 0);
9209 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9213 msg = g_strdup_printf
9214 (_("The external editor is still working.\n"
9215 "Force terminating the process?\n"
9216 "process group id: %d"), -pgid);
9217 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9218 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9222 if (val == G_ALERTALTERNATE) {
9223 g_source_remove(compose->exteditor_tag);
9224 g_io_channel_shutdown(compose->exteditor_ch,
9226 g_io_channel_unref(compose->exteditor_ch);
9228 if (kill(pgid, SIGTERM) < 0) perror("kill");
9229 waitpid(compose->exteditor_pid, NULL, 0);
9231 g_warning("Terminated process group id: %d", -pgid);
9232 g_warning("Temporary file: %s",
9233 compose->exteditor_file);
9235 compose_set_ext_editor_sensitive(compose, TRUE);
9237 g_free(compose->exteditor_file);
9238 compose->exteditor_file = NULL;
9239 compose->exteditor_pid = -1;
9240 compose->exteditor_ch = NULL;
9241 compose->exteditor_tag = -1;
9249 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9253 Compose *compose = (Compose *)data;
9256 debug_print("Compose: input from monitoring process\n");
9258 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9260 g_io_channel_shutdown(source, FALSE, NULL);
9261 g_io_channel_unref(source);
9263 waitpid(compose->exteditor_pid, NULL, 0);
9265 if (buf[0] == '0') { /* success */
9266 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9267 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9269 gtk_text_buffer_set_text(buffer, "", -1);
9270 compose_insert_file(compose, compose->exteditor_file);
9271 compose_changed_cb(NULL, compose);
9272 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9274 if (claws_unlink(compose->exteditor_file) < 0)
9275 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9276 } else if (buf[0] == '1') { /* failed */
9277 g_warning("Couldn't exec external editor\n");
9278 if (claws_unlink(compose->exteditor_file) < 0)
9279 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9280 } else if (buf[0] == '2') {
9281 g_warning("Couldn't write to file\n");
9282 } else if (buf[0] == '3') {
9283 g_warning("Pipe read failed\n");
9286 compose_set_ext_editor_sensitive(compose, TRUE);
9288 g_free(compose->exteditor_file);
9289 compose->exteditor_file = NULL;
9290 compose->exteditor_pid = -1;
9291 compose->exteditor_ch = NULL;
9292 compose->exteditor_tag = -1;
9297 static void compose_set_ext_editor_sensitive(Compose *compose,
9300 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9301 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9302 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9303 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9304 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9305 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9306 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9307 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9309 gtk_widget_set_sensitive(compose->text, sensitive);
9310 if (compose->toolbar->send_btn)
9311 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9312 if (compose->toolbar->sendl_btn)
9313 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9314 if (compose->toolbar->draft_btn)
9315 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9316 if (compose->toolbar->insert_btn)
9317 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9318 if (compose->toolbar->sig_btn)
9319 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9320 if (compose->toolbar->exteditor_btn)
9321 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9322 if (compose->toolbar->linewrap_current_btn)
9323 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9324 if (compose->toolbar->linewrap_all_btn)
9325 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9327 #endif /* G_OS_UNIX */
9330 * compose_undo_state_changed:
9332 * Change the sensivity of the menuentries undo and redo
9334 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9335 gint redo_state, gpointer data)
9337 Compose *compose = (Compose *)data;
9339 switch (undo_state) {
9340 case UNDO_STATE_TRUE:
9341 if (!undostruct->undo_state) {
9342 undostruct->undo_state = TRUE;
9343 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9346 case UNDO_STATE_FALSE:
9347 if (undostruct->undo_state) {
9348 undostruct->undo_state = FALSE;
9349 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9352 case UNDO_STATE_UNCHANGED:
9354 case UNDO_STATE_REFRESH:
9355 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9358 g_warning("Undo state not recognized");
9362 switch (redo_state) {
9363 case UNDO_STATE_TRUE:
9364 if (!undostruct->redo_state) {
9365 undostruct->redo_state = TRUE;
9366 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9369 case UNDO_STATE_FALSE:
9370 if (undostruct->redo_state) {
9371 undostruct->redo_state = FALSE;
9372 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9375 case UNDO_STATE_UNCHANGED:
9377 case UNDO_STATE_REFRESH:
9378 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9381 g_warning("Redo state not recognized");
9386 /* callback functions */
9388 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9389 GtkAllocation *allocation,
9392 prefs_common.compose_notebook_height = allocation->height;
9395 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9396 * includes "non-client" (windows-izm) in calculation, so this calculation
9397 * may not be accurate.
9399 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9400 GtkAllocation *allocation,
9401 GtkSHRuler *shruler)
9403 if (prefs_common.show_ruler) {
9404 gint char_width = 0, char_height = 0;
9405 gint line_width_in_chars;
9407 gtkut_get_font_size(GTK_WIDGET(widget),
9408 &char_width, &char_height);
9409 line_width_in_chars =
9410 (allocation->width - allocation->x) / char_width;
9412 /* got the maximum */
9413 gtk_shruler_set_range(GTK_SHRULER(shruler),
9414 0.0, line_width_in_chars, 0);
9423 ComposePrefType type;
9424 gboolean entry_marked;
9427 static void account_activated(GtkComboBox *optmenu, gpointer data)
9429 Compose *compose = (Compose *)data;
9432 gchar *folderidentifier;
9433 gint account_id = 0;
9436 GSList *list, *saved_list = NULL;
9437 HeaderEntryState *state;
9438 GtkRcStyle *style = NULL;
9439 #if !GTK_CHECK_VERSION(3, 0, 0)
9440 static GdkColor yellow;
9441 static gboolean color_set = FALSE;
9443 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9446 /* Get ID of active account in the combo box */
9447 menu = gtk_combo_box_get_model(optmenu);
9448 gtk_combo_box_get_active_iter(optmenu, &iter);
9449 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9451 ac = account_find_from_id(account_id);
9452 cm_return_if_fail(ac != NULL);
9454 if (ac != compose->account) {
9455 compose_select_account(compose, ac, FALSE);
9457 for (list = compose->header_list; list; list = list->next) {
9458 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9460 if (hentry->type == PREF_ACCOUNT || !list->next) {
9461 compose_destroy_headerentry(compose, hentry);
9465 state = g_malloc0(sizeof(HeaderEntryState));
9466 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9467 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9468 state->entry = gtk_editable_get_chars(
9469 GTK_EDITABLE(hentry->entry), 0, -1);
9470 state->type = hentry->type;
9472 #if !GTK_CHECK_VERSION(3, 0, 0)
9474 gdk_color_parse("#f5f6be", &yellow);
9475 color_set = gdk_colormap_alloc_color(
9476 gdk_colormap_get_system(),
9477 &yellow, FALSE, TRUE);
9481 style = gtk_widget_get_modifier_style(hentry->entry);
9482 state->entry_marked = gdk_color_equal(&yellow,
9483 &style->base[GTK_STATE_NORMAL]);
9485 saved_list = g_slist_append(saved_list, state);
9486 compose_destroy_headerentry(compose, hentry);
9489 compose->header_last = NULL;
9490 g_slist_free(compose->header_list);
9491 compose->header_list = NULL;
9492 compose->header_nextrow = 1;
9493 compose_create_header_entry(compose);
9495 if (ac->set_autocc && ac->auto_cc)
9496 compose_entry_append(compose, ac->auto_cc,
9497 COMPOSE_CC, PREF_ACCOUNT);
9499 if (ac->set_autobcc && ac->auto_bcc)
9500 compose_entry_append(compose, ac->auto_bcc,
9501 COMPOSE_BCC, PREF_ACCOUNT);
9503 if (ac->set_autoreplyto && ac->auto_replyto)
9504 compose_entry_append(compose, ac->auto_replyto,
9505 COMPOSE_REPLYTO, PREF_ACCOUNT);
9507 for (list = saved_list; list; list = list->next) {
9508 state = (HeaderEntryState *) list->data;
9510 compose_add_header_entry(compose, state->header,
9511 state->entry, state->type);
9512 if (state->entry_marked)
9513 compose_entry_mark_default_to(compose, state->entry);
9515 g_free(state->header);
9516 g_free(state->entry);
9519 g_slist_free(saved_list);
9521 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9522 (ac->protocol == A_NNTP) ?
9523 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9526 /* Set message save folder */
9527 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9528 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9530 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9531 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9533 compose_set_save_to(compose, NULL);
9534 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9535 folderidentifier = folder_item_get_identifier(account_get_special_folder
9536 (compose->account, F_OUTBOX));
9537 compose_set_save_to(compose, folderidentifier);
9538 g_free(folderidentifier);
9542 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9543 GtkTreeViewColumn *column, Compose *compose)
9545 compose_attach_property(NULL, compose);
9548 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9551 Compose *compose = (Compose *)data;
9552 GtkTreeSelection *attach_selection;
9553 gint attach_nr_selected;
9555 if (!event) return FALSE;
9557 if (event->button == 3) {
9558 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9559 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9561 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9562 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9564 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9565 NULL, NULL, event->button, event->time);
9572 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9575 Compose *compose = (Compose *)data;
9577 if (!event) return FALSE;
9579 switch (event->keyval) {
9580 case GDK_KEY_Delete:
9581 compose_attach_remove_selected(NULL, compose);
9587 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9589 toolbar_comp_set_sensitive(compose, allow);
9590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9591 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9593 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9596 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9597 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9599 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9603 static void compose_send_cb(GtkAction *action, gpointer data)
9605 Compose *compose = (Compose *)data;
9607 if (prefs_common.work_offline &&
9608 !inc_offline_should_override(TRUE,
9609 _("Claws Mail needs network access in order "
9610 "to send this email.")))
9613 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9614 g_source_remove(compose->draft_timeout_tag);
9615 compose->draft_timeout_tag = -1;
9618 compose_send(compose);
9621 static void compose_send_later_cb(GtkAction *action, gpointer data)
9623 Compose *compose = (Compose *)data;
9627 compose_allow_user_actions(compose, FALSE);
9628 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9629 compose_allow_user_actions(compose, TRUE);
9633 compose_close(compose);
9634 } else if (val == -1) {
9635 alertpanel_error(_("Could not queue message."));
9636 } else if (val == -2) {
9637 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9638 } else if (val == -3) {
9639 if (privacy_peek_error())
9640 alertpanel_error(_("Could not queue message for sending:\n\n"
9641 "Signature failed: %s"), privacy_get_error());
9642 } else if (val == -4) {
9643 alertpanel_error(_("Could not queue message for sending:\n\n"
9644 "Charset conversion failed."));
9645 } else if (val == -5) {
9646 alertpanel_error(_("Could not queue message for sending:\n\n"
9647 "Couldn't get recipient encryption key."));
9648 } else if (val == -6) {
9651 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9654 #define DRAFTED_AT_EXIT "drafted_at_exit"
9655 static void compose_register_draft(MsgInfo *info)
9657 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9658 DRAFTED_AT_EXIT, NULL);
9659 FILE *fp = g_fopen(filepath, "ab");
9662 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9670 gboolean compose_draft (gpointer data, guint action)
9672 Compose *compose = (Compose *)data;
9677 MsgFlags flag = {0, 0};
9678 static gboolean lock = FALSE;
9679 MsgInfo *newmsginfo;
9681 gboolean target_locked = FALSE;
9682 gboolean err = FALSE;
9684 if (lock) return FALSE;
9686 if (compose->sending)
9689 draft = account_get_special_folder(compose->account, F_DRAFT);
9690 cm_return_val_if_fail(draft != NULL, FALSE);
9692 if (!g_mutex_trylock(compose->mutex)) {
9693 /* we don't want to lock the mutex once it's available,
9694 * because as the only other part of compose.c locking
9695 * it is compose_close - which means once unlocked,
9696 * the compose struct will be freed */
9697 debug_print("couldn't lock mutex, probably sending\n");
9703 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9704 G_DIR_SEPARATOR, compose);
9705 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9706 FILE_OP_ERROR(tmp, "fopen");
9710 /* chmod for security */
9711 if (change_file_mode_rw(fp, tmp) < 0) {
9712 FILE_OP_ERROR(tmp, "chmod");
9713 g_warning("can't change file mode\n");
9716 /* Save draft infos */
9717 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9718 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9720 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9721 gchar *savefolderid;
9723 savefolderid = compose_get_save_to(compose);
9724 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9725 g_free(savefolderid);
9727 if (compose->return_receipt) {
9728 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9730 if (compose->privacy_system) {
9731 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9732 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9733 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9736 /* Message-ID of message replying to */
9737 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9740 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9741 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9744 /* Message-ID of message forwarding to */
9745 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9748 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9749 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9753 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9754 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9756 sheaders = compose_get_manual_headers_info(compose);
9757 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9760 /* end of headers */
9761 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9768 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9772 if (fclose(fp) == EOF) {
9776 if (compose->targetinfo) {
9777 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9778 flag.perm_flags = target_locked?MSG_LOCKED:0;
9780 flag.tmp_flags = MSG_DRAFT;
9782 folder_item_scan(draft);
9783 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9784 MsgInfo *tmpinfo = NULL;
9785 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9786 if (compose->msgid) {
9787 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9790 msgnum = tmpinfo->msgnum;
9791 procmsg_msginfo_free(tmpinfo);
9792 debug_print("got draft msgnum %d from scanning\n", msgnum);
9794 debug_print("didn't get draft msgnum after scanning\n");
9797 debug_print("got draft msgnum %d from adding\n", msgnum);
9803 if (action != COMPOSE_AUTO_SAVE) {
9804 if (action != COMPOSE_DRAFT_FOR_EXIT)
9805 alertpanel_error(_("Could not save draft."));
9808 gtkut_window_popup(compose->window);
9809 val = alertpanel_full(_("Could not save draft"),
9810 _("Could not save draft.\n"
9811 "Do you want to cancel exit or discard this email?"),
9812 _("_Cancel exit"), _("_Discard email"), NULL,
9813 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9814 if (val == G_ALERTALTERNATE) {
9816 g_mutex_unlock(compose->mutex); /* must be done before closing */
9817 compose_close(compose);
9821 g_mutex_unlock(compose->mutex); /* must be done before closing */
9830 if (compose->mode == COMPOSE_REEDIT) {
9831 compose_remove_reedit_target(compose, TRUE);
9834 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9837 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9839 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9841 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9842 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9843 procmsg_msginfo_set_flags(newmsginfo, 0,
9844 MSG_HAS_ATTACHMENT);
9846 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9847 compose_register_draft(newmsginfo);
9849 procmsg_msginfo_free(newmsginfo);
9852 folder_item_scan(draft);
9854 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9856 g_mutex_unlock(compose->mutex); /* must be done before closing */
9857 compose_close(compose);
9863 path = folder_item_fetch_msg(draft, msgnum);
9865 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9868 if (g_stat(path, &s) < 0) {
9869 FILE_OP_ERROR(path, "stat");
9875 procmsg_msginfo_free(compose->targetinfo);
9876 compose->targetinfo = procmsg_msginfo_new();
9877 compose->targetinfo->msgnum = msgnum;
9878 compose->targetinfo->size = (goffset)s.st_size;
9879 compose->targetinfo->mtime = s.st_mtime;
9880 compose->targetinfo->folder = draft;
9882 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9883 compose->mode = COMPOSE_REEDIT;
9885 if (action == COMPOSE_AUTO_SAVE) {
9886 compose->autosaved_draft = compose->targetinfo;
9888 compose->modified = FALSE;
9889 compose_set_title(compose);
9893 g_mutex_unlock(compose->mutex);
9897 void compose_clear_exit_drafts(void)
9899 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9900 DRAFTED_AT_EXIT, NULL);
9901 if (is_file_exist(filepath))
9902 claws_unlink(filepath);
9907 void compose_reopen_exit_drafts(void)
9909 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9910 DRAFTED_AT_EXIT, NULL);
9911 FILE *fp = g_fopen(filepath, "rb");
9915 while (fgets(buf, sizeof(buf), fp)) {
9916 gchar **parts = g_strsplit(buf, "\t", 2);
9917 const gchar *folder = parts[0];
9918 int msgnum = parts[1] ? atoi(parts[1]):-1;
9920 if (folder && *folder && msgnum > -1) {
9921 FolderItem *item = folder_find_item_from_identifier(folder);
9922 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9924 compose_reedit(info, FALSE);
9931 compose_clear_exit_drafts();
9934 static void compose_save_cb(GtkAction *action, gpointer data)
9936 Compose *compose = (Compose *)data;
9937 compose_draft(compose, COMPOSE_KEEP_EDITING);
9938 compose->rmode = COMPOSE_REEDIT;
9941 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9943 if (compose && file_list) {
9946 for ( tmp = file_list; tmp; tmp = tmp->next) {
9947 gchar *file = (gchar *) tmp->data;
9948 gchar *utf8_filename = conv_filename_to_utf8(file);
9949 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9950 compose_changed_cb(NULL, compose);
9955 g_free(utf8_filename);
9960 static void compose_attach_cb(GtkAction *action, gpointer data)
9962 Compose *compose = (Compose *)data;
9965 if (compose->redirect_filename != NULL)
9968 /* Set focus_window properly, in case we were called via popup menu,
9969 * which unsets it (via focus_out_event callback on compose window). */
9970 manage_window_focus_in(compose->window, NULL, NULL);
9972 file_list = filesel_select_multiple_files_open(_("Select file"));
9975 compose_attach_from_list(compose, file_list, TRUE);
9976 g_list_free(file_list);
9980 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9982 Compose *compose = (Compose *)data;
9984 gint files_inserted = 0;
9986 file_list = filesel_select_multiple_files_open(_("Select file"));
9991 for ( tmp = file_list; tmp; tmp = tmp->next) {
9992 gchar *file = (gchar *) tmp->data;
9993 gchar *filedup = g_strdup(file);
9994 gchar *shortfile = g_path_get_basename(filedup);
9995 ComposeInsertResult res;
9996 /* insert the file if the file is short or if the user confirmed that
9997 he/she wants to insert the large file */
9998 res = compose_insert_file(compose, file);
9999 if (res == COMPOSE_INSERT_READ_ERROR) {
10000 alertpanel_error(_("File '%s' could not be read."), shortfile);
10001 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10002 alertpanel_error(_("File '%s' contained invalid characters\n"
10003 "for the current encoding, insertion may be incorrect."),
10005 } else if (res == COMPOSE_INSERT_SUCCESS)
10012 g_list_free(file_list);
10016 if (files_inserted > 0 && compose->gtkaspell &&
10017 compose->gtkaspell->check_while_typing)
10018 gtkaspell_highlight_all(compose->gtkaspell);
10022 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10024 Compose *compose = (Compose *)data;
10026 compose_insert_sig(compose, FALSE);
10029 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10031 Compose *compose = (Compose *)data;
10033 compose_insert_sig(compose, TRUE);
10036 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10040 Compose *compose = (Compose *)data;
10042 gtkut_widget_get_uposition(widget, &x, &y);
10043 if (!compose->batch) {
10044 prefs_common.compose_x = x;
10045 prefs_common.compose_y = y;
10047 if (compose->sending || compose->updating)
10049 compose_close_cb(NULL, compose);
10053 void compose_close_toolbar(Compose *compose)
10055 compose_close_cb(NULL, compose);
10058 static gboolean compose_can_autosave(Compose *compose)
10060 if (compose->privacy_system && compose->use_encryption)
10061 return prefs_common.autosave && prefs_common.autosave_encrypted;
10063 return prefs_common.autosave;
10066 static void compose_close_cb(GtkAction *action, gpointer data)
10068 Compose *compose = (Compose *)data;
10072 if (compose->exteditor_tag != -1) {
10073 if (!compose_ext_editor_kill(compose))
10078 if (compose->modified) {
10079 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10080 if (!g_mutex_trylock(compose->mutex)) {
10081 /* we don't want to lock the mutex once it's available,
10082 * because as the only other part of compose.c locking
10083 * it is compose_close - which means once unlocked,
10084 * the compose struct will be freed */
10085 debug_print("couldn't lock mutex, probably sending\n");
10089 val = alertpanel(_("Discard message"),
10090 _("This message has been modified. Discard it?"),
10091 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10093 val = alertpanel(_("Save changes"),
10094 _("This message has been modified. Save the latest changes?"),
10095 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10097 g_mutex_unlock(compose->mutex);
10099 case G_ALERTDEFAULT:
10100 if (compose_can_autosave(compose) && !reedit)
10101 compose_remove_draft(compose);
10103 case G_ALERTALTERNATE:
10104 compose_draft(data, COMPOSE_QUIT_EDITING);
10111 compose_close(compose);
10114 static void compose_print_cb(GtkAction *action, gpointer data)
10116 Compose *compose = (Compose *) data;
10118 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10119 if (compose->targetinfo)
10120 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10123 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10125 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10126 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10127 Compose *compose = (Compose *) data;
10130 compose->out_encoding = (CharSet)value;
10133 static void compose_address_cb(GtkAction *action, gpointer data)
10135 Compose *compose = (Compose *)data;
10137 #ifndef USE_NEW_ADDRBOOK
10138 addressbook_open(compose);
10140 GError* error = NULL;
10141 addressbook_connect_signals(compose);
10142 addressbook_dbus_open(TRUE, &error);
10144 g_warning("%s", error->message);
10145 g_error_free(error);
10150 static void about_show_cb(GtkAction *action, gpointer data)
10155 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10157 Compose *compose = (Compose *)data;
10162 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10163 cm_return_if_fail(tmpl != NULL);
10165 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10167 val = alertpanel(_("Apply template"), msg,
10168 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10171 if (val == G_ALERTDEFAULT)
10172 compose_template_apply(compose, tmpl, TRUE);
10173 else if (val == G_ALERTALTERNATE)
10174 compose_template_apply(compose, tmpl, FALSE);
10177 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10179 Compose *compose = (Compose *)data;
10181 compose_exec_ext_editor(compose);
10184 static void compose_undo_cb(GtkAction *action, gpointer data)
10186 Compose *compose = (Compose *)data;
10187 gboolean prev_autowrap = compose->autowrap;
10189 compose->autowrap = FALSE;
10190 undo_undo(compose->undostruct);
10191 compose->autowrap = prev_autowrap;
10194 static void compose_redo_cb(GtkAction *action, gpointer data)
10196 Compose *compose = (Compose *)data;
10197 gboolean prev_autowrap = compose->autowrap;
10199 compose->autowrap = FALSE;
10200 undo_redo(compose->undostruct);
10201 compose->autowrap = prev_autowrap;
10204 static void entry_cut_clipboard(GtkWidget *entry)
10206 if (GTK_IS_EDITABLE(entry))
10207 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10208 else if (GTK_IS_TEXT_VIEW(entry))
10209 gtk_text_buffer_cut_clipboard(
10210 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10211 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10215 static void entry_copy_clipboard(GtkWidget *entry)
10217 if (GTK_IS_EDITABLE(entry))
10218 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10219 else if (GTK_IS_TEXT_VIEW(entry))
10220 gtk_text_buffer_copy_clipboard(
10221 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10222 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10225 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10226 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10228 if (GTK_IS_TEXT_VIEW(entry)) {
10229 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10230 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10231 GtkTextIter start_iter, end_iter;
10233 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10235 if (contents == NULL)
10238 /* we shouldn't delete the selection when middle-click-pasting, or we
10239 * can't mid-click-paste our own selection */
10240 if (clip != GDK_SELECTION_PRIMARY) {
10241 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10242 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10245 if (insert_place == NULL) {
10246 /* if insert_place isn't specified, insert at the cursor.
10247 * used for Ctrl-V pasting */
10248 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10249 start = gtk_text_iter_get_offset(&start_iter);
10250 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10252 /* if insert_place is specified, paste here.
10253 * used for mid-click-pasting */
10254 start = gtk_text_iter_get_offset(insert_place);
10255 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10256 if (prefs_common.primary_paste_unselects)
10257 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10261 /* paste unwrapped: mark the paste so it's not wrapped later */
10262 end = start + strlen(contents);
10263 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10264 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10265 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10266 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10267 /* rewrap paragraph now (after a mid-click-paste) */
10268 mark_start = gtk_text_buffer_get_insert(buffer);
10269 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10270 gtk_text_iter_backward_char(&start_iter);
10271 compose_beautify_paragraph(compose, &start_iter, TRUE);
10273 } else if (GTK_IS_EDITABLE(entry))
10274 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10276 compose->modified = TRUE;
10279 static void entry_allsel(GtkWidget *entry)
10281 if (GTK_IS_EDITABLE(entry))
10282 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10283 else if (GTK_IS_TEXT_VIEW(entry)) {
10284 GtkTextIter startiter, enditer;
10285 GtkTextBuffer *textbuf;
10287 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10288 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10289 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10291 gtk_text_buffer_move_mark_by_name(textbuf,
10292 "selection_bound", &startiter);
10293 gtk_text_buffer_move_mark_by_name(textbuf,
10294 "insert", &enditer);
10298 static void compose_cut_cb(GtkAction *action, gpointer data)
10300 Compose *compose = (Compose *)data;
10301 if (compose->focused_editable
10302 #ifndef GENERIC_UMPC
10303 && gtk_widget_has_focus(compose->focused_editable)
10306 entry_cut_clipboard(compose->focused_editable);
10309 static void compose_copy_cb(GtkAction *action, gpointer data)
10311 Compose *compose = (Compose *)data;
10312 if (compose->focused_editable
10313 #ifndef GENERIC_UMPC
10314 && gtk_widget_has_focus(compose->focused_editable)
10317 entry_copy_clipboard(compose->focused_editable);
10320 static void compose_paste_cb(GtkAction *action, gpointer data)
10322 Compose *compose = (Compose *)data;
10323 gint prev_autowrap;
10324 GtkTextBuffer *buffer;
10326 if (compose->focused_editable &&
10327 #ifndef GENERIC_UMPC
10328 gtk_widget_has_focus(compose->focused_editable)
10331 entry_paste_clipboard(compose, compose->focused_editable,
10332 prefs_common.linewrap_pastes,
10333 GDK_SELECTION_CLIPBOARD, NULL);
10338 #ifndef GENERIC_UMPC
10339 gtk_widget_has_focus(compose->text) &&
10341 compose->gtkaspell &&
10342 compose->gtkaspell->check_while_typing)
10343 gtkaspell_highlight_all(compose->gtkaspell);
10347 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10349 Compose *compose = (Compose *)data;
10350 gint wrap_quote = prefs_common.linewrap_quote;
10351 if (compose->focused_editable
10352 #ifndef GENERIC_UMPC
10353 && gtk_widget_has_focus(compose->focused_editable)
10356 /* let text_insert() (called directly or at a later time
10357 * after the gtk_editable_paste_clipboard) know that
10358 * text is to be inserted as a quotation. implemented
10359 * by using a simple refcount... */
10360 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10361 G_OBJECT(compose->focused_editable),
10362 "paste_as_quotation"));
10363 g_object_set_data(G_OBJECT(compose->focused_editable),
10364 "paste_as_quotation",
10365 GINT_TO_POINTER(paste_as_quotation + 1));
10366 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10367 entry_paste_clipboard(compose, compose->focused_editable,
10368 prefs_common.linewrap_pastes,
10369 GDK_SELECTION_CLIPBOARD, NULL);
10370 prefs_common.linewrap_quote = wrap_quote;
10374 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10376 Compose *compose = (Compose *)data;
10377 gint prev_autowrap;
10378 GtkTextBuffer *buffer;
10380 if (compose->focused_editable
10381 #ifndef GENERIC_UMPC
10382 && gtk_widget_has_focus(compose->focused_editable)
10385 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10386 GDK_SELECTION_CLIPBOARD, NULL);
10391 #ifndef GENERIC_UMPC
10392 gtk_widget_has_focus(compose->text) &&
10394 compose->gtkaspell &&
10395 compose->gtkaspell->check_while_typing)
10396 gtkaspell_highlight_all(compose->gtkaspell);
10400 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10402 Compose *compose = (Compose *)data;
10403 gint prev_autowrap;
10404 GtkTextBuffer *buffer;
10406 if (compose->focused_editable
10407 #ifndef GENERIC_UMPC
10408 && gtk_widget_has_focus(compose->focused_editable)
10411 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10412 GDK_SELECTION_CLIPBOARD, NULL);
10417 #ifndef GENERIC_UMPC
10418 gtk_widget_has_focus(compose->text) &&
10420 compose->gtkaspell &&
10421 compose->gtkaspell->check_while_typing)
10422 gtkaspell_highlight_all(compose->gtkaspell);
10426 static void compose_allsel_cb(GtkAction *action, gpointer data)
10428 Compose *compose = (Compose *)data;
10429 if (compose->focused_editable
10430 #ifndef GENERIC_UMPC
10431 && gtk_widget_has_focus(compose->focused_editable)
10434 entry_allsel(compose->focused_editable);
10437 static void textview_move_beginning_of_line (GtkTextView *text)
10439 GtkTextBuffer *buffer;
10443 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10445 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10446 mark = gtk_text_buffer_get_insert(buffer);
10447 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10448 gtk_text_iter_set_line_offset(&ins, 0);
10449 gtk_text_buffer_place_cursor(buffer, &ins);
10452 static void textview_move_forward_character (GtkTextView *text)
10454 GtkTextBuffer *buffer;
10458 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10460 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10461 mark = gtk_text_buffer_get_insert(buffer);
10462 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10463 if (gtk_text_iter_forward_cursor_position(&ins))
10464 gtk_text_buffer_place_cursor(buffer, &ins);
10467 static void textview_move_backward_character (GtkTextView *text)
10469 GtkTextBuffer *buffer;
10473 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10475 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10476 mark = gtk_text_buffer_get_insert(buffer);
10477 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10478 if (gtk_text_iter_backward_cursor_position(&ins))
10479 gtk_text_buffer_place_cursor(buffer, &ins);
10482 static void textview_move_forward_word (GtkTextView *text)
10484 GtkTextBuffer *buffer;
10489 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10491 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10492 mark = gtk_text_buffer_get_insert(buffer);
10493 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10494 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10495 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10496 gtk_text_iter_backward_word_start(&ins);
10497 gtk_text_buffer_place_cursor(buffer, &ins);
10501 static void textview_move_backward_word (GtkTextView *text)
10503 GtkTextBuffer *buffer;
10507 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10509 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10510 mark = gtk_text_buffer_get_insert(buffer);
10511 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10512 if (gtk_text_iter_backward_word_starts(&ins, 1))
10513 gtk_text_buffer_place_cursor(buffer, &ins);
10516 static void textview_move_end_of_line (GtkTextView *text)
10518 GtkTextBuffer *buffer;
10522 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10524 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10525 mark = gtk_text_buffer_get_insert(buffer);
10526 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10527 if (gtk_text_iter_forward_to_line_end(&ins))
10528 gtk_text_buffer_place_cursor(buffer, &ins);
10531 static void textview_move_next_line (GtkTextView *text)
10533 GtkTextBuffer *buffer;
10538 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10540 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10541 mark = gtk_text_buffer_get_insert(buffer);
10542 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10543 offset = gtk_text_iter_get_line_offset(&ins);
10544 if (gtk_text_iter_forward_line(&ins)) {
10545 gtk_text_iter_set_line_offset(&ins, offset);
10546 gtk_text_buffer_place_cursor(buffer, &ins);
10550 static void textview_move_previous_line (GtkTextView *text)
10552 GtkTextBuffer *buffer;
10557 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10559 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10560 mark = gtk_text_buffer_get_insert(buffer);
10561 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10562 offset = gtk_text_iter_get_line_offset(&ins);
10563 if (gtk_text_iter_backward_line(&ins)) {
10564 gtk_text_iter_set_line_offset(&ins, offset);
10565 gtk_text_buffer_place_cursor(buffer, &ins);
10569 static void textview_delete_forward_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_forward_char(&end_iter)) {
10582 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10586 static void textview_delete_backward_character (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_backward_char(&end_iter)) {
10599 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10603 static void textview_delete_forward_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_forward_word_end(&end_iter)) {
10616 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10620 static void textview_delete_backward_word (GtkTextView *text)
10622 GtkTextBuffer *buffer;
10624 GtkTextIter ins, 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);
10632 if (gtk_text_iter_backward_word_start(&end_iter)) {
10633 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10637 static void textview_delete_line (GtkTextView *text)
10639 GtkTextBuffer *buffer;
10641 GtkTextIter ins, start_iter, end_iter;
10643 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10645 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10646 mark = gtk_text_buffer_get_insert(buffer);
10647 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10650 gtk_text_iter_set_line_offset(&start_iter, 0);
10653 if (gtk_text_iter_ends_line(&end_iter)){
10654 if (!gtk_text_iter_forward_char(&end_iter))
10655 gtk_text_iter_backward_char(&start_iter);
10658 gtk_text_iter_forward_to_line_end(&end_iter);
10659 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10662 static void textview_delete_to_line_end (GtkTextView *text)
10664 GtkTextBuffer *buffer;
10666 GtkTextIter ins, end_iter;
10668 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10670 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10671 mark = gtk_text_buffer_get_insert(buffer);
10672 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10674 if (gtk_text_iter_ends_line(&end_iter))
10675 gtk_text_iter_forward_char(&end_iter);
10677 gtk_text_iter_forward_to_line_end(&end_iter);
10678 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10681 #define DO_ACTION(name, act) { \
10682 if(!strcmp(name, a_name)) { \
10686 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10688 const gchar *a_name = gtk_action_get_name(action);
10689 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10690 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10691 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10692 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10693 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10694 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10695 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10696 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10697 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10698 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10699 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10700 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10701 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10702 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10706 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10708 Compose *compose = (Compose *)data;
10709 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10710 ComposeCallAdvancedAction action = -1;
10712 action = compose_call_advanced_action_from_path(gaction);
10715 void (*do_action) (GtkTextView *text);
10716 } action_table[] = {
10717 {textview_move_beginning_of_line},
10718 {textview_move_forward_character},
10719 {textview_move_backward_character},
10720 {textview_move_forward_word},
10721 {textview_move_backward_word},
10722 {textview_move_end_of_line},
10723 {textview_move_next_line},
10724 {textview_move_previous_line},
10725 {textview_delete_forward_character},
10726 {textview_delete_backward_character},
10727 {textview_delete_forward_word},
10728 {textview_delete_backward_word},
10729 {textview_delete_line},
10730 {textview_delete_to_line_end}
10733 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10735 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10736 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10737 if (action_table[action].do_action)
10738 action_table[action].do_action(text);
10740 g_warning("Not implemented yet.");
10744 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10746 GtkAllocation allocation;
10750 if (GTK_IS_EDITABLE(widget)) {
10751 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10752 gtk_editable_set_position(GTK_EDITABLE(widget),
10755 if ((parent = gtk_widget_get_parent(widget))
10756 && (parent = gtk_widget_get_parent(parent))
10757 && (parent = gtk_widget_get_parent(parent))) {
10758 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10759 gtk_widget_get_allocation(widget, &allocation);
10760 gint y = allocation.y;
10761 gint height = allocation.height;
10762 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10763 (GTK_SCROLLED_WINDOW(parent));
10765 gfloat value = gtk_adjustment_get_value(shown);
10766 gfloat upper = gtk_adjustment_get_upper(shown);
10767 gfloat page_size = gtk_adjustment_get_page_size(shown);
10768 if (y < (int)value) {
10769 gtk_adjustment_set_value(shown, y - 1);
10771 if ((y + height) > ((int)value + (int)page_size)) {
10772 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10773 gtk_adjustment_set_value(shown,
10774 y + height - (int)page_size - 1);
10776 gtk_adjustment_set_value(shown,
10777 (int)upper - (int)page_size - 1);
10784 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10785 compose->focused_editable = widget;
10787 #ifdef GENERIC_UMPC
10788 if (GTK_IS_TEXT_VIEW(widget)
10789 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
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->edit_vbox);
10795 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
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),
10805 } else if (!GTK_IS_TEXT_VIEW(widget)
10806 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10807 g_object_ref(compose->notebook);
10808 g_object_ref(compose->edit_vbox);
10809 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10810 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10811 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10812 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10813 g_object_unref(compose->notebook);
10814 g_object_unref(compose->edit_vbox);
10815 g_signal_handlers_block_by_func(G_OBJECT(widget),
10816 G_CALLBACK(compose_grab_focus_cb),
10818 gtk_widget_grab_focus(widget);
10819 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10820 G_CALLBACK(compose_grab_focus_cb),
10826 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10828 compose->modified = TRUE;
10829 // compose_beautify_paragraph(compose, NULL, TRUE);
10830 #ifndef GENERIC_UMPC
10831 compose_set_title(compose);
10835 static void compose_wrap_cb(GtkAction *action, gpointer data)
10837 Compose *compose = (Compose *)data;
10838 compose_beautify_paragraph(compose, NULL, TRUE);
10841 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10843 Compose *compose = (Compose *)data;
10844 compose_wrap_all_full(compose, TRUE);
10847 static void compose_find_cb(GtkAction *action, gpointer data)
10849 Compose *compose = (Compose *)data;
10851 message_search_compose(compose);
10854 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10857 Compose *compose = (Compose *)data;
10858 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10859 if (compose->autowrap)
10860 compose_wrap_all_full(compose, TRUE);
10861 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10864 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10867 Compose *compose = (Compose *)data;
10868 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10871 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10873 Compose *compose = (Compose *)data;
10875 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10878 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10880 Compose *compose = (Compose *)data;
10882 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10885 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10887 g_free(compose->privacy_system);
10889 compose->privacy_system = g_strdup(account->default_privacy_system);
10890 compose_update_privacy_system_menu_item(compose, warn);
10893 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10895 Compose *compose = (Compose *)data;
10897 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10898 gtk_widget_show(compose->ruler_hbox);
10899 prefs_common.show_ruler = TRUE;
10901 gtk_widget_hide(compose->ruler_hbox);
10902 gtk_widget_queue_resize(compose->edit_vbox);
10903 prefs_common.show_ruler = FALSE;
10907 static void compose_attach_drag_received_cb (GtkWidget *widget,
10908 GdkDragContext *context,
10911 GtkSelectionData *data,
10914 gpointer user_data)
10916 Compose *compose = (Compose *)user_data;
10920 type = gtk_selection_data_get_data_type(data);
10921 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10923 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10925 ) && gtk_drag_get_source_widget(context) !=
10926 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10927 list = uri_list_extract_filenames(
10928 (const gchar *)gtk_selection_data_get_data(data));
10929 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10930 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10931 compose_attach_append
10932 (compose, (const gchar *)tmp->data,
10933 utf8_filename, NULL, NULL);
10934 g_free(utf8_filename);
10936 if (list) compose_changed_cb(NULL, compose);
10937 list_free_strings(list);
10939 } else if (gtk_drag_get_source_widget(context)
10940 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10941 /* comes from our summaryview */
10942 SummaryView * summaryview = NULL;
10943 GSList * list = NULL, *cur = NULL;
10945 if (mainwindow_get_mainwindow())
10946 summaryview = mainwindow_get_mainwindow()->summaryview;
10949 list = summary_get_selected_msg_list(summaryview);
10951 for (cur = list; cur; cur = cur->next) {
10952 MsgInfo *msginfo = (MsgInfo *)cur->data;
10953 gchar *file = NULL;
10955 file = procmsg_get_message_file_full(msginfo,
10958 compose_attach_append(compose, (const gchar *)file,
10959 (const gchar *)file, "message/rfc822", NULL);
10963 g_slist_free(list);
10967 static gboolean compose_drag_drop(GtkWidget *widget,
10968 GdkDragContext *drag_context,
10970 guint time, gpointer user_data)
10972 /* not handling this signal makes compose_insert_drag_received_cb
10977 static gboolean completion_set_focus_to_subject
10978 (GtkWidget *widget,
10979 GdkEventKey *event,
10982 cm_return_val_if_fail(compose != NULL, FALSE);
10984 /* make backtab move to subject field */
10985 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10986 gtk_widget_grab_focus(compose->subject_entry);
10992 static void compose_insert_drag_received_cb (GtkWidget *widget,
10993 GdkDragContext *drag_context,
10996 GtkSelectionData *data,
10999 gpointer user_data)
11001 Compose *compose = (Compose *)user_data;
11005 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11007 type = gtk_selection_data_get_data_type(data);
11009 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11011 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11013 AlertValue val = G_ALERTDEFAULT;
11014 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11016 list = uri_list_extract_filenames(ddata);
11017 if (list == NULL && strstr(ddata, "://")) {
11018 /* Assume a list of no files, and data has ://, is a remote link */
11019 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11020 gchar *tmpfile = get_tmp_file();
11021 str_write_to_file(tmpdata, tmpfile);
11023 compose_insert_file(compose, tmpfile);
11024 claws_unlink(tmpfile);
11026 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11027 compose_beautify_paragraph(compose, NULL, TRUE);
11030 switch (prefs_common.compose_dnd_mode) {
11031 case COMPOSE_DND_ASK:
11032 val = alertpanel_full(_("Insert or attach?"),
11033 _("Do you want to insert the contents of the file(s) "
11034 "into the message body, or attach it to the email?"),
11035 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11036 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11038 case COMPOSE_DND_INSERT:
11039 val = G_ALERTALTERNATE;
11041 case COMPOSE_DND_ATTACH:
11042 val = G_ALERTOTHER;
11045 /* unexpected case */
11046 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11049 if (val & G_ALERTDISABLE) {
11050 val &= ~G_ALERTDISABLE;
11051 /* remember what action to perform by default, only if we don't click Cancel */
11052 if (val == G_ALERTALTERNATE)
11053 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11054 else if (val == G_ALERTOTHER)
11055 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11058 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11059 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11060 list_free_strings(list);
11063 } else if (val == G_ALERTOTHER) {
11064 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11065 list_free_strings(list);
11070 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11071 compose_insert_file(compose, (const gchar *)tmp->data);
11073 list_free_strings(list);
11075 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11080 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11083 static void compose_header_drag_received_cb (GtkWidget *widget,
11084 GdkDragContext *drag_context,
11087 GtkSelectionData *data,
11090 gpointer user_data)
11092 GtkEditable *entry = (GtkEditable *)user_data;
11093 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11095 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11098 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11099 gchar *decoded=g_new(gchar, strlen(email));
11102 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11103 gtk_editable_delete_text(entry, 0, -1);
11104 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11105 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11109 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11112 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11114 Compose *compose = (Compose *)data;
11116 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11117 compose->return_receipt = TRUE;
11119 compose->return_receipt = FALSE;
11122 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11124 Compose *compose = (Compose *)data;
11126 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11127 compose->remove_references = TRUE;
11129 compose->remove_references = FALSE;
11132 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11133 ComposeHeaderEntry *headerentry)
11135 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11139 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11140 GdkEventKey *event,
11141 ComposeHeaderEntry *headerentry)
11143 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11144 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11145 !(event->state & GDK_MODIFIER_MASK) &&
11146 (event->keyval == GDK_KEY_BackSpace) &&
11147 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11148 gtk_container_remove
11149 (GTK_CONTAINER(headerentry->compose->header_table),
11150 headerentry->combo);
11151 gtk_container_remove
11152 (GTK_CONTAINER(headerentry->compose->header_table),
11153 headerentry->entry);
11154 headerentry->compose->header_list =
11155 g_slist_remove(headerentry->compose->header_list,
11157 g_free(headerentry);
11158 } else if (event->keyval == GDK_KEY_Tab) {
11159 if (headerentry->compose->header_last == headerentry) {
11160 /* Override default next focus, and give it to subject_entry
11161 * instead of notebook tabs
11163 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11164 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11171 static gboolean scroll_postpone(gpointer data)
11173 Compose *compose = (Compose *)data;
11175 cm_return_val_if_fail(!compose->batch, FALSE);
11177 GTK_EVENTS_FLUSH();
11178 compose_show_first_last_header(compose, FALSE);
11182 static void compose_headerentry_changed_cb(GtkWidget *entry,
11183 ComposeHeaderEntry *headerentry)
11185 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11186 compose_create_header_entry(headerentry->compose);
11187 g_signal_handlers_disconnect_matched
11188 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11189 0, 0, NULL, NULL, headerentry);
11191 if (!headerentry->compose->batch)
11192 g_timeout_add(0, scroll_postpone, headerentry->compose);
11196 static gboolean compose_defer_auto_save_draft(Compose *compose)
11198 compose->draft_timeout_tag = -1;
11199 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11203 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11205 GtkAdjustment *vadj;
11207 cm_return_if_fail(compose);
11208 cm_return_if_fail(!compose->batch);
11209 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11210 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11211 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11212 gtk_widget_get_parent(compose->header_table)));
11213 gtk_adjustment_set_value(vadj, (show_first ?
11214 gtk_adjustment_get_lower(vadj) :
11215 (gtk_adjustment_get_upper(vadj) -
11216 gtk_adjustment_get_page_size(vadj))));
11217 gtk_adjustment_changed(vadj);
11220 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11221 const gchar *text, gint len, Compose *compose)
11223 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11224 (G_OBJECT(compose->text), "paste_as_quotation"));
11227 cm_return_if_fail(text != NULL);
11229 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11230 G_CALLBACK(text_inserted),
11232 if (paste_as_quotation) {
11234 const gchar *qmark;
11236 GtkTextIter start_iter;
11239 len = strlen(text);
11241 new_text = g_strndup(text, len);
11243 qmark = compose_quote_char_from_context(compose);
11245 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11246 gtk_text_buffer_place_cursor(buffer, iter);
11248 pos = gtk_text_iter_get_offset(iter);
11250 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11251 _("Quote format error at line %d."));
11252 quote_fmt_reset_vartable();
11254 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11255 GINT_TO_POINTER(paste_as_quotation - 1));
11257 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11258 gtk_text_buffer_place_cursor(buffer, iter);
11259 gtk_text_buffer_delete_mark(buffer, mark);
11261 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11262 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11263 compose_beautify_paragraph(compose, &start_iter, FALSE);
11264 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11265 gtk_text_buffer_delete_mark(buffer, mark);
11267 if (strcmp(text, "\n") || compose->automatic_break
11268 || gtk_text_iter_starts_line(iter)) {
11269 GtkTextIter before_ins;
11270 gtk_text_buffer_insert(buffer, iter, text, len);
11271 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11272 before_ins = *iter;
11273 gtk_text_iter_backward_chars(&before_ins, len);
11274 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11277 /* check if the preceding is just whitespace or quote */
11278 GtkTextIter start_line;
11279 gchar *tmp = NULL, *quote = NULL;
11280 gint quote_len = 0, is_normal = 0;
11281 start_line = *iter;
11282 gtk_text_iter_set_line_offset(&start_line, 0);
11283 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11286 if (*tmp == '\0') {
11289 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11297 gtk_text_buffer_insert(buffer, iter, text, len);
11299 gtk_text_buffer_insert_with_tags_by_name(buffer,
11300 iter, text, len, "no_join", NULL);
11305 if (!paste_as_quotation) {
11306 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11307 compose_beautify_paragraph(compose, iter, FALSE);
11308 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11309 gtk_text_buffer_delete_mark(buffer, mark);
11312 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11313 G_CALLBACK(text_inserted),
11315 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11317 if (compose_can_autosave(compose) &&
11318 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11319 compose->draft_timeout_tag != -2 /* disabled while loading */)
11320 compose->draft_timeout_tag = g_timeout_add
11321 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11325 static void compose_check_all(GtkAction *action, gpointer data)
11327 Compose *compose = (Compose *)data;
11328 if (!compose->gtkaspell)
11331 if (gtk_widget_has_focus(compose->subject_entry))
11332 claws_spell_entry_check_all(
11333 CLAWS_SPELL_ENTRY(compose->subject_entry));
11335 gtkaspell_check_all(compose->gtkaspell);
11338 static void compose_highlight_all(GtkAction *action, gpointer data)
11340 Compose *compose = (Compose *)data;
11341 if (compose->gtkaspell) {
11342 claws_spell_entry_recheck_all(
11343 CLAWS_SPELL_ENTRY(compose->subject_entry));
11344 gtkaspell_highlight_all(compose->gtkaspell);
11348 static void compose_check_backwards(GtkAction *action, gpointer data)
11350 Compose *compose = (Compose *)data;
11351 if (!compose->gtkaspell) {
11352 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11356 if (gtk_widget_has_focus(compose->subject_entry))
11357 claws_spell_entry_check_backwards(
11358 CLAWS_SPELL_ENTRY(compose->subject_entry));
11360 gtkaspell_check_backwards(compose->gtkaspell);
11363 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11365 Compose *compose = (Compose *)data;
11366 if (!compose->gtkaspell) {
11367 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11371 if (gtk_widget_has_focus(compose->subject_entry))
11372 claws_spell_entry_check_forwards_go(
11373 CLAWS_SPELL_ENTRY(compose->subject_entry));
11375 gtkaspell_check_forwards_go(compose->gtkaspell);
11380 *\brief Guess originating forward account from MsgInfo and several
11381 * "common preference" settings. Return NULL if no guess.
11383 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11385 PrefsAccount *account = NULL;
11387 cm_return_val_if_fail(msginfo, NULL);
11388 cm_return_val_if_fail(msginfo->folder, NULL);
11389 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11391 if (msginfo->folder->prefs->enable_default_account)
11392 account = account_find_from_id(msginfo->folder->prefs->default_account);
11395 account = msginfo->folder->folder->account;
11397 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11399 Xstrdup_a(to, msginfo->to, return NULL);
11400 extract_address(to);
11401 account = account_find_from_address(to, FALSE);
11404 if (!account && prefs_common.forward_account_autosel) {
11405 gchar cc[BUFFSIZE];
11406 if (!procheader_get_header_from_msginfo
11407 (msginfo, cc,sizeof cc , "Cc:")) {
11408 gchar *buf = cc + strlen("Cc:");
11409 extract_address(buf);
11410 account = account_find_from_address(buf, FALSE);
11414 if (!account && prefs_common.forward_account_autosel) {
11415 gchar deliveredto[BUFFSIZE];
11416 if (!procheader_get_header_from_msginfo
11417 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11418 gchar *buf = deliveredto + strlen("Delivered-To:");
11419 extract_address(buf);
11420 account = account_find_from_address(buf, FALSE);
11427 gboolean compose_close(Compose *compose)
11431 if (!g_mutex_trylock(compose->mutex)) {
11432 /* we have to wait for the (possibly deferred by auto-save)
11433 * drafting to be done, before destroying the compose under
11435 debug_print("waiting for drafting to finish...\n");
11436 compose_allow_user_actions(compose, FALSE);
11437 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11440 cm_return_val_if_fail(compose, FALSE);
11441 gtkut_widget_get_uposition(compose->window, &x, &y);
11442 if (!compose->batch) {
11443 prefs_common.compose_x = x;
11444 prefs_common.compose_y = y;
11446 g_mutex_unlock(compose->mutex);
11447 compose_destroy(compose);
11452 * Add entry field for each address in list.
11453 * \param compose E-Mail composition object.
11454 * \param listAddress List of (formatted) E-Mail addresses.
11456 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11459 node = listAddress;
11461 addr = ( gchar * ) node->data;
11462 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11463 node = g_list_next( node );
11467 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11468 guint action, gboolean opening_multiple)
11470 gchar *body = NULL;
11471 GSList *new_msglist = NULL;
11472 MsgInfo *tmp_msginfo = NULL;
11473 gboolean originally_enc = FALSE;
11474 gboolean originally_sig = FALSE;
11475 Compose *compose = NULL;
11476 gchar *s_system = NULL;
11478 cm_return_if_fail(msgview != NULL);
11480 cm_return_if_fail(msginfo_list != NULL);
11482 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11483 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11484 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11486 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11487 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11488 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11489 orig_msginfo, mimeinfo);
11490 if (tmp_msginfo != NULL) {
11491 new_msglist = g_slist_append(NULL, tmp_msginfo);
11493 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11494 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11495 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11497 tmp_msginfo->folder = orig_msginfo->folder;
11498 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11499 if (orig_msginfo->tags) {
11500 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11501 tmp_msginfo->folder->tags_dirty = TRUE;
11507 if (!opening_multiple)
11508 body = messageview_get_selection(msgview);
11511 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11512 procmsg_msginfo_free(tmp_msginfo);
11513 g_slist_free(new_msglist);
11515 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11517 if (compose && originally_enc) {
11518 compose_force_encryption(compose, compose->account, FALSE, s_system);
11521 if (compose && originally_sig && compose->account->default_sign_reply) {
11522 compose_force_signing(compose, compose->account, s_system);
11526 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11529 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11532 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11533 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11534 GSList *cur = msginfo_list;
11535 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11536 "messages. Opening the windows "
11537 "could take some time. Do you "
11538 "want to continue?"),
11539 g_slist_length(msginfo_list));
11540 if (g_slist_length(msginfo_list) > 9
11541 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11542 != G_ALERTALTERNATE) {
11547 /* We'll open multiple compose windows */
11548 /* let the WM place the next windows */
11549 compose_force_window_origin = FALSE;
11550 for (; cur; cur = cur->next) {
11552 tmplist.data = cur->data;
11553 tmplist.next = NULL;
11554 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11556 compose_force_window_origin = TRUE;
11558 /* forwarding multiple mails as attachments is done via a
11559 * single compose window */
11560 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11564 void compose_check_for_email_account(Compose *compose)
11566 PrefsAccount *ac = NULL, *curr = NULL;
11572 if (compose->account && compose->account->protocol == A_NNTP) {
11573 ac = account_get_cur_account();
11574 if (ac->protocol == A_NNTP) {
11575 list = account_get_list();
11577 for( ; list != NULL ; list = g_list_next(list)) {
11578 curr = (PrefsAccount *) list->data;
11579 if (curr->protocol != A_NNTP) {
11585 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11590 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11591 const gchar *address)
11593 GSList *msginfo_list = NULL;
11594 gchar *body = messageview_get_selection(msgview);
11597 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11599 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11600 compose_check_for_email_account(compose);
11601 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11602 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11603 compose_reply_set_subject(compose, msginfo);
11606 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11609 void compose_set_position(Compose *compose, gint pos)
11611 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11613 gtkut_text_view_set_position(text, pos);
11616 gboolean compose_search_string(Compose *compose,
11617 const gchar *str, gboolean case_sens)
11619 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11621 return gtkut_text_view_search_string(text, str, case_sens);
11624 gboolean compose_search_string_backward(Compose *compose,
11625 const gchar *str, gboolean case_sens)
11627 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11629 return gtkut_text_view_search_string_backward(text, str, case_sens);
11632 /* allocate a msginfo structure and populate its data from a compose data structure */
11633 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11635 MsgInfo *newmsginfo;
11637 gchar buf[BUFFSIZE];
11639 cm_return_val_if_fail( compose != NULL, NULL );
11641 newmsginfo = procmsg_msginfo_new();
11644 get_rfc822_date(buf, sizeof(buf));
11645 newmsginfo->date = g_strdup(buf);
11648 if (compose->from_name) {
11649 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11650 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11654 if (compose->subject_entry)
11655 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11657 /* to, cc, reply-to, newsgroups */
11658 for (list = compose->header_list; list; list = list->next) {
11659 gchar *header = gtk_editable_get_chars(
11661 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11662 gchar *entry = gtk_editable_get_chars(
11663 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11665 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11666 if ( newmsginfo->to == NULL ) {
11667 newmsginfo->to = g_strdup(entry);
11668 } else if (entry && *entry) {
11669 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11670 g_free(newmsginfo->to);
11671 newmsginfo->to = tmp;
11674 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11675 if ( newmsginfo->cc == NULL ) {
11676 newmsginfo->cc = g_strdup(entry);
11677 } else if (entry && *entry) {
11678 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11679 g_free(newmsginfo->cc);
11680 newmsginfo->cc = tmp;
11683 if ( strcasecmp(header,
11684 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11685 if ( newmsginfo->newsgroups == NULL ) {
11686 newmsginfo->newsgroups = g_strdup(entry);
11687 } else if (entry && *entry) {
11688 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11689 g_free(newmsginfo->newsgroups);
11690 newmsginfo->newsgroups = tmp;
11698 /* other data is unset */
11704 /* update compose's dictionaries from folder dict settings */
11705 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11706 FolderItem *folder_item)
11708 cm_return_if_fail(compose != NULL);
11710 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11711 FolderItemPrefs *prefs = folder_item->prefs;
11713 if (prefs->enable_default_dictionary)
11714 gtkaspell_change_dict(compose->gtkaspell,
11715 prefs->default_dictionary, FALSE);
11716 if (folder_item->prefs->enable_default_alt_dictionary)
11717 gtkaspell_change_alt_dict(compose->gtkaspell,
11718 prefs->default_alt_dictionary);
11719 if (prefs->enable_default_dictionary
11720 || prefs->enable_default_alt_dictionary)
11721 compose_spell_menu_changed(compose);
11726 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11728 Compose *compose = (Compose *)data;
11730 cm_return_if_fail(compose != NULL);
11732 gtk_widget_grab_focus(compose->text);