2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 static GList *compose_list = NULL;
186 static GSList *extra_headers = NULL;
188 static Compose *compose_generic_new (PrefsAccount *account,
192 GList *listAddress );
194 static Compose *compose_create (PrefsAccount *account,
199 static void compose_entry_mark_default_to (Compose *compose,
200 const gchar *address);
201 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
202 ComposeQuoteMode quote_mode,
206 static Compose *compose_forward_multiple (PrefsAccount *account,
207 GSList *msginfo_list);
208 static Compose *compose_reply (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
214 static Compose *compose_reply_mode (ComposeMode mode,
215 GSList *msginfo_list,
217 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
218 static void compose_update_privacy_systems_menu(Compose *compose);
220 static GtkWidget *compose_account_option_menu_create
222 static void compose_set_out_encoding (Compose *compose);
223 static void compose_set_template_menu (Compose *compose);
224 static void compose_destroy (Compose *compose);
226 static MailField compose_entries_set (Compose *compose,
228 ComposeEntryType to_type);
229 static gint compose_parse_header (Compose *compose,
231 static gint compose_parse_manual_headers (Compose *compose,
233 HeaderEntry *entries);
234 static gchar *compose_parse_references (const gchar *ref,
237 static gchar *compose_quote_fmt (Compose *compose,
243 gboolean need_unescape,
244 const gchar *err_msg);
246 static void compose_reply_set_entry (Compose *compose,
252 followup_and_reply_to);
253 static void compose_reedit_set_entry (Compose *compose,
256 static void compose_insert_sig (Compose *compose,
258 static ComposeInsertResult compose_insert_file (Compose *compose,
261 static gboolean compose_attach_append (Compose *compose,
264 const gchar *content_type,
265 const gchar *charset);
266 static void compose_attach_parts (Compose *compose,
269 static gboolean compose_beautify_paragraph (Compose *compose,
270 GtkTextIter *par_iter,
272 static void compose_wrap_all (Compose *compose);
273 static void compose_wrap_all_full (Compose *compose,
276 static void compose_set_title (Compose *compose);
277 static void compose_select_account (Compose *compose,
278 PrefsAccount *account,
281 static PrefsAccount *compose_current_mail_account(void);
282 /* static gint compose_send (Compose *compose); */
283 static gboolean compose_check_for_valid_recipient
285 static gboolean compose_check_entries (Compose *compose,
286 gboolean check_everything);
287 static gint compose_write_to_file (Compose *compose,
290 gboolean attach_parts);
291 static gint compose_write_body_to_file (Compose *compose,
293 static gint compose_remove_reedit_target (Compose *compose,
295 static void compose_remove_draft (Compose *compose);
296 static gint compose_queue_sub (Compose *compose,
300 gboolean check_subject,
301 gboolean remove_reedit_target);
302 static int compose_add_attachments (Compose *compose,
304 static gchar *compose_get_header (Compose *compose);
305 static gchar *compose_get_manual_headers_info (Compose *compose);
307 static void compose_convert_header (Compose *compose,
312 gboolean addr_field);
314 static void compose_attach_info_free (AttachInfo *ainfo);
315 static void compose_attach_remove_selected (GtkAction *action,
318 static void compose_template_apply (Compose *compose,
321 static void compose_attach_property (GtkAction *action,
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
342 static void compose_set_ext_editor_sensitive (Compose *compose,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header,
353 gchar *text, ComposePrefType pref_type);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
359 static void compose_dict_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static void compose_notebook_size_alloc (GtkNotebook *notebook,
367 GtkAllocation *allocation,
369 static gboolean compose_edit_size_alloc (GtkEditable *widget,
370 GtkAllocation *allocation,
371 GtkSHRuler *shruler);
372 static void account_activated (GtkComboBox *optmenu,
374 static void attach_selected (GtkTreeView *tree_view,
375 GtkTreePath *tree_path,
376 GtkTreeViewColumn *column,
378 static gboolean attach_button_pressed (GtkWidget *widget,
379 GdkEventButton *event,
381 static gboolean attach_key_pressed (GtkWidget *widget,
384 static void compose_send_cb (GtkAction *action, gpointer data);
385 static void compose_send_later_cb (GtkAction *action, gpointer data);
387 static void compose_save_cb (GtkAction *action,
390 static void compose_attach_cb (GtkAction *action,
392 static void compose_insert_file_cb (GtkAction *action,
394 static void compose_insert_sig_cb (GtkAction *action,
396 static void compose_replace_sig_cb (GtkAction *action,
399 static void compose_close_cb (GtkAction *action,
401 static void compose_print_cb (GtkAction *action,
404 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
406 static void compose_address_cb (GtkAction *action,
408 static void about_show_cb (GtkAction *action,
410 static void compose_template_activate_cb(GtkWidget *widget,
413 static void compose_ext_editor_cb (GtkAction *action,
416 static gint compose_delete_cb (GtkWidget *widget,
420 static void compose_undo_cb (GtkAction *action,
422 static void compose_redo_cb (GtkAction *action,
424 static void compose_cut_cb (GtkAction *action,
426 static void compose_copy_cb (GtkAction *action,
428 static void compose_paste_cb (GtkAction *action,
430 static void compose_paste_as_quote_cb (GtkAction *action,
432 static void compose_paste_no_wrap_cb (GtkAction *action,
434 static void compose_paste_wrap_cb (GtkAction *action,
436 static void compose_allsel_cb (GtkAction *action,
439 static void compose_advanced_action_cb (GtkAction *action,
442 static void compose_grab_focus_cb (GtkWidget *widget,
445 static void compose_changed_cb (GtkTextBuffer *textbuf,
448 static void compose_wrap_cb (GtkAction *action,
450 static void compose_wrap_all_cb (GtkAction *action,
452 static void compose_find_cb (GtkAction *action,
454 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
456 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
459 static void compose_toggle_ruler_cb (GtkToggleAction *action,
461 static void compose_toggle_sign_cb (GtkToggleAction *action,
463 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
465 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
466 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
467 static void activate_privacy_system (Compose *compose,
468 PrefsAccount *account,
470 static void compose_use_signing(Compose *compose, gboolean use_signing);
471 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
472 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
474 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
476 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
477 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
478 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
480 static void compose_attach_drag_received_cb (GtkWidget *widget,
481 GdkDragContext *drag_context,
484 GtkSelectionData *data,
488 static void compose_insert_drag_received_cb (GtkWidget *widget,
489 GdkDragContext *drag_context,
492 GtkSelectionData *data,
496 static void compose_header_drag_received_cb (GtkWidget *widget,
497 GdkDragContext *drag_context,
500 GtkSelectionData *data,
505 static gboolean compose_drag_drop (GtkWidget *widget,
506 GdkDragContext *drag_context,
508 guint time, gpointer user_data);
509 static gboolean completion_set_focus_to_subject
514 static void text_inserted (GtkTextBuffer *buffer,
519 static Compose *compose_generic_reply(MsgInfo *msginfo,
520 ComposeQuoteMode quote_mode,
524 gboolean followup_and_reply_to,
527 static void compose_headerentry_changed_cb (GtkWidget *entry,
528 ComposeHeaderEntry *headerentry);
529 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
531 ComposeHeaderEntry *headerentry);
532 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
533 ComposeHeaderEntry *headerentry);
535 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
537 static void compose_allow_user_actions (Compose *compose, gboolean allow);
539 static void compose_nothing_cb (GtkAction *action, gpointer data)
545 static void compose_check_all (GtkAction *action, gpointer data);
546 static void compose_highlight_all (GtkAction *action, gpointer data);
547 static void compose_check_backwards (GtkAction *action, gpointer data);
548 static void compose_check_forwards_go (GtkAction *action, gpointer data);
551 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
553 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
556 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
557 FolderItem *folder_item);
559 static void compose_attach_update_label(Compose *compose);
560 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
561 gboolean respect_default_to);
562 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
564 static GtkActionEntry compose_popup_entries[] =
566 {"Compose", NULL, "Compose" },
567 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
568 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
569 {"Compose/---", NULL, "---", NULL, NULL, NULL },
570 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
573 static GtkActionEntry compose_entries[] =
575 {"Menu", NULL, "Menu" },
577 {"Message", NULL, N_("_Message") },
578 {"Edit", NULL, N_("_Edit") },
580 {"Spelling", NULL, N_("_Spelling") },
582 {"Options", NULL, N_("_Options") },
583 {"Tools", NULL, N_("_Tools") },
584 {"Help", NULL, N_("_Help") },
586 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
587 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
588 {"Message/---", NULL, "---" },
590 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
591 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
592 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
593 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
594 /* {"Message/---", NULL, "---" }, */
595 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
596 /* {"Message/---", NULL, "---" }, */
597 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
598 /* {"Message/---", NULL, "---" }, */
599 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
602 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
603 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
604 {"Edit/---", NULL, "---" },
606 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
607 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
608 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
610 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
611 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
612 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
613 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
615 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
617 {"Edit/Advanced", NULL, N_("A_dvanced") },
618 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
619 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
620 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
621 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
622 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
623 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
624 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
625 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
626 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
627 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
628 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
629 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
630 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
631 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
633 /* {"Edit/---", NULL, "---" }, */
634 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
638 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
643 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
644 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
645 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
646 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
648 {"Spelling/---", NULL, "---" },
649 {"Spelling/Options", NULL, N_("_Options") },
654 {"Options/ReplyMode", NULL, N_("Reply _mode") },
655 {"Options/---", NULL, "---" },
656 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
657 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
659 /* {"Options/---", NULL, "---" }, */
661 {"Options/Priority", NULL, N_("_Priority") },
663 {"Options/Encoding", NULL, N_("Character _encoding") },
664 {"Options/Encoding/---", NULL, "---" },
665 #define ENC_ACTION(cs_char,c_char,string) \
666 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
668 {"Options/Encoding/Western", NULL, N_("Western European") },
669 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
670 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
671 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
672 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
673 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
674 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
675 {"Options/Encoding/Korean", NULL, N_("Korean") },
676 {"Options/Encoding/Thai", NULL, N_("Thai") },
679 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
681 {"Tools/Template", NULL, N_("_Template") },
682 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
683 {"Tools/Actions", NULL, N_("Actio_ns") },
684 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
687 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
690 static GtkToggleActionEntry compose_toggle_entries[] =
692 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
693 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
694 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
695 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
696 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
697 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
698 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
701 static GtkRadioActionEntry compose_radio_rm_entries[] =
703 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
704 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
705 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
706 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
709 static GtkRadioActionEntry compose_radio_prio_entries[] =
711 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
712 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
713 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
714 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
718 static GtkRadioActionEntry compose_radio_enc_entries[] =
720 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
754 static GtkTargetEntry compose_mime_types[] =
756 {"text/uri-list", 0, 0},
757 {"UTF8_STRING", 0, 0},
761 static gboolean compose_put_existing_to_front(MsgInfo *info)
763 GList *compose_list = compose_get_compose_list();
767 for (elem = compose_list; elem != NULL && elem->data != NULL;
769 Compose *c = (Compose*)elem->data;
771 if (!c->targetinfo || !c->targetinfo->msgid ||
775 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
776 gtkut_window_popup(c->window);
784 static GdkColor quote_color1 =
785 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
786 static GdkColor quote_color2 =
787 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
788 static GdkColor quote_color3 =
789 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_bgcolor1 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
793 static GdkColor quote_bgcolor2 =
794 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor quote_bgcolor3 =
796 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor signature_color = {
805 static GdkColor uri_color = {
812 static void compose_create_tags(GtkTextView *text, Compose *compose)
814 GtkTextBuffer *buffer;
815 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
816 #if !GTK_CHECK_VERSION(2, 24, 0)
823 buffer = gtk_text_view_get_buffer(text);
825 if (prefs_common.enable_color) {
826 /* grab the quote colors, converting from an int to a GdkColor */
827 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
829 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
831 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
833 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
835 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
837 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
839 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
841 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
844 signature_color = quote_color1 = quote_color2 = quote_color3 =
845 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
848 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
849 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
850 "foreground-gdk", "e_color1,
851 "paragraph-background-gdk", "e_bgcolor1,
853 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
854 "foreground-gdk", "e_color2,
855 "paragraph-background-gdk", "e_bgcolor2,
857 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
858 "foreground-gdk", "e_color3,
859 "paragraph-background-gdk", "e_bgcolor3,
862 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
863 "foreground-gdk", "e_color1,
865 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
866 "foreground-gdk", "e_color2,
868 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
869 "foreground-gdk", "e_color3,
873 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
874 "foreground-gdk", &signature_color,
877 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
878 "foreground-gdk", &uri_color,
880 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
881 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
883 #if !GTK_CHECK_VERSION(2, 24, 0)
884 color[0] = quote_color1;
885 color[1] = quote_color2;
886 color[2] = quote_color3;
887 color[3] = quote_bgcolor1;
888 color[4] = quote_bgcolor2;
889 color[5] = quote_bgcolor3;
890 color[6] = signature_color;
891 color[7] = uri_color;
893 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
894 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
896 for (i = 0; i < 8; i++) {
897 if (success[i] == FALSE) {
898 g_warning("Compose: color allocation failed.\n");
899 quote_color1 = quote_color2 = quote_color3 =
900 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
901 signature_color = uri_color = black;
907 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
910 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
913 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
915 return compose_generic_new(account, mailto, item, NULL, NULL);
918 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
920 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
923 #define SCROLL_TO_CURSOR(compose) { \
924 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
925 gtk_text_view_get_buffer( \
926 GTK_TEXT_VIEW(compose->text))); \
927 gtk_text_view_scroll_mark_onscreen( \
928 GTK_TEXT_VIEW(compose->text), \
932 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
935 if (folderidentifier) {
936 #if !GTK_CHECK_VERSION(2, 24, 0)
937 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
939 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
941 prefs_common.compose_save_to_history = add_history(
942 prefs_common.compose_save_to_history, folderidentifier);
943 #if !GTK_CHECK_VERSION(2, 24, 0)
944 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
945 prefs_common.compose_save_to_history);
947 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
948 prefs_common.compose_save_to_history);
952 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
953 if (folderidentifier)
954 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
956 gtk_entry_set_text(GTK_ENTRY(entry), "");
959 static gchar *compose_get_save_to(Compose *compose)
962 gchar *result = NULL;
963 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
964 result = gtk_editable_get_chars(entry, 0, -1);
967 #if !GTK_CHECK_VERSION(2, 24, 0)
968 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
970 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
972 prefs_common.compose_save_to_history = add_history(
973 prefs_common.compose_save_to_history, result);
974 #if !GTK_CHECK_VERSION(2, 24, 0)
975 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
976 prefs_common.compose_save_to_history);
978 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
979 prefs_common.compose_save_to_history);
985 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
986 GList *attach_files, GList *listAddress )
989 GtkTextView *textview;
990 GtkTextBuffer *textbuf;
992 const gchar *subject_format = NULL;
993 const gchar *body_format = NULL;
994 gchar *mailto_from = NULL;
995 PrefsAccount *mailto_account = NULL;
996 MsgInfo* dummyinfo = NULL;
997 gint cursor_pos = -1;
998 MailField mfield = NO_FIELD_PRESENT;
1002 /* check if mailto defines a from */
1003 if (mailto && *mailto != '\0') {
1004 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1005 /* mailto defines a from, check if we can get account prefs from it,
1006 if not, the account prefs will be guessed using other ways, but we'll keep
1009 mailto_account = account_find_from_address(mailto_from, TRUE);
1010 if (mailto_account == NULL) {
1012 Xstrdup_a(tmp_from, mailto_from, return NULL);
1013 extract_address(tmp_from);
1014 mailto_account = account_find_from_address(tmp_from, TRUE);
1018 account = mailto_account;
1021 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1022 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1023 account = account_find_from_id(item->prefs->default_account);
1025 /* if no account prefs set, fallback to the current one */
1026 if (!account) account = cur_account;
1027 cm_return_val_if_fail(account != NULL, NULL);
1029 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1031 /* override from name if mailto asked for it */
1033 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1034 g_free(mailto_from);
1036 /* override from name according to folder properties */
1037 if (item && item->prefs &&
1038 item->prefs->compose_with_format &&
1039 item->prefs->compose_override_from_format &&
1040 *item->prefs->compose_override_from_format != '\0') {
1045 dummyinfo = compose_msginfo_new_from_compose(compose);
1047 /* decode \-escape sequences in the internal representation of the quote format */
1048 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1049 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1052 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1053 compose->gtkaspell);
1055 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1057 quote_fmt_scan_string(tmp);
1060 buf = quote_fmt_get_buffer();
1062 alertpanel_error(_("New message From format error."));
1064 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1065 quote_fmt_reset_vartable();
1070 compose->replyinfo = NULL;
1071 compose->fwdinfo = NULL;
1073 textview = GTK_TEXT_VIEW(compose->text);
1074 textbuf = gtk_text_view_get_buffer(textview);
1075 compose_create_tags(textview, compose);
1077 undo_block(compose->undostruct);
1079 compose_set_dictionaries_from_folder_prefs(compose, item);
1082 if (account->auto_sig)
1083 compose_insert_sig(compose, FALSE);
1084 gtk_text_buffer_get_start_iter(textbuf, &iter);
1085 gtk_text_buffer_place_cursor(textbuf, &iter);
1087 if (account->protocol != A_NNTP) {
1088 if (mailto && *mailto != '\0') {
1089 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1092 compose_set_folder_prefs(compose, item, TRUE);
1094 if (item && item->ret_rcpt) {
1095 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1098 if (mailto && *mailto != '\0') {
1099 if (!strchr(mailto, '@'))
1100 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1102 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1103 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1104 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1105 mfield = TO_FIELD_PRESENT;
1108 * CLAWS: just don't allow return receipt request, even if the user
1109 * may want to send an email. simple but foolproof.
1111 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1113 compose_add_field_list( compose, listAddress );
1115 if (item && item->prefs && item->prefs->compose_with_format) {
1116 subject_format = item->prefs->compose_subject_format;
1117 body_format = item->prefs->compose_body_format;
1118 } else if (account->compose_with_format) {
1119 subject_format = account->compose_subject_format;
1120 body_format = account->compose_body_format;
1121 } else if (prefs_common.compose_with_format) {
1122 subject_format = prefs_common.compose_subject_format;
1123 body_format = prefs_common.compose_body_format;
1126 if (subject_format || body_format) {
1129 && *subject_format != '\0' )
1131 gchar *subject = NULL;
1136 dummyinfo = compose_msginfo_new_from_compose(compose);
1138 /* decode \-escape sequences in the internal representation of the quote format */
1139 tmp = g_malloc(strlen(subject_format)+1);
1140 pref_get_unescaped_pref(tmp, subject_format);
1142 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1144 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1145 compose->gtkaspell);
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1149 quote_fmt_scan_string(tmp);
1152 buf = quote_fmt_get_buffer();
1154 alertpanel_error(_("New message subject format error."));
1156 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1157 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1158 quote_fmt_reset_vartable();
1162 mfield = SUBJECT_FIELD_PRESENT;
1166 && *body_format != '\0' )
1169 GtkTextBuffer *buffer;
1170 GtkTextIter start, end;
1174 dummyinfo = compose_msginfo_new_from_compose(compose);
1176 text = GTK_TEXT_VIEW(compose->text);
1177 buffer = gtk_text_view_get_buffer(text);
1178 gtk_text_buffer_get_start_iter(buffer, &start);
1179 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1180 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1182 compose_quote_fmt(compose, dummyinfo,
1184 NULL, tmp, FALSE, TRUE,
1185 _("The body of the \"New message\" template has an error at line %d."));
1186 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1187 quote_fmt_reset_vartable();
1191 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1192 gtkaspell_highlight_all(compose->gtkaspell);
1194 mfield = BODY_FIELD_PRESENT;
1198 procmsg_msginfo_free( dummyinfo );
1204 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1205 ainfo = (AttachInfo *) curr->data;
1206 compose_attach_append(compose, ainfo->file, ainfo->name,
1207 ainfo->content_type, ainfo->charset);
1211 compose_show_first_last_header(compose, TRUE);
1213 /* Set save folder */
1214 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1215 gchar *folderidentifier;
1217 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1218 folderidentifier = folder_item_get_identifier(item);
1219 compose_set_save_to(compose, folderidentifier);
1220 g_free(folderidentifier);
1223 /* Place cursor according to provided input (mfield) */
1225 case NO_FIELD_PRESENT:
1226 if (compose->header_last)
1227 gtk_widget_grab_focus(compose->header_last->entry);
1229 case TO_FIELD_PRESENT:
1230 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1232 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1235 gtk_widget_grab_focus(compose->subject_entry);
1237 case SUBJECT_FIELD_PRESENT:
1238 textview = GTK_TEXT_VIEW(compose->text);
1241 textbuf = gtk_text_view_get_buffer(textview);
1244 mark = gtk_text_buffer_get_insert(textbuf);
1245 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1246 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1248 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1249 * only defers where it comes to the variable body
1250 * is not null. If no body is present compose->text
1251 * will be null in which case you cannot place the
1252 * cursor inside the component so. An empty component
1253 * is therefore created before placing the cursor
1255 case BODY_FIELD_PRESENT:
1256 cursor_pos = quote_fmt_get_cursor_pos();
1257 if (cursor_pos == -1)
1258 gtk_widget_grab_focus(compose->header_last->entry);
1260 gtk_widget_grab_focus(compose->text);
1264 undo_unblock(compose->undostruct);
1266 if (prefs_common.auto_exteditor)
1267 compose_exec_ext_editor(compose);
1269 compose->draft_timeout_tag = -1;
1270 SCROLL_TO_CURSOR(compose);
1272 compose->modified = FALSE;
1273 compose_set_title(compose);
1275 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1280 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1281 gboolean override_pref, const gchar *system)
1283 const gchar *privacy = NULL;
1285 cm_return_if_fail(compose != NULL);
1286 cm_return_if_fail(account != NULL);
1288 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1291 if (account->default_privacy_system && strlen(account->default_privacy_system))
1292 privacy = account->default_privacy_system;
1296 GSList *privacy_avail = privacy_get_system_ids();
1297 if (privacy_avail && g_slist_length(privacy_avail)) {
1298 privacy = (gchar *)(privacy_avail->data);
1301 if (privacy != NULL) {
1303 g_free(compose->privacy_system);
1304 compose->privacy_system = NULL;
1306 if (compose->privacy_system == NULL)
1307 compose->privacy_system = g_strdup(privacy);
1308 else if (*(compose->privacy_system) == '\0') {
1309 g_free(compose->privacy_system);
1310 compose->privacy_system = g_strdup(privacy);
1312 compose_update_privacy_system_menu_item(compose, FALSE);
1313 compose_use_encryption(compose, TRUE);
1317 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1319 const gchar *privacy = NULL;
1321 if (account->default_privacy_system && strlen(account->default_privacy_system))
1322 privacy = account->default_privacy_system;
1326 GSList *privacy_avail = privacy_get_system_ids();
1327 if (privacy_avail && g_slist_length(privacy_avail)) {
1328 privacy = (gchar *)(privacy_avail->data);
1332 if (privacy != NULL) {
1334 g_free(compose->privacy_system);
1335 compose->privacy_system = NULL;
1337 if (compose->privacy_system == NULL)
1338 compose->privacy_system = g_strdup(privacy);
1339 compose_update_privacy_system_menu_item(compose, FALSE);
1340 compose_use_signing(compose, TRUE);
1344 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1348 Compose *compose = NULL;
1350 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1352 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1353 cm_return_val_if_fail(msginfo != NULL, NULL);
1355 list_len = g_slist_length(msginfo_list);
1359 case COMPOSE_REPLY_TO_ADDRESS:
1360 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1361 FALSE, prefs_common.default_reply_list, FALSE, body);
1363 case COMPOSE_REPLY_WITH_QUOTE:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1365 FALSE, prefs_common.default_reply_list, FALSE, body);
1367 case COMPOSE_REPLY_WITHOUT_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1369 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1371 case COMPOSE_REPLY_TO_SENDER:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1373 FALSE, FALSE, TRUE, body);
1375 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1376 compose = compose_followup_and_reply_to(msginfo,
1377 COMPOSE_QUOTE_CHECK,
1378 FALSE, FALSE, body);
1380 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1381 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1382 FALSE, FALSE, TRUE, body);
1384 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1386 FALSE, FALSE, TRUE, NULL);
1388 case COMPOSE_REPLY_TO_ALL:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1390 TRUE, FALSE, FALSE, body);
1392 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1394 TRUE, FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1398 TRUE, FALSE, FALSE, NULL);
1400 case COMPOSE_REPLY_TO_LIST:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1402 FALSE, TRUE, FALSE, body);
1404 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1406 FALSE, TRUE, FALSE, body);
1408 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1410 FALSE, TRUE, FALSE, NULL);
1412 case COMPOSE_FORWARD:
1413 if (prefs_common.forward_as_attachment) {
1414 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1417 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1421 case COMPOSE_FORWARD_INLINE:
1422 /* check if we reply to more than one Message */
1423 if (list_len == 1) {
1424 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1427 /* more messages FALL THROUGH */
1428 case COMPOSE_FORWARD_AS_ATTACH:
1429 compose = compose_forward_multiple(NULL, msginfo_list);
1431 case COMPOSE_REDIRECT:
1432 compose = compose_redirect(NULL, msginfo, FALSE);
1435 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1438 if (compose == NULL) {
1439 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1443 compose->rmode = mode;
1444 switch (compose->rmode) {
1446 case COMPOSE_REPLY_WITH_QUOTE:
1447 case COMPOSE_REPLY_WITHOUT_QUOTE:
1448 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1449 debug_print("reply mode Normal\n");
1450 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1451 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1453 case COMPOSE_REPLY_TO_SENDER:
1454 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1455 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1456 debug_print("reply mode Sender\n");
1457 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1459 case COMPOSE_REPLY_TO_ALL:
1460 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1461 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1462 debug_print("reply mode All\n");
1463 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1465 case COMPOSE_REPLY_TO_LIST:
1466 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1467 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1468 debug_print("reply mode List\n");
1469 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1471 case COMPOSE_REPLY_TO_ADDRESS:
1472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1480 static Compose *compose_reply(MsgInfo *msginfo,
1481 ComposeQuoteMode quote_mode,
1487 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1488 to_sender, FALSE, body);
1491 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1492 ComposeQuoteMode quote_mode,
1497 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1498 to_sender, TRUE, body);
1501 static void compose_extract_original_charset(Compose *compose)
1503 MsgInfo *info = NULL;
1504 if (compose->replyinfo) {
1505 info = compose->replyinfo;
1506 } else if (compose->fwdinfo) {
1507 info = compose->fwdinfo;
1508 } else if (compose->targetinfo) {
1509 info = compose->targetinfo;
1512 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1513 MimeInfo *partinfo = mimeinfo;
1514 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1515 partinfo = procmime_mimeinfo_next(partinfo);
1517 compose->orig_charset =
1518 g_strdup(procmime_mimeinfo_get_parameter(
1519 partinfo, "charset"));
1521 procmime_mimeinfo_free_all(mimeinfo);
1525 #define SIGNAL_BLOCK(buffer) { \
1526 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1527 G_CALLBACK(compose_changed_cb), \
1529 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1530 G_CALLBACK(text_inserted), \
1534 #define SIGNAL_UNBLOCK(buffer) { \
1535 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(compose_changed_cb), \
1538 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1539 G_CALLBACK(text_inserted), \
1543 static Compose *compose_generic_reply(MsgInfo *msginfo,
1544 ComposeQuoteMode quote_mode,
1545 gboolean to_all, gboolean to_ml,
1547 gboolean followup_and_reply_to,
1551 PrefsAccount *account = NULL;
1552 GtkTextView *textview;
1553 GtkTextBuffer *textbuf;
1554 gboolean quote = FALSE;
1555 const gchar *qmark = NULL;
1556 const gchar *body_fmt = NULL;
1557 gchar *s_system = NULL;
1559 cm_return_val_if_fail(msginfo != NULL, NULL);
1560 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1562 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1564 cm_return_val_if_fail(account != NULL, NULL);
1566 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1568 compose->updating = TRUE;
1570 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1571 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1573 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1574 if (!compose->replyinfo)
1575 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1577 compose_extract_original_charset(compose);
1579 if (msginfo->folder && msginfo->folder->ret_rcpt)
1580 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1582 /* Set save folder */
1583 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1584 gchar *folderidentifier;
1586 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1587 folderidentifier = folder_item_get_identifier(msginfo->folder);
1588 compose_set_save_to(compose, folderidentifier);
1589 g_free(folderidentifier);
1592 if (compose_parse_header(compose, msginfo) < 0) {
1593 compose->updating = FALSE;
1594 compose_destroy(compose);
1598 /* override from name according to folder properties */
1599 if (msginfo->folder && msginfo->folder->prefs &&
1600 msginfo->folder->prefs->reply_with_format &&
1601 msginfo->folder->prefs->reply_override_from_format &&
1602 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1607 /* decode \-escape sequences in the internal representation of the quote format */
1608 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1609 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1613 compose->gtkaspell);
1615 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1617 quote_fmt_scan_string(tmp);
1620 buf = quote_fmt_get_buffer();
1622 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1624 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1625 quote_fmt_reset_vartable();
1630 textview = (GTK_TEXT_VIEW(compose->text));
1631 textbuf = gtk_text_view_get_buffer(textview);
1632 compose_create_tags(textview, compose);
1634 undo_block(compose->undostruct);
1636 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1637 gtkaspell_block_check(compose->gtkaspell);
1640 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1641 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1642 /* use the reply format of folder (if enabled), or the account's one
1643 (if enabled) or fallback to the global reply format, which is always
1644 enabled (even if empty), and use the relevant quotemark */
1646 if (msginfo->folder && msginfo->folder->prefs &&
1647 msginfo->folder->prefs->reply_with_format) {
1648 qmark = msginfo->folder->prefs->reply_quotemark;
1649 body_fmt = msginfo->folder->prefs->reply_body_format;
1651 } else if (account->reply_with_format) {
1652 qmark = account->reply_quotemark;
1653 body_fmt = account->reply_body_format;
1656 qmark = prefs_common.quotemark;
1657 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1658 body_fmt = gettext(prefs_common.quotefmt);
1665 /* empty quotemark is not allowed */
1666 if (qmark == NULL || *qmark == '\0')
1668 compose_quote_fmt(compose, compose->replyinfo,
1669 body_fmt, qmark, body, FALSE, TRUE,
1670 _("The body of the \"Reply\" template has an error at line %d."));
1671 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1672 quote_fmt_reset_vartable();
1675 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1676 compose_force_encryption(compose, account, FALSE, s_system);
1679 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1680 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1681 compose_force_signing(compose, account, s_system);
1685 SIGNAL_BLOCK(textbuf);
1687 if (account->auto_sig)
1688 compose_insert_sig(compose, FALSE);
1690 compose_wrap_all(compose);
1693 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1694 gtkaspell_highlight_all(compose->gtkaspell);
1695 gtkaspell_unblock_check(compose->gtkaspell);
1697 SIGNAL_UNBLOCK(textbuf);
1699 gtk_widget_grab_focus(compose->text);
1701 undo_unblock(compose->undostruct);
1703 if (prefs_common.auto_exteditor)
1704 compose_exec_ext_editor(compose);
1706 compose->modified = FALSE;
1707 compose_set_title(compose);
1709 compose->updating = FALSE;
1710 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1711 SCROLL_TO_CURSOR(compose);
1713 if (compose->deferred_destroy) {
1714 compose_destroy(compose);
1722 #define INSERT_FW_HEADER(var, hdr) \
1723 if (msginfo->var && *msginfo->var) { \
1724 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1725 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1726 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1729 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1730 gboolean as_attach, const gchar *body,
1731 gboolean no_extedit,
1735 GtkTextView *textview;
1736 GtkTextBuffer *textbuf;
1737 gint cursor_pos = -1;
1740 cm_return_val_if_fail(msginfo != NULL, NULL);
1741 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1744 !(account = compose_guess_forward_account_from_msginfo
1746 account = cur_account;
1748 if (!prefs_common.forward_as_attachment)
1749 mode = COMPOSE_FORWARD_INLINE;
1751 mode = COMPOSE_FORWARD;
1752 compose = compose_create(account, msginfo->folder, mode, batch);
1754 compose->updating = TRUE;
1755 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1756 if (!compose->fwdinfo)
1757 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1759 compose_extract_original_charset(compose);
1761 if (msginfo->subject && *msginfo->subject) {
1762 gchar *buf, *buf2, *p;
1764 buf = p = g_strdup(msginfo->subject);
1765 p += subject_get_prefix_length(p);
1766 memmove(buf, p, strlen(p) + 1);
1768 buf2 = g_strdup_printf("Fw: %s", buf);
1769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1775 /* override from name according to folder properties */
1776 if (msginfo->folder && msginfo->folder->prefs &&
1777 msginfo->folder->prefs->forward_with_format &&
1778 msginfo->folder->prefs->forward_override_from_format &&
1779 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1783 MsgInfo *full_msginfo = NULL;
1786 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1788 full_msginfo = procmsg_msginfo_copy(msginfo);
1790 /* decode \-escape sequences in the internal representation of the quote format */
1791 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1792 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1795 gtkaspell_block_check(compose->gtkaspell);
1796 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1797 compose->gtkaspell);
1799 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1801 quote_fmt_scan_string(tmp);
1804 buf = quote_fmt_get_buffer();
1806 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1808 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1809 quote_fmt_reset_vartable();
1812 procmsg_msginfo_free(full_msginfo);
1815 textview = GTK_TEXT_VIEW(compose->text);
1816 textbuf = gtk_text_view_get_buffer(textview);
1817 compose_create_tags(textview, compose);
1819 undo_block(compose->undostruct);
1823 msgfile = procmsg_get_message_file(msginfo);
1824 if (!is_file_exist(msgfile))
1825 g_warning("%s: file not exist\n", msgfile);
1827 compose_attach_append(compose, msgfile, msgfile,
1828 "message/rfc822", NULL);
1832 const gchar *qmark = NULL;
1833 const gchar *body_fmt = NULL;
1834 MsgInfo *full_msginfo;
1836 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1838 full_msginfo = procmsg_msginfo_copy(msginfo);
1840 /* use the forward format of folder (if enabled), or the account's one
1841 (if enabled) or fallback to the global forward format, which is always
1842 enabled (even if empty), and use the relevant quotemark */
1843 if (msginfo->folder && msginfo->folder->prefs &&
1844 msginfo->folder->prefs->forward_with_format) {
1845 qmark = msginfo->folder->prefs->forward_quotemark;
1846 body_fmt = msginfo->folder->prefs->forward_body_format;
1848 } else if (account->forward_with_format) {
1849 qmark = account->forward_quotemark;
1850 body_fmt = account->forward_body_format;
1853 qmark = prefs_common.fw_quotemark;
1854 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1855 body_fmt = gettext(prefs_common.fw_quotefmt);
1860 /* empty quotemark is not allowed */
1861 if (qmark == NULL || *qmark == '\0')
1864 compose_quote_fmt(compose, full_msginfo,
1865 body_fmt, qmark, body, FALSE, TRUE,
1866 _("The body of the \"Forward\" template has an error at line %d."));
1867 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1868 quote_fmt_reset_vartable();
1869 compose_attach_parts(compose, msginfo);
1871 procmsg_msginfo_free(full_msginfo);
1874 SIGNAL_BLOCK(textbuf);
1876 if (account->auto_sig)
1877 compose_insert_sig(compose, FALSE);
1879 compose_wrap_all(compose);
1882 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1883 gtkaspell_highlight_all(compose->gtkaspell);
1884 gtkaspell_unblock_check(compose->gtkaspell);
1886 SIGNAL_UNBLOCK(textbuf);
1888 cursor_pos = quote_fmt_get_cursor_pos();
1889 if (cursor_pos == -1)
1890 gtk_widget_grab_focus(compose->header_last->entry);
1892 gtk_widget_grab_focus(compose->text);
1894 if (!no_extedit && prefs_common.auto_exteditor)
1895 compose_exec_ext_editor(compose);
1898 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1899 gchar *folderidentifier;
1901 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1902 folderidentifier = folder_item_get_identifier(msginfo->folder);
1903 compose_set_save_to(compose, folderidentifier);
1904 g_free(folderidentifier);
1907 undo_unblock(compose->undostruct);
1909 compose->modified = FALSE;
1910 compose_set_title(compose);
1912 compose->updating = FALSE;
1913 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1914 SCROLL_TO_CURSOR(compose);
1916 if (compose->deferred_destroy) {
1917 compose_destroy(compose);
1921 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1926 #undef INSERT_FW_HEADER
1928 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1931 GtkTextView *textview;
1932 GtkTextBuffer *textbuf;
1936 gboolean single_mail = TRUE;
1938 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1940 if (g_slist_length(msginfo_list) > 1)
1941 single_mail = FALSE;
1943 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1944 if (((MsgInfo *)msginfo->data)->folder == NULL)
1947 /* guess account from first selected message */
1949 !(account = compose_guess_forward_account_from_msginfo
1950 (msginfo_list->data)))
1951 account = cur_account;
1953 cm_return_val_if_fail(account != NULL, NULL);
1955 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1956 if (msginfo->data) {
1957 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1958 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1962 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1963 g_warning("no msginfo_list");
1967 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1969 compose->updating = TRUE;
1971 /* override from name according to folder properties */
1972 if (msginfo_list->data) {
1973 MsgInfo *msginfo = msginfo_list->data;
1975 if (msginfo->folder && msginfo->folder->prefs &&
1976 msginfo->folder->prefs->forward_with_format &&
1977 msginfo->folder->prefs->forward_override_from_format &&
1978 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1983 /* decode \-escape sequences in the internal representation of the quote format */
1984 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1985 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1988 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1989 compose->gtkaspell);
1991 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1993 quote_fmt_scan_string(tmp);
1996 buf = quote_fmt_get_buffer();
1998 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2000 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2001 quote_fmt_reset_vartable();
2007 textview = GTK_TEXT_VIEW(compose->text);
2008 textbuf = gtk_text_view_get_buffer(textview);
2009 compose_create_tags(textview, compose);
2011 undo_block(compose->undostruct);
2012 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2013 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2015 if (!is_file_exist(msgfile))
2016 g_warning("%s: file not exist\n", msgfile);
2018 compose_attach_append(compose, msgfile, msgfile,
2019 "message/rfc822", NULL);
2024 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2025 if (info->subject && *info->subject) {
2026 gchar *buf, *buf2, *p;
2028 buf = p = g_strdup(info->subject);
2029 p += subject_get_prefix_length(p);
2030 memmove(buf, p, strlen(p) + 1);
2032 buf2 = g_strdup_printf("Fw: %s", buf);
2033 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2039 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2040 _("Fw: multiple emails"));
2043 SIGNAL_BLOCK(textbuf);
2045 if (account->auto_sig)
2046 compose_insert_sig(compose, FALSE);
2048 compose_wrap_all(compose);
2050 SIGNAL_UNBLOCK(textbuf);
2052 gtk_text_buffer_get_start_iter(textbuf, &iter);
2053 gtk_text_buffer_place_cursor(textbuf, &iter);
2055 gtk_widget_grab_focus(compose->header_last->entry);
2056 undo_unblock(compose->undostruct);
2057 compose->modified = FALSE;
2058 compose_set_title(compose);
2060 compose->updating = FALSE;
2061 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2062 SCROLL_TO_CURSOR(compose);
2064 if (compose->deferred_destroy) {
2065 compose_destroy(compose);
2069 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2074 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2076 GtkTextIter start = *iter;
2077 GtkTextIter end_iter;
2078 int start_pos = gtk_text_iter_get_offset(&start);
2080 if (!compose->account->sig_sep)
2083 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2084 start_pos+strlen(compose->account->sig_sep));
2086 /* check sig separator */
2087 str = gtk_text_iter_get_text(&start, &end_iter);
2088 if (!strcmp(str, compose->account->sig_sep)) {
2090 /* check end of line (\n) */
2091 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2092 start_pos+strlen(compose->account->sig_sep));
2093 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2094 start_pos+strlen(compose->account->sig_sep)+1);
2095 tmp = gtk_text_iter_get_text(&start, &end_iter);
2096 if (!strcmp(tmp,"\n")) {
2108 static void compose_colorize_signature(Compose *compose)
2110 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2112 GtkTextIter end_iter;
2113 gtk_text_buffer_get_start_iter(buffer, &iter);
2114 while (gtk_text_iter_forward_line(&iter))
2115 if (compose_is_sig_separator(compose, buffer, &iter)) {
2116 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2117 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2121 #define BLOCK_WRAP() { \
2122 prev_autowrap = compose->autowrap; \
2123 buffer = gtk_text_view_get_buffer( \
2124 GTK_TEXT_VIEW(compose->text)); \
2125 compose->autowrap = FALSE; \
2127 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2128 G_CALLBACK(compose_changed_cb), \
2130 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2131 G_CALLBACK(text_inserted), \
2134 #define UNBLOCK_WRAP() { \
2135 compose->autowrap = prev_autowrap; \
2136 if (compose->autowrap) { \
2137 gint old = compose->draft_timeout_tag; \
2138 compose->draft_timeout_tag = -2; \
2139 compose_wrap_all(compose); \
2140 compose->draft_timeout_tag = old; \
2143 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2144 G_CALLBACK(compose_changed_cb), \
2146 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2147 G_CALLBACK(text_inserted), \
2151 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2153 Compose *compose = NULL;
2154 PrefsAccount *account = NULL;
2155 GtkTextView *textview;
2156 GtkTextBuffer *textbuf;
2160 gchar buf[BUFFSIZE];
2161 gboolean use_signing = FALSE;
2162 gboolean use_encryption = FALSE;
2163 gchar *privacy_system = NULL;
2164 int priority = PRIORITY_NORMAL;
2165 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2166 gboolean autowrap = prefs_common.autowrap;
2167 gboolean autoindent = prefs_common.auto_indent;
2168 HeaderEntry *manual_headers = NULL;
2170 cm_return_val_if_fail(msginfo != NULL, NULL);
2171 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2173 if (compose_put_existing_to_front(msginfo)) {
2177 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2178 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2179 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2180 gchar queueheader_buf[BUFFSIZE];
2183 /* Select Account from queue headers */
2184 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2185 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2186 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2187 account = account_find_from_id(id);
2189 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2190 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2191 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2192 account = account_find_from_id(id);
2194 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2195 sizeof(queueheader_buf), "NAID:")) {
2196 id = atoi(&queueheader_buf[strlen("NAID:")]);
2197 account = account_find_from_id(id);
2199 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2200 sizeof(queueheader_buf), "MAID:")) {
2201 id = atoi(&queueheader_buf[strlen("MAID:")]);
2202 account = account_find_from_id(id);
2204 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2205 sizeof(queueheader_buf), "S:")) {
2206 account = account_find_from_address(queueheader_buf, FALSE);
2208 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2209 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2210 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2211 use_signing = param;
2214 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2215 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2216 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2217 use_signing = param;
2220 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2221 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2222 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2223 use_encryption = param;
2225 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2226 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2227 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2228 use_encryption = param;
2230 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2231 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2232 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2235 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2236 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2237 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2240 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2241 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2242 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2244 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2245 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2246 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2248 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2249 sizeof(queueheader_buf), "X-Priority: ")) {
2250 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2253 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2254 sizeof(queueheader_buf), "RMID:")) {
2255 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2256 if (tokens[0] && tokens[1] && tokens[2]) {
2257 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2258 if (orig_item != NULL) {
2259 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2264 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2265 sizeof(queueheader_buf), "FMID:")) {
2266 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2267 if (tokens[0] && tokens[1] && tokens[2]) {
2268 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2269 if (orig_item != NULL) {
2270 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2275 /* Get manual headers */
2276 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2277 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2278 if (*listmh != '\0') {
2279 debug_print("Got manual headers: %s\n", listmh);
2280 manual_headers = procheader_entries_from_str(listmh);
2285 account = msginfo->folder->folder->account;
2288 if (!account && prefs_common.reedit_account_autosel) {
2289 gchar from[BUFFSIZE];
2290 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2291 extract_address(from);
2292 account = account_find_from_address(from, FALSE);
2296 account = cur_account;
2298 cm_return_val_if_fail(account != NULL, NULL);
2300 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2302 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2303 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2304 compose->autowrap = autowrap;
2305 compose->replyinfo = replyinfo;
2306 compose->fwdinfo = fwdinfo;
2308 compose->updating = TRUE;
2309 compose->priority = priority;
2311 if (privacy_system != NULL) {
2312 compose->privacy_system = privacy_system;
2313 compose_use_signing(compose, use_signing);
2314 compose_use_encryption(compose, use_encryption);
2315 compose_update_privacy_system_menu_item(compose, FALSE);
2317 activate_privacy_system(compose, account, FALSE);
2320 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2322 compose_extract_original_charset(compose);
2324 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2325 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2326 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2327 gchar queueheader_buf[BUFFSIZE];
2329 /* Set message save folder */
2330 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2331 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2332 compose_set_save_to(compose, &queueheader_buf[4]);
2334 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2335 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2337 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2342 if (compose_parse_header(compose, msginfo) < 0) {
2343 compose->updating = FALSE;
2344 compose_destroy(compose);
2347 compose_reedit_set_entry(compose, msginfo);
2349 textview = GTK_TEXT_VIEW(compose->text);
2350 textbuf = gtk_text_view_get_buffer(textview);
2351 compose_create_tags(textview, compose);
2353 mark = gtk_text_buffer_get_insert(textbuf);
2354 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2356 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2357 G_CALLBACK(compose_changed_cb),
2360 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2361 fp = procmime_get_first_encrypted_text_content(msginfo);
2363 compose_force_encryption(compose, account, TRUE, NULL);
2366 fp = procmime_get_first_text_content(msginfo);
2369 g_warning("Can't get text part\n");
2373 gboolean prev_autowrap;
2374 GtkTextBuffer *buffer;
2376 while (fgets(buf, sizeof(buf), fp) != NULL) {
2378 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2384 compose_attach_parts(compose, msginfo);
2386 compose_colorize_signature(compose);
2388 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2389 G_CALLBACK(compose_changed_cb),
2392 if (manual_headers != NULL) {
2393 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2394 procheader_entries_free(manual_headers);
2395 compose->updating = FALSE;
2396 compose_destroy(compose);
2399 procheader_entries_free(manual_headers);
2402 gtk_widget_grab_focus(compose->text);
2404 if (prefs_common.auto_exteditor) {
2405 compose_exec_ext_editor(compose);
2407 compose->modified = FALSE;
2408 compose_set_title(compose);
2410 compose->updating = FALSE;
2411 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2412 SCROLL_TO_CURSOR(compose);
2414 if (compose->deferred_destroy) {
2415 compose_destroy(compose);
2419 compose->sig_str = account_get_signature_str(compose->account);
2421 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2426 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2433 cm_return_val_if_fail(msginfo != NULL, NULL);
2436 account = account_get_reply_account(msginfo,
2437 prefs_common.reply_account_autosel);
2438 cm_return_val_if_fail(account != NULL, NULL);
2440 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2442 compose->updating = TRUE;
2444 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2445 compose->replyinfo = NULL;
2446 compose->fwdinfo = NULL;
2448 compose_show_first_last_header(compose, TRUE);
2450 gtk_widget_grab_focus(compose->header_last->entry);
2452 filename = procmsg_get_message_file(msginfo);
2454 if (filename == NULL) {
2455 compose->updating = FALSE;
2456 compose_destroy(compose);
2461 compose->redirect_filename = filename;
2463 /* Set save folder */
2464 item = msginfo->folder;
2465 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2466 gchar *folderidentifier;
2468 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2469 folderidentifier = folder_item_get_identifier(item);
2470 compose_set_save_to(compose, folderidentifier);
2471 g_free(folderidentifier);
2474 compose_attach_parts(compose, msginfo);
2476 if (msginfo->subject)
2477 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2479 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2481 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2482 _("The body of the \"Redirect\" template has an error at line %d."));
2483 quote_fmt_reset_vartable();
2484 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2486 compose_colorize_signature(compose);
2489 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2490 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2491 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2493 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2494 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2495 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2496 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2497 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2498 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2499 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2500 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2501 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2503 if (compose->toolbar->draft_btn)
2504 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2505 if (compose->toolbar->insert_btn)
2506 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2507 if (compose->toolbar->attach_btn)
2508 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2509 if (compose->toolbar->sig_btn)
2510 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2511 if (compose->toolbar->exteditor_btn)
2512 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2513 if (compose->toolbar->linewrap_current_btn)
2514 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2515 if (compose->toolbar->linewrap_all_btn)
2516 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2518 compose->modified = FALSE;
2519 compose_set_title(compose);
2520 compose->updating = FALSE;
2521 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2522 SCROLL_TO_CURSOR(compose);
2524 if (compose->deferred_destroy) {
2525 compose_destroy(compose);
2529 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2534 GList *compose_get_compose_list(void)
2536 return compose_list;
2539 void compose_entry_append(Compose *compose, const gchar *address,
2540 ComposeEntryType type, ComposePrefType pref_type)
2542 const gchar *header;
2544 gboolean in_quote = FALSE;
2545 if (!address || *address == '\0') return;
2552 header = N_("Bcc:");
2554 case COMPOSE_REPLYTO:
2555 header = N_("Reply-To:");
2557 case COMPOSE_NEWSGROUPS:
2558 header = N_("Newsgroups:");
2560 case COMPOSE_FOLLOWUPTO:
2561 header = N_( "Followup-To:");
2563 case COMPOSE_INREPLYTO:
2564 header = N_( "In-Reply-To:");
2571 header = prefs_common_translated_header_name(header);
2573 cur = begin = (gchar *)address;
2575 /* we separate the line by commas, but not if we're inside a quoted
2577 while (*cur != '\0') {
2579 in_quote = !in_quote;
2580 if (*cur == ',' && !in_quote) {
2581 gchar *tmp = g_strdup(begin);
2583 tmp[cur-begin]='\0';
2586 while (*tmp == ' ' || *tmp == '\t')
2588 compose_add_header_entry(compose, header, tmp, pref_type);
2595 gchar *tmp = g_strdup(begin);
2597 tmp[cur-begin]='\0';
2598 while (*tmp == ' ' || *tmp == '\t')
2600 compose_add_header_entry(compose, header, tmp, pref_type);
2605 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2607 #if !GTK_CHECK_VERSION(3, 0, 0)
2608 static GdkColor yellow;
2609 static GdkColor black;
2610 static gboolean yellow_initialised = FALSE;
2612 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2613 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2618 #if !GTK_CHECK_VERSION(3, 0, 0)
2619 if (!yellow_initialised) {
2620 gdk_color_parse("#f5f6be", &yellow);
2621 gdk_color_parse("#000000", &black);
2622 yellow_initialised = gdk_colormap_alloc_color(
2623 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2624 yellow_initialised &= gdk_colormap_alloc_color(
2625 gdk_colormap_get_system(), &black, FALSE, TRUE);
2629 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2630 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2631 if (gtk_entry_get_text(entry) &&
2632 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2633 #if !GTK_CHECK_VERSION(3, 0, 0)
2634 if (yellow_initialised) {
2636 gtk_widget_modify_base(
2637 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2638 GTK_STATE_NORMAL, &yellow);
2639 gtk_widget_modify_text(
2640 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2641 GTK_STATE_NORMAL, &black);
2642 #if !GTK_CHECK_VERSION(3, 0, 0)
2649 void compose_toolbar_cb(gint action, gpointer data)
2651 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2652 Compose *compose = (Compose*)toolbar_item->parent;
2654 cm_return_if_fail(compose != NULL);
2658 compose_send_cb(NULL, compose);
2661 compose_send_later_cb(NULL, compose);
2664 compose_draft(compose, COMPOSE_QUIT_EDITING);
2667 compose_insert_file_cb(NULL, compose);
2670 compose_attach_cb(NULL, compose);
2673 compose_insert_sig(compose, FALSE);
2676 compose_insert_sig(compose, TRUE);
2679 compose_ext_editor_cb(NULL, compose);
2681 case A_LINEWRAP_CURRENT:
2682 compose_beautify_paragraph(compose, NULL, TRUE);
2684 case A_LINEWRAP_ALL:
2685 compose_wrap_all_full(compose, TRUE);
2688 compose_address_cb(NULL, compose);
2691 case A_CHECK_SPELLING:
2692 compose_check_all(NULL, compose);
2700 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2705 gchar *subject = NULL;
2709 gchar **attach = NULL;
2710 gchar *inreplyto = NULL;
2711 MailField mfield = NO_FIELD_PRESENT;
2713 /* get mailto parts but skip from */
2714 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2717 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2718 mfield = TO_FIELD_PRESENT;
2721 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2723 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2725 if (!g_utf8_validate (subject, -1, NULL)) {
2726 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2727 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2730 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2732 mfield = SUBJECT_FIELD_PRESENT;
2735 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2736 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2739 gboolean prev_autowrap = compose->autowrap;
2741 compose->autowrap = FALSE;
2743 mark = gtk_text_buffer_get_insert(buffer);
2744 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2746 if (!g_utf8_validate (body, -1, NULL)) {
2747 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2748 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2751 gtk_text_buffer_insert(buffer, &iter, body, -1);
2753 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2755 compose->autowrap = prev_autowrap;
2756 if (compose->autowrap)
2757 compose_wrap_all(compose);
2758 mfield = BODY_FIELD_PRESENT;
2762 gint i = 0, att = 0;
2763 gchar *warn_files = NULL;
2764 while (attach[i] != NULL) {
2765 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2766 if (utf8_filename) {
2767 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2768 gchar *tmp = g_strdup_printf("%s%s\n",
2769 warn_files?warn_files:"",
2775 g_free(utf8_filename);
2777 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2782 alertpanel_notice(ngettext(
2783 "The following file has been attached: \n%s",
2784 "The following files have been attached: \n%s", att), warn_files);
2789 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2802 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2804 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2805 {"Cc:", NULL, TRUE},
2806 {"References:", NULL, FALSE},
2807 {"Bcc:", NULL, TRUE},
2808 {"Newsgroups:", NULL, TRUE},
2809 {"Followup-To:", NULL, TRUE},
2810 {"List-Post:", NULL, FALSE},
2811 {"X-Priority:", NULL, FALSE},
2812 {NULL, NULL, FALSE}};
2828 cm_return_val_if_fail(msginfo != NULL, -1);
2830 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2831 procheader_get_header_fields(fp, hentry);
2834 if (hentry[H_REPLY_TO].body != NULL) {
2835 if (hentry[H_REPLY_TO].body[0] != '\0') {
2837 conv_unmime_header(hentry[H_REPLY_TO].body,
2840 g_free(hentry[H_REPLY_TO].body);
2841 hentry[H_REPLY_TO].body = NULL;
2843 if (hentry[H_CC].body != NULL) {
2844 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2845 g_free(hentry[H_CC].body);
2846 hentry[H_CC].body = NULL;
2848 if (hentry[H_REFERENCES].body != NULL) {
2849 if (compose->mode == COMPOSE_REEDIT)
2850 compose->references = hentry[H_REFERENCES].body;
2852 compose->references = compose_parse_references
2853 (hentry[H_REFERENCES].body, msginfo->msgid);
2854 g_free(hentry[H_REFERENCES].body);
2856 hentry[H_REFERENCES].body = NULL;
2858 if (hentry[H_BCC].body != NULL) {
2859 if (compose->mode == COMPOSE_REEDIT)
2861 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2862 g_free(hentry[H_BCC].body);
2863 hentry[H_BCC].body = NULL;
2865 if (hentry[H_NEWSGROUPS].body != NULL) {
2866 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2867 hentry[H_NEWSGROUPS].body = NULL;
2869 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2870 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2871 compose->followup_to =
2872 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2875 g_free(hentry[H_FOLLOWUP_TO].body);
2876 hentry[H_FOLLOWUP_TO].body = NULL;
2878 if (hentry[H_LIST_POST].body != NULL) {
2879 gchar *to = NULL, *start = NULL;
2881 extract_address(hentry[H_LIST_POST].body);
2882 if (hentry[H_LIST_POST].body[0] != '\0') {
2883 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2885 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2886 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2889 g_free(compose->ml_post);
2890 compose->ml_post = to;
2893 g_free(hentry[H_LIST_POST].body);
2894 hentry[H_LIST_POST].body = NULL;
2897 /* CLAWS - X-Priority */
2898 if (compose->mode == COMPOSE_REEDIT)
2899 if (hentry[H_X_PRIORITY].body != NULL) {
2902 priority = atoi(hentry[H_X_PRIORITY].body);
2903 g_free(hentry[H_X_PRIORITY].body);
2905 hentry[H_X_PRIORITY].body = NULL;
2907 if (priority < PRIORITY_HIGHEST ||
2908 priority > PRIORITY_LOWEST)
2909 priority = PRIORITY_NORMAL;
2911 compose->priority = priority;
2914 if (compose->mode == COMPOSE_REEDIT) {
2915 if (msginfo->inreplyto && *msginfo->inreplyto)
2916 compose->inreplyto = g_strdup(msginfo->inreplyto);
2920 if (msginfo->msgid && *msginfo->msgid)
2921 compose->inreplyto = g_strdup(msginfo->msgid);
2923 if (!compose->references) {
2924 if (msginfo->msgid && *msginfo->msgid) {
2925 if (msginfo->inreplyto && *msginfo->inreplyto)
2926 compose->references =
2927 g_strdup_printf("<%s>\n\t<%s>",
2931 compose->references =
2932 g_strconcat("<", msginfo->msgid, ">",
2934 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2935 compose->references =
2936 g_strconcat("<", msginfo->inreplyto, ">",
2944 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2949 cm_return_val_if_fail(msginfo != NULL, -1);
2951 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2952 procheader_get_header_fields(fp, entries);
2956 while (he != NULL && he->name != NULL) {
2958 GtkListStore *model = NULL;
2960 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2961 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2962 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2963 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2964 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2971 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2973 GSList *ref_id_list, *cur;
2977 ref_id_list = references_list_append(NULL, ref);
2978 if (!ref_id_list) return NULL;
2979 if (msgid && *msgid)
2980 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2985 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2986 /* "<" + Message-ID + ">" + CR+LF+TAB */
2987 len += strlen((gchar *)cur->data) + 5;
2989 if (len > MAX_REFERENCES_LEN) {
2990 /* remove second message-ID */
2991 if (ref_id_list && ref_id_list->next &&
2992 ref_id_list->next->next) {
2993 g_free(ref_id_list->next->data);
2994 ref_id_list = g_slist_remove
2995 (ref_id_list, ref_id_list->next->data);
2997 slist_free_strings_full(ref_id_list);
3004 new_ref = g_string_new("");
3005 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3006 if (new_ref->len > 0)
3007 g_string_append(new_ref, "\n\t");
3008 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3011 slist_free_strings_full(ref_id_list);
3013 new_ref_str = new_ref->str;
3014 g_string_free(new_ref, FALSE);
3019 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3020 const gchar *fmt, const gchar *qmark,
3021 const gchar *body, gboolean rewrap,
3022 gboolean need_unescape,
3023 const gchar *err_msg)
3025 MsgInfo* dummyinfo = NULL;
3026 gchar *quote_str = NULL;
3028 gboolean prev_autowrap;
3029 const gchar *trimmed_body = body;
3030 gint cursor_pos = -1;
3031 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3032 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3037 SIGNAL_BLOCK(buffer);
3040 dummyinfo = compose_msginfo_new_from_compose(compose);
3041 msginfo = dummyinfo;
3044 if (qmark != NULL) {
3046 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3047 compose->gtkaspell);
3049 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3051 quote_fmt_scan_string(qmark);
3054 buf = quote_fmt_get_buffer();
3056 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3058 Xstrdup_a(quote_str, buf, goto error)
3061 if (fmt && *fmt != '\0') {
3064 while (*trimmed_body == '\n')
3068 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3069 compose->gtkaspell);
3071 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3073 if (need_unescape) {
3076 /* decode \-escape sequences in the internal representation of the quote format */
3077 tmp = g_malloc(strlen(fmt)+1);
3078 pref_get_unescaped_pref(tmp, fmt);
3079 quote_fmt_scan_string(tmp);
3083 quote_fmt_scan_string(fmt);
3087 buf = quote_fmt_get_buffer();
3089 gint line = quote_fmt_get_line();
3090 alertpanel_error(err_msg, line);
3096 prev_autowrap = compose->autowrap;
3097 compose->autowrap = FALSE;
3099 mark = gtk_text_buffer_get_insert(buffer);
3100 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3101 if (g_utf8_validate(buf, -1, NULL)) {
3102 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3104 gchar *tmpout = NULL;
3105 tmpout = conv_codeset_strdup
3106 (buf, conv_get_locale_charset_str_no_utf8(),
3108 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3110 tmpout = g_malloc(strlen(buf)*2+1);
3111 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3113 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3117 cursor_pos = quote_fmt_get_cursor_pos();
3118 if (cursor_pos == -1)
3119 cursor_pos = gtk_text_iter_get_offset(&iter);
3120 compose->set_cursor_pos = cursor_pos;
3122 gtk_text_buffer_get_start_iter(buffer, &iter);
3123 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3124 gtk_text_buffer_place_cursor(buffer, &iter);
3126 compose->autowrap = prev_autowrap;
3127 if (compose->autowrap && rewrap)
3128 compose_wrap_all(compose);
3135 SIGNAL_UNBLOCK(buffer);
3137 procmsg_msginfo_free( dummyinfo );
3142 /* if ml_post is of type addr@host and from is of type
3143 * addr-anything@host, return TRUE
3145 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3147 gchar *left_ml = NULL;
3148 gchar *right_ml = NULL;
3149 gchar *left_from = NULL;
3150 gchar *right_from = NULL;
3151 gboolean result = FALSE;
3153 if (!ml_post || !from)
3156 left_ml = g_strdup(ml_post);
3157 if (strstr(left_ml, "@")) {
3158 right_ml = strstr(left_ml, "@")+1;
3159 *(strstr(left_ml, "@")) = '\0';
3162 left_from = g_strdup(from);
3163 if (strstr(left_from, "@")) {
3164 right_from = strstr(left_from, "@")+1;
3165 *(strstr(left_from, "@")) = '\0';
3168 if (left_ml && left_from && right_ml && right_from
3169 && !strncmp(left_from, left_ml, strlen(left_ml))
3170 && !strcmp(right_from, right_ml)) {
3179 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3180 gboolean respect_default_to)
3184 if (!folder || !folder->prefs)
3187 if (respect_default_to && folder->prefs->enable_default_to) {
3188 compose_entry_append(compose, folder->prefs->default_to,
3189 COMPOSE_TO, PREF_FOLDER);
3190 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3192 if (folder->prefs->enable_default_cc)
3193 compose_entry_append(compose, folder->prefs->default_cc,
3194 COMPOSE_CC, PREF_FOLDER);
3195 if (folder->prefs->enable_default_bcc)
3196 compose_entry_append(compose, folder->prefs->default_bcc,
3197 COMPOSE_BCC, PREF_FOLDER);
3198 if (folder->prefs->enable_default_replyto)
3199 compose_entry_append(compose, folder->prefs->default_replyto,
3200 COMPOSE_REPLYTO, PREF_FOLDER);
3203 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3208 if (!compose || !msginfo)
3211 if (msginfo->subject && *msginfo->subject) {
3212 buf = p = g_strdup(msginfo->subject);
3213 p += subject_get_prefix_length(p);
3214 memmove(buf, p, strlen(p) + 1);
3216 buf2 = g_strdup_printf("Re: %s", buf);
3217 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3222 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3225 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3226 gboolean to_all, gboolean to_ml,
3228 gboolean followup_and_reply_to)
3230 GSList *cc_list = NULL;
3233 gchar *replyto = NULL;
3234 gchar *ac_email = NULL;
3236 gboolean reply_to_ml = FALSE;
3237 gboolean default_reply_to = FALSE;
3239 cm_return_if_fail(compose->account != NULL);
3240 cm_return_if_fail(msginfo != NULL);
3242 reply_to_ml = to_ml && compose->ml_post;
3244 default_reply_to = msginfo->folder &&
3245 msginfo->folder->prefs->enable_default_reply_to;
3247 if (compose->account->protocol != A_NNTP) {
3248 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3250 if (reply_to_ml && !default_reply_to) {
3252 gboolean is_subscr = is_subscription(compose->ml_post,
3255 /* normal answer to ml post with a reply-to */
3256 compose_entry_append(compose,
3258 COMPOSE_TO, PREF_ML);
3259 if (compose->replyto)
3260 compose_entry_append(compose,
3262 COMPOSE_CC, PREF_ML);
3264 /* answer to subscription confirmation */
3265 if (compose->replyto)
3266 compose_entry_append(compose,
3268 COMPOSE_TO, PREF_ML);
3269 else if (msginfo->from)
3270 compose_entry_append(compose,
3272 COMPOSE_TO, PREF_ML);
3275 else if (!(to_all || to_sender) && default_reply_to) {
3276 compose_entry_append(compose,
3277 msginfo->folder->prefs->default_reply_to,
3278 COMPOSE_TO, PREF_FOLDER);
3279 compose_entry_mark_default_to(compose,
3280 msginfo->folder->prefs->default_reply_to);
3286 compose_entry_append(compose, msginfo->from,
3287 COMPOSE_TO, PREF_NONE);
3289 Xstrdup_a(tmp1, msginfo->from, return);
3290 extract_address(tmp1);
3291 compose_entry_append(compose,
3292 (!account_find_from_address(tmp1, FALSE))
3295 COMPOSE_TO, PREF_NONE);
3297 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3298 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3299 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3300 if (compose->replyto) {
3301 compose_entry_append(compose,
3303 COMPOSE_TO, PREF_NONE);
3305 compose_entry_append(compose,
3306 msginfo->from ? msginfo->from : "",
3307 COMPOSE_TO, PREF_NONE);
3310 /* replying to own mail, use original recp */
3311 compose_entry_append(compose,
3312 msginfo->to ? msginfo->to : "",
3313 COMPOSE_TO, PREF_NONE);
3314 compose_entry_append(compose,
3315 msginfo->cc ? msginfo->cc : "",
3316 COMPOSE_CC, PREF_NONE);
3321 if (to_sender || (compose->followup_to &&
3322 !strncmp(compose->followup_to, "poster", 6)))
3323 compose_entry_append
3325 (compose->replyto ? compose->replyto :
3326 msginfo->from ? msginfo->from : ""),
3327 COMPOSE_TO, PREF_NONE);
3329 else if (followup_and_reply_to || to_all) {
3330 compose_entry_append
3332 (compose->replyto ? compose->replyto :
3333 msginfo->from ? msginfo->from : ""),
3334 COMPOSE_TO, PREF_NONE);
3336 compose_entry_append
3338 compose->followup_to ? compose->followup_to :
3339 compose->newsgroups ? compose->newsgroups : "",
3340 COMPOSE_NEWSGROUPS, PREF_NONE);
3343 compose_entry_append
3345 compose->followup_to ? compose->followup_to :
3346 compose->newsgroups ? compose->newsgroups : "",
3347 COMPOSE_NEWSGROUPS, PREF_NONE);
3349 compose_reply_set_subject(compose, msginfo);
3351 if (to_ml && compose->ml_post) return;
3352 if (!to_all || compose->account->protocol == A_NNTP) return;
3354 if (compose->replyto) {
3355 Xstrdup_a(replyto, compose->replyto, return);
3356 extract_address(replyto);
3358 if (msginfo->from) {
3359 Xstrdup_a(from, msginfo->from, return);
3360 extract_address(from);
3363 if (replyto && from)
3364 cc_list = address_list_append_with_comments(cc_list, from);
3365 if (to_all && msginfo->folder &&
3366 msginfo->folder->prefs->enable_default_reply_to)
3367 cc_list = address_list_append_with_comments(cc_list,
3368 msginfo->folder->prefs->default_reply_to);
3369 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3370 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3372 ac_email = g_utf8_strdown(compose->account->address, -1);
3375 for (cur = cc_list; cur != NULL; cur = cur->next) {
3376 gchar *addr = g_utf8_strdown(cur->data, -1);
3377 extract_address(addr);
3379 if (strcmp(ac_email, addr))
3380 compose_entry_append(compose, (gchar *)cur->data,
3381 COMPOSE_CC, PREF_NONE);
3383 debug_print("Cc address same as compose account's, ignoring\n");
3388 slist_free_strings_full(cc_list);
3394 #define SET_ENTRY(entry, str) \
3397 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3400 #define SET_ADDRESS(type, str) \
3403 compose_entry_append(compose, str, type, PREF_NONE); \
3406 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3408 cm_return_if_fail(msginfo != NULL);
3410 SET_ENTRY(subject_entry, msginfo->subject);
3411 SET_ENTRY(from_name, msginfo->from);
3412 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3413 SET_ADDRESS(COMPOSE_CC, compose->cc);
3414 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3415 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3416 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3417 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3419 compose_update_priority_menu_item(compose);
3420 compose_update_privacy_system_menu_item(compose, FALSE);
3421 compose_show_first_last_header(compose, TRUE);
3427 static void compose_insert_sig(Compose *compose, gboolean replace)
3429 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3430 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3432 GtkTextIter iter, iter_end;
3433 gint cur_pos, ins_pos;
3434 gboolean prev_autowrap;
3435 gboolean found = FALSE;
3436 gboolean exists = FALSE;
3438 cm_return_if_fail(compose->account != NULL);
3442 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3443 G_CALLBACK(compose_changed_cb),
3446 mark = gtk_text_buffer_get_insert(buffer);
3447 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3448 cur_pos = gtk_text_iter_get_offset (&iter);
3451 gtk_text_buffer_get_end_iter(buffer, &iter);
3453 exists = (compose->sig_str != NULL);
3456 GtkTextIter first_iter, start_iter, end_iter;
3458 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3460 if (!exists || compose->sig_str[0] == '\0')
3463 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3464 compose->signature_tag);
3467 /* include previous \n\n */
3468 gtk_text_iter_backward_chars(&first_iter, 1);
3469 start_iter = first_iter;
3470 end_iter = first_iter;
3472 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3473 compose->signature_tag);
3474 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3475 compose->signature_tag);
3477 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3483 g_free(compose->sig_str);
3484 compose->sig_str = account_get_signature_str(compose->account);
3486 cur_pos = gtk_text_iter_get_offset(&iter);
3488 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3489 g_free(compose->sig_str);
3490 compose->sig_str = NULL;
3492 if (compose->sig_inserted == FALSE)
3493 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3494 compose->sig_inserted = TRUE;
3496 cur_pos = gtk_text_iter_get_offset(&iter);
3497 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3499 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3500 gtk_text_iter_forward_chars(&iter, 1);
3501 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3502 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3504 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3505 cur_pos = gtk_text_buffer_get_char_count (buffer);
3508 /* put the cursor where it should be
3509 * either where the quote_fmt says, either where it was */
3510 if (compose->set_cursor_pos < 0)
3511 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3513 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3514 compose->set_cursor_pos);
3516 compose->set_cursor_pos = -1;
3517 gtk_text_buffer_place_cursor(buffer, &iter);
3518 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3519 G_CALLBACK(compose_changed_cb),
3525 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3528 GtkTextBuffer *buffer;
3531 const gchar *cur_encoding;
3532 gchar buf[BUFFSIZE];
3535 gboolean prev_autowrap;
3536 gboolean badtxt = FALSE;
3537 struct stat file_stat;
3539 GString *file_contents = NULL;
3541 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3543 /* get the size of the file we are about to insert */
3544 ret = g_stat(file, &file_stat);
3546 gchar *shortfile = g_path_get_basename(file);
3547 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3549 return COMPOSE_INSERT_NO_FILE;
3550 } else if (prefs_common.warn_large_insert == TRUE) {
3552 /* ask user for confirmation if the file is large */
3553 if (prefs_common.warn_large_insert_size < 0 ||
3554 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3558 msg = g_strdup_printf(_("You are about to insert a file of %s "
3559 "in the message body. Are you sure you want to do that?"),
3560 to_human_readable(file_stat.st_size));
3561 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3562 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3565 /* do we ask for confirmation next time? */
3566 if (aval & G_ALERTDISABLE) {
3567 /* no confirmation next time, disable feature in preferences */
3568 aval &= ~G_ALERTDISABLE;
3569 prefs_common.warn_large_insert = FALSE;
3572 /* abort file insertion if user canceled action */
3573 if (aval != G_ALERTALTERNATE) {
3574 return COMPOSE_INSERT_NO_FILE;
3580 if ((fp = g_fopen(file, "rb")) == NULL) {
3581 FILE_OP_ERROR(file, "fopen");
3582 return COMPOSE_INSERT_READ_ERROR;
3585 prev_autowrap = compose->autowrap;
3586 compose->autowrap = FALSE;
3588 text = GTK_TEXT_VIEW(compose->text);
3589 buffer = gtk_text_view_get_buffer(text);
3590 mark = gtk_text_buffer_get_insert(buffer);
3591 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3593 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3594 G_CALLBACK(text_inserted),
3597 cur_encoding = conv_get_locale_charset_str_no_utf8();
3599 file_contents = g_string_new("");
3600 while (fgets(buf, sizeof(buf), fp) != NULL) {
3603 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3604 str = g_strdup(buf);
3606 str = conv_codeset_strdup
3607 (buf, cur_encoding, CS_INTERNAL);
3610 /* strip <CR> if DOS/Windows file,
3611 replace <CR> with <LF> if Macintosh file. */
3614 if (len > 0 && str[len - 1] != '\n') {
3616 if (str[len] == '\r') str[len] = '\n';
3619 file_contents = g_string_append(file_contents, str);
3623 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3624 g_string_free(file_contents, TRUE);
3626 compose_changed_cb(NULL, compose);
3627 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3628 G_CALLBACK(text_inserted),
3630 compose->autowrap = prev_autowrap;
3631 if (compose->autowrap)
3632 compose_wrap_all(compose);
3637 return COMPOSE_INSERT_INVALID_CHARACTER;
3639 return COMPOSE_INSERT_SUCCESS;
3642 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3643 const gchar *filename,
3644 const gchar *content_type,
3645 const gchar *charset)
3653 GtkListStore *store;
3655 gboolean has_binary = FALSE;
3657 if (!is_file_exist(file)) {
3658 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3659 gboolean result = FALSE;
3660 if (file_from_uri && is_file_exist(file_from_uri)) {
3661 result = compose_attach_append(
3662 compose, file_from_uri,
3663 filename, content_type,
3666 g_free(file_from_uri);
3669 alertpanel_error("File %s doesn't exist\n", filename);
3672 if ((size = get_file_size(file)) < 0) {
3673 alertpanel_error("Can't get file size of %s\n", filename);
3677 /* In batch mode, we allow 0-length files to be attached no questions asked */
3678 if (size == 0 && !compose->batch) {
3679 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3680 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3681 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3682 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3685 if (aval != G_ALERTALTERNATE) {
3689 if ((fp = g_fopen(file, "rb")) == NULL) {
3690 alertpanel_error(_("Can't read %s."), filename);
3695 ainfo = g_new0(AttachInfo, 1);
3696 auto_ainfo = g_auto_pointer_new_with_free
3697 (ainfo, (GFreeFunc) compose_attach_info_free);
3698 ainfo->file = g_strdup(file);
3701 ainfo->content_type = g_strdup(content_type);
3702 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3704 MsgFlags flags = {0, 0};
3706 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3707 ainfo->encoding = ENC_7BIT;
3709 ainfo->encoding = ENC_8BIT;
3711 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3712 if (msginfo && msginfo->subject)
3713 name = g_strdup(msginfo->subject);
3715 name = g_path_get_basename(filename ? filename : file);
3717 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3719 procmsg_msginfo_free(msginfo);
3721 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3722 ainfo->charset = g_strdup(charset);
3723 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3725 ainfo->encoding = ENC_BASE64;
3727 name = g_path_get_basename(filename ? filename : file);
3728 ainfo->name = g_strdup(name);
3732 ainfo->content_type = procmime_get_mime_type(file);
3733 if (!ainfo->content_type) {
3734 ainfo->content_type =
3735 g_strdup("application/octet-stream");
3736 ainfo->encoding = ENC_BASE64;
3737 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3739 procmime_get_encoding_for_text_file(file, &has_binary);
3741 ainfo->encoding = ENC_BASE64;
3742 name = g_path_get_basename(filename ? filename : file);
3743 ainfo->name = g_strdup(name);
3747 if (ainfo->name != NULL
3748 && !strcmp(ainfo->name, ".")) {
3749 g_free(ainfo->name);
3753 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3754 g_free(ainfo->content_type);
3755 ainfo->content_type = g_strdup("application/octet-stream");
3756 g_free(ainfo->charset);
3757 ainfo->charset = NULL;
3760 ainfo->size = (goffset)size;
3761 size_text = to_human_readable((goffset)size);
3763 store = GTK_LIST_STORE(gtk_tree_view_get_model
3764 (GTK_TREE_VIEW(compose->attach_clist)));
3766 gtk_list_store_append(store, &iter);
3767 gtk_list_store_set(store, &iter,
3768 COL_MIMETYPE, ainfo->content_type,
3769 COL_SIZE, size_text,
3770 COL_NAME, ainfo->name,
3771 COL_CHARSET, ainfo->charset,
3773 COL_AUTODATA, auto_ainfo,
3776 g_auto_pointer_free(auto_ainfo);
3777 compose_attach_update_label(compose);
3781 static void compose_use_signing(Compose *compose, gboolean use_signing)
3783 compose->use_signing = use_signing;
3784 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3787 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3789 compose->use_encryption = use_encryption;
3790 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3793 #define NEXT_PART_NOT_CHILD(info) \
3795 node = info->node; \
3796 while (node->children) \
3797 node = g_node_last_child(node); \
3798 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3801 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3805 MimeInfo *firsttext = NULL;
3806 MimeInfo *encrypted = NULL;
3809 const gchar *partname = NULL;
3811 mimeinfo = procmime_scan_message(msginfo);
3812 if (!mimeinfo) return;
3814 if (mimeinfo->node->children == NULL) {
3815 procmime_mimeinfo_free_all(mimeinfo);
3819 /* find first content part */
3820 child = (MimeInfo *) mimeinfo->node->children->data;
3821 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3822 child = (MimeInfo *)child->node->children->data;
3825 if (child->type == MIMETYPE_TEXT) {
3827 debug_print("First text part found\n");
3828 } else if (compose->mode == COMPOSE_REEDIT &&
3829 child->type == MIMETYPE_APPLICATION &&
3830 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3831 encrypted = (MimeInfo *)child->node->parent->data;
3834 child = (MimeInfo *) mimeinfo->node->children->data;
3835 while (child != NULL) {
3838 if (child == encrypted) {
3839 /* skip this part of tree */
3840 NEXT_PART_NOT_CHILD(child);
3844 if (child->type == MIMETYPE_MULTIPART) {
3845 /* get the actual content */
3846 child = procmime_mimeinfo_next(child);
3850 if (child == firsttext) {
3851 child = procmime_mimeinfo_next(child);
3855 outfile = procmime_get_tmp_file_name(child);
3856 if ((err = procmime_get_part(outfile, child)) < 0)
3857 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3859 gchar *content_type;
3861 content_type = procmime_get_content_type_str(child->type, child->subtype);
3863 /* if we meet a pgp signature, we don't attach it, but
3864 * we force signing. */
3865 if ((strcmp(content_type, "application/pgp-signature") &&
3866 strcmp(content_type, "application/pkcs7-signature") &&
3867 strcmp(content_type, "application/x-pkcs7-signature"))
3868 || compose->mode == COMPOSE_REDIRECT) {
3869 partname = procmime_mimeinfo_get_parameter(child, "filename");
3870 if (partname == NULL)
3871 partname = procmime_mimeinfo_get_parameter(child, "name");
3872 if (partname == NULL)
3874 compose_attach_append(compose, outfile,
3875 partname, content_type,
3876 procmime_mimeinfo_get_parameter(child, "charset"));
3878 compose_force_signing(compose, compose->account, NULL);
3880 g_free(content_type);
3883 NEXT_PART_NOT_CHILD(child);
3885 procmime_mimeinfo_free_all(mimeinfo);
3888 #undef NEXT_PART_NOT_CHILD
3893 WAIT_FOR_INDENT_CHAR,
3894 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3897 /* return indent length, we allow:
3898 indent characters followed by indent characters or spaces/tabs,
3899 alphabets and numbers immediately followed by indent characters,
3900 and the repeating sequences of the above
3901 If quote ends with multiple spaces, only the first one is included. */
3902 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3903 const GtkTextIter *start, gint *len)
3905 GtkTextIter iter = *start;
3909 IndentState state = WAIT_FOR_INDENT_CHAR;
3912 gint alnum_count = 0;
3913 gint space_count = 0;
3916 if (prefs_common.quote_chars == NULL) {
3920 while (!gtk_text_iter_ends_line(&iter)) {
3921 wc = gtk_text_iter_get_char(&iter);
3922 if (g_unichar_iswide(wc))
3924 clen = g_unichar_to_utf8(wc, ch);
3928 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3929 is_space = g_unichar_isspace(wc);
3931 if (state == WAIT_FOR_INDENT_CHAR) {
3932 if (!is_indent && !g_unichar_isalnum(wc))
3935 quote_len += alnum_count + space_count + 1;
3936 alnum_count = space_count = 0;
3937 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3940 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3941 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3945 else if (is_indent) {
3946 quote_len += alnum_count + space_count + 1;
3947 alnum_count = space_count = 0;
3950 state = WAIT_FOR_INDENT_CHAR;
3954 gtk_text_iter_forward_char(&iter);
3957 if (quote_len > 0 && space_count > 0)
3963 if (quote_len > 0) {
3965 gtk_text_iter_forward_chars(&iter, quote_len);
3966 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3972 /* return >0 if the line is itemized */
3973 static int compose_itemized_length(GtkTextBuffer *buffer,
3974 const GtkTextIter *start)
3976 GtkTextIter iter = *start;
3981 if (gtk_text_iter_ends_line(&iter))
3986 wc = gtk_text_iter_get_char(&iter);
3987 if (!g_unichar_isspace(wc))
3989 gtk_text_iter_forward_char(&iter);
3990 if (gtk_text_iter_ends_line(&iter))
3994 clen = g_unichar_to_utf8(wc, ch);
3998 if (!strchr("*-+", ch[0]))
4001 gtk_text_iter_forward_char(&iter);
4002 if (gtk_text_iter_ends_line(&iter))
4004 wc = gtk_text_iter_get_char(&iter);
4005 if (g_unichar_isspace(wc)) {
4011 /* return the string at the start of the itemization */
4012 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4013 const GtkTextIter *start)
4015 GtkTextIter iter = *start;
4018 GString *item_chars = g_string_new("");
4021 if (gtk_text_iter_ends_line(&iter))
4026 wc = gtk_text_iter_get_char(&iter);
4027 if (!g_unichar_isspace(wc))
4029 gtk_text_iter_forward_char(&iter);
4030 if (gtk_text_iter_ends_line(&iter))
4032 g_string_append_unichar(item_chars, wc);
4035 str = item_chars->str;
4036 g_string_free(item_chars, FALSE);
4040 /* return the number of spaces at a line's start */
4041 static int compose_left_offset_length(GtkTextBuffer *buffer,
4042 const GtkTextIter *start)
4044 GtkTextIter iter = *start;
4047 if (gtk_text_iter_ends_line(&iter))
4051 wc = gtk_text_iter_get_char(&iter);
4052 if (!g_unichar_isspace(wc))
4055 gtk_text_iter_forward_char(&iter);
4056 if (gtk_text_iter_ends_line(&iter))
4060 gtk_text_iter_forward_char(&iter);
4061 if (gtk_text_iter_ends_line(&iter))
4066 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4067 const GtkTextIter *start,
4068 GtkTextIter *break_pos,
4072 GtkTextIter iter = *start, line_end = *start;
4073 PangoLogAttr *attrs;
4080 gboolean can_break = FALSE;
4081 gboolean do_break = FALSE;
4082 gboolean was_white = FALSE;
4083 gboolean prev_dont_break = FALSE;
4085 gtk_text_iter_forward_to_line_end(&line_end);
4086 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4087 len = g_utf8_strlen(str, -1);
4091 g_warning("compose_get_line_break_pos: len = 0!\n");
4095 /* g_print("breaking line: %d: %s (len = %d)\n",
4096 gtk_text_iter_get_line(&iter), str, len); */
4098 attrs = g_new(PangoLogAttr, len + 1);
4100 pango_default_break(str, -1, NULL, attrs, len + 1);
4104 /* skip quote and leading spaces */
4105 for (i = 0; *p != '\0' && i < len; i++) {
4108 wc = g_utf8_get_char(p);
4109 if (i >= quote_len && !g_unichar_isspace(wc))
4111 if (g_unichar_iswide(wc))
4113 else if (*p == '\t')
4117 p = g_utf8_next_char(p);
4120 for (; *p != '\0' && i < len; i++) {
4121 PangoLogAttr *attr = attrs + i;
4125 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4128 was_white = attr->is_white;
4130 /* don't wrap URI */
4131 if ((uri_len = get_uri_len(p)) > 0) {
4133 if (pos > 0 && col > max_col) {
4143 wc = g_utf8_get_char(p);
4144 if (g_unichar_iswide(wc)) {
4146 if (prev_dont_break && can_break && attr->is_line_break)
4148 } else if (*p == '\t')
4152 if (pos > 0 && col > max_col) {
4157 if (*p == '-' || *p == '/')
4158 prev_dont_break = TRUE;
4160 prev_dont_break = FALSE;
4162 p = g_utf8_next_char(p);
4166 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4171 *break_pos = *start;
4172 gtk_text_iter_set_line_offset(break_pos, pos);
4177 static gboolean compose_join_next_line(Compose *compose,
4178 GtkTextBuffer *buffer,
4180 const gchar *quote_str)
4182 GtkTextIter iter_ = *iter, cur, prev, next, end;
4183 PangoLogAttr attrs[3];
4185 gchar *next_quote_str;
4188 gboolean keep_cursor = FALSE;
4190 if (!gtk_text_iter_forward_line(&iter_) ||
4191 gtk_text_iter_ends_line(&iter_)) {
4194 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4196 if ((quote_str || next_quote_str) &&
4197 strcmp2(quote_str, next_quote_str) != 0) {
4198 g_free(next_quote_str);
4201 g_free(next_quote_str);
4204 if (quote_len > 0) {
4205 gtk_text_iter_forward_chars(&end, quote_len);
4206 if (gtk_text_iter_ends_line(&end)) {
4211 /* don't join itemized lines */
4212 if (compose_itemized_length(buffer, &end) > 0) {
4216 /* don't join signature separator */
4217 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4220 /* delete quote str */
4222 gtk_text_buffer_delete(buffer, &iter_, &end);
4224 /* don't join line breaks put by the user */
4226 gtk_text_iter_backward_char(&cur);
4227 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4228 gtk_text_iter_forward_char(&cur);
4232 gtk_text_iter_forward_char(&cur);
4233 /* delete linebreak and extra spaces */
4234 while (gtk_text_iter_backward_char(&cur)) {
4235 wc1 = gtk_text_iter_get_char(&cur);
4236 if (!g_unichar_isspace(wc1))
4241 while (!gtk_text_iter_ends_line(&cur)) {
4242 wc1 = gtk_text_iter_get_char(&cur);
4243 if (!g_unichar_isspace(wc1))
4245 gtk_text_iter_forward_char(&cur);
4248 if (!gtk_text_iter_equal(&prev, &next)) {
4251 mark = gtk_text_buffer_get_insert(buffer);
4252 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4253 if (gtk_text_iter_equal(&prev, &cur))
4255 gtk_text_buffer_delete(buffer, &prev, &next);
4259 /* insert space if required */
4260 gtk_text_iter_backward_char(&prev);
4261 wc1 = gtk_text_iter_get_char(&prev);
4262 wc2 = gtk_text_iter_get_char(&next);
4263 gtk_text_iter_forward_char(&next);
4264 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4265 pango_default_break(str, -1, NULL, attrs, 3);
4266 if (!attrs[1].is_line_break ||
4267 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4268 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4270 gtk_text_iter_backward_char(&iter_);
4271 gtk_text_buffer_place_cursor(buffer, &iter_);
4280 #define ADD_TXT_POS(bp_, ep_, pti_) \
4281 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4282 last = last->next; \
4283 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4284 last->next = NULL; \
4286 g_warning("alloc error scanning URIs\n"); \
4289 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4291 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4292 GtkTextBuffer *buffer;
4293 GtkTextIter iter, break_pos, end_of_line;
4294 gchar *quote_str = NULL;
4296 gboolean wrap_quote = prefs_common.linewrap_quote;
4297 gboolean prev_autowrap = compose->autowrap;
4298 gint startq_offset = -1, noq_offset = -1;
4299 gint uri_start = -1, uri_stop = -1;
4300 gint nouri_start = -1, nouri_stop = -1;
4301 gint num_blocks = 0;
4302 gint quotelevel = -1;
4303 gboolean modified = force;
4304 gboolean removed = FALSE;
4305 gboolean modified_before_remove = FALSE;
4307 gboolean start = TRUE;
4308 gint itemized_len = 0, rem_item_len = 0;
4309 gchar *itemized_chars = NULL;
4310 gboolean item_continuation = FALSE;
4315 if (compose->draft_timeout_tag == -2) {
4319 compose->autowrap = FALSE;
4321 buffer = gtk_text_view_get_buffer(text);
4322 undo_wrapping(compose->undostruct, TRUE);
4327 mark = gtk_text_buffer_get_insert(buffer);
4328 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4332 if (compose->draft_timeout_tag == -2) {
4333 if (gtk_text_iter_ends_line(&iter)) {
4334 while (gtk_text_iter_ends_line(&iter) &&
4335 gtk_text_iter_forward_line(&iter))
4338 while (gtk_text_iter_backward_line(&iter)) {
4339 if (gtk_text_iter_ends_line(&iter)) {
4340 gtk_text_iter_forward_line(&iter);
4346 /* move to line start */
4347 gtk_text_iter_set_line_offset(&iter, 0);
4350 itemized_len = compose_itemized_length(buffer, &iter);
4352 if (!itemized_len) {
4353 itemized_len = compose_left_offset_length(buffer, &iter);
4354 item_continuation = TRUE;
4358 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4360 /* go until paragraph end (empty line) */
4361 while (start || !gtk_text_iter_ends_line(&iter)) {
4362 gchar *scanpos = NULL;
4363 /* parse table - in order of priority */
4365 const gchar *needle; /* token */
4367 /* token search function */
4368 gchar *(*search) (const gchar *haystack,
4369 const gchar *needle);
4370 /* part parsing function */
4371 gboolean (*parse) (const gchar *start,
4372 const gchar *scanpos,
4376 /* part to URI function */
4377 gchar *(*build_uri) (const gchar *bp,
4381 static struct table parser[] = {
4382 {"http://", strcasestr, get_uri_part, make_uri_string},
4383 {"https://", strcasestr, get_uri_part, make_uri_string},
4384 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4385 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4386 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4387 {"www.", strcasestr, get_uri_part, make_http_string},
4388 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4389 {"@", strcasestr, get_email_part, make_email_string}
4391 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4392 gint last_index = PARSE_ELEMS;
4394 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4398 if (!prev_autowrap && num_blocks == 0) {
4400 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4401 G_CALLBACK(text_inserted),
4404 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4407 uri_start = uri_stop = -1;
4409 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4412 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4413 if (startq_offset == -1)
4414 startq_offset = gtk_text_iter_get_offset(&iter);
4415 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4416 if (quotelevel > 2) {
4417 /* recycle colors */
4418 if (prefs_common.recycle_quote_colors)
4427 if (startq_offset == -1)
4428 noq_offset = gtk_text_iter_get_offset(&iter);
4432 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4435 if (gtk_text_iter_ends_line(&iter)) {
4437 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4438 prefs_common.linewrap_len,
4440 GtkTextIter prev, next, cur;
4441 if (prev_autowrap != FALSE || force) {
4442 compose->automatic_break = TRUE;
4444 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4445 compose->automatic_break = FALSE;
4446 if (itemized_len && compose->autoindent) {
4447 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4448 if (!item_continuation)
4449 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4451 } else if (quote_str && wrap_quote) {
4452 compose->automatic_break = TRUE;
4454 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4455 compose->automatic_break = FALSE;
4456 if (itemized_len && compose->autoindent) {
4457 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4458 if (!item_continuation)
4459 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4463 /* remove trailing spaces */
4465 rem_item_len = itemized_len;
4466 while (compose->autoindent && rem_item_len-- > 0)
4467 gtk_text_iter_backward_char(&cur);
4468 gtk_text_iter_backward_char(&cur);
4471 while (!gtk_text_iter_starts_line(&cur)) {
4474 gtk_text_iter_backward_char(&cur);
4475 wc = gtk_text_iter_get_char(&cur);
4476 if (!g_unichar_isspace(wc))
4480 if (!gtk_text_iter_equal(&prev, &next)) {
4481 gtk_text_buffer_delete(buffer, &prev, &next);
4483 gtk_text_iter_forward_char(&break_pos);
4487 gtk_text_buffer_insert(buffer, &break_pos,
4491 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4493 /* move iter to current line start */
4494 gtk_text_iter_set_line_offset(&iter, 0);
4501 /* move iter to next line start */
4507 if (!prev_autowrap && num_blocks > 0) {
4509 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4510 G_CALLBACK(text_inserted),
4514 while (!gtk_text_iter_ends_line(&end_of_line)) {
4515 gtk_text_iter_forward_char(&end_of_line);
4517 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4519 nouri_start = gtk_text_iter_get_offset(&iter);
4520 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4522 walk_pos = gtk_text_iter_get_offset(&iter);
4523 /* FIXME: this looks phony. scanning for anything in the parse table */
4524 for (n = 0; n < PARSE_ELEMS; n++) {
4527 tmp = parser[n].search(walk, parser[n].needle);
4529 if (scanpos == NULL || tmp < scanpos) {
4538 /* check if URI can be parsed */
4539 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4540 (const gchar **)&ep, FALSE)
4541 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4545 strlen(parser[last_index].needle);
4548 uri_start = walk_pos + (bp - o_walk);
4549 uri_stop = walk_pos + (ep - o_walk);
4553 gtk_text_iter_forward_line(&iter);
4556 if (startq_offset != -1) {
4557 GtkTextIter startquote, endquote;
4558 gtk_text_buffer_get_iter_at_offset(
4559 buffer, &startquote, startq_offset);
4562 switch (quotelevel) {
4564 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4565 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4566 gtk_text_buffer_apply_tag_by_name(
4567 buffer, "quote0", &startquote, &endquote);
4568 gtk_text_buffer_remove_tag_by_name(
4569 buffer, "quote1", &startquote, &endquote);
4570 gtk_text_buffer_remove_tag_by_name(
4571 buffer, "quote2", &startquote, &endquote);
4576 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4577 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4578 gtk_text_buffer_apply_tag_by_name(
4579 buffer, "quote1", &startquote, &endquote);
4580 gtk_text_buffer_remove_tag_by_name(
4581 buffer, "quote0", &startquote, &endquote);
4582 gtk_text_buffer_remove_tag_by_name(
4583 buffer, "quote2", &startquote, &endquote);
4588 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4589 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4590 gtk_text_buffer_apply_tag_by_name(
4591 buffer, "quote2", &startquote, &endquote);
4592 gtk_text_buffer_remove_tag_by_name(
4593 buffer, "quote0", &startquote, &endquote);
4594 gtk_text_buffer_remove_tag_by_name(
4595 buffer, "quote1", &startquote, &endquote);
4601 } else if (noq_offset != -1) {
4602 GtkTextIter startnoquote, endnoquote;
4603 gtk_text_buffer_get_iter_at_offset(
4604 buffer, &startnoquote, noq_offset);
4607 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4608 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4609 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4610 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4611 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4612 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4613 gtk_text_buffer_remove_tag_by_name(
4614 buffer, "quote0", &startnoquote, &endnoquote);
4615 gtk_text_buffer_remove_tag_by_name(
4616 buffer, "quote1", &startnoquote, &endnoquote);
4617 gtk_text_buffer_remove_tag_by_name(
4618 buffer, "quote2", &startnoquote, &endnoquote);
4624 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4625 GtkTextIter nouri_start_iter, nouri_end_iter;
4626 gtk_text_buffer_get_iter_at_offset(
4627 buffer, &nouri_start_iter, nouri_start);
4628 gtk_text_buffer_get_iter_at_offset(
4629 buffer, &nouri_end_iter, nouri_stop);
4630 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4631 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4632 gtk_text_buffer_remove_tag_by_name(
4633 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4634 modified_before_remove = modified;
4639 if (uri_start >= 0 && uri_stop > 0) {
4640 GtkTextIter uri_start_iter, uri_end_iter, back;
4641 gtk_text_buffer_get_iter_at_offset(
4642 buffer, &uri_start_iter, uri_start);
4643 gtk_text_buffer_get_iter_at_offset(
4644 buffer, &uri_end_iter, uri_stop);
4645 back = uri_end_iter;
4646 gtk_text_iter_backward_char(&back);
4647 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4648 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4649 gtk_text_buffer_apply_tag_by_name(
4650 buffer, "link", &uri_start_iter, &uri_end_iter);
4652 if (removed && !modified_before_remove) {
4658 // debug_print("not modified, out after %d lines\n", lines);
4662 // debug_print("modified, out after %d lines\n", lines);
4664 g_free(itemized_chars);
4667 undo_wrapping(compose->undostruct, FALSE);
4668 compose->autowrap = prev_autowrap;
4673 void compose_action_cb(void *data)
4675 Compose *compose = (Compose *)data;
4676 compose_wrap_all(compose);
4679 static void compose_wrap_all(Compose *compose)
4681 compose_wrap_all_full(compose, FALSE);
4684 static void compose_wrap_all_full(Compose *compose, gboolean force)
4686 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4687 GtkTextBuffer *buffer;
4689 gboolean modified = TRUE;
4691 buffer = gtk_text_view_get_buffer(text);
4693 gtk_text_buffer_get_start_iter(buffer, &iter);
4694 while (!gtk_text_iter_is_end(&iter) && modified)
4695 modified = compose_beautify_paragraph(compose, &iter, force);
4699 static void compose_set_title(Compose *compose)
4705 edited = compose->modified ? _(" [Edited]") : "";
4707 subject = gtk_editable_get_chars(
4708 GTK_EDITABLE(compose->subject_entry), 0, -1);
4710 #ifndef GENERIC_UMPC
4711 if (subject && strlen(subject))
4712 str = g_strdup_printf(_("%s - Compose message%s"),
4715 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4717 str = g_strdup(_("Compose message"));
4720 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4726 * compose_current_mail_account:
4728 * Find a current mail account (the currently selected account, or the
4729 * default account, if a news account is currently selected). If a
4730 * mail account cannot be found, display an error message.
4732 * Return value: Mail account, or NULL if not found.
4734 static PrefsAccount *
4735 compose_current_mail_account(void)
4739 if (cur_account && cur_account->protocol != A_NNTP)
4742 ac = account_get_default();
4743 if (!ac || ac->protocol == A_NNTP) {
4744 alertpanel_error(_("Account for sending mail is not specified.\n"
4745 "Please select a mail account before sending."));
4752 #define QUOTE_IF_REQUIRED(out, str) \
4754 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4758 len = strlen(str) + 3; \
4759 if ((__tmp = alloca(len)) == NULL) { \
4760 g_warning("can't allocate memory\n"); \
4761 g_string_free(header, TRUE); \
4764 g_snprintf(__tmp, len, "\"%s\"", str); \
4769 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4770 g_warning("can't allocate memory\n"); \
4771 g_string_free(header, TRUE); \
4774 strcpy(__tmp, str); \
4780 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4782 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4786 len = strlen(str) + 3; \
4787 if ((__tmp = alloca(len)) == NULL) { \
4788 g_warning("can't allocate memory\n"); \
4791 g_snprintf(__tmp, len, "\"%s\"", str); \
4796 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4797 g_warning("can't allocate memory\n"); \
4800 strcpy(__tmp, str); \
4806 static void compose_select_account(Compose *compose, PrefsAccount *account,
4809 gchar *from = NULL, *header = NULL;
4810 ComposeHeaderEntry *header_entry;
4811 #if GTK_CHECK_VERSION(2, 24, 0)
4815 cm_return_if_fail(account != NULL);
4817 compose->account = account;
4818 if (account->name && *account->name) {
4820 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4821 qbuf = escape_internal_quotes(buf, '"');
4822 from = g_strdup_printf("%s <%s>",
4823 qbuf, account->address);
4826 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4828 from = g_strdup_printf("<%s>",
4830 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4835 compose_set_title(compose);
4837 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4838 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4840 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4841 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4842 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4844 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4846 activate_privacy_system(compose, account, FALSE);
4848 if (!init && compose->mode != COMPOSE_REDIRECT) {
4849 undo_block(compose->undostruct);
4850 compose_insert_sig(compose, TRUE);
4851 undo_unblock(compose->undostruct);
4854 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4855 #if !GTK_CHECK_VERSION(2, 24, 0)
4856 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4858 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4859 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4860 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4863 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4864 if (account->protocol == A_NNTP) {
4865 if (!strcmp(header, _("To:")))
4866 combobox_select_by_text(
4867 GTK_COMBO_BOX(header_entry->combo),
4870 if (!strcmp(header, _("Newsgroups:")))
4871 combobox_select_by_text(
4872 GTK_COMBO_BOX(header_entry->combo),
4880 /* use account's dict info if set */
4881 if (compose->gtkaspell) {
4882 if (account->enable_default_dictionary)
4883 gtkaspell_change_dict(compose->gtkaspell,
4884 account->default_dictionary, FALSE);
4885 if (account->enable_default_alt_dictionary)
4886 gtkaspell_change_alt_dict(compose->gtkaspell,
4887 account->default_alt_dictionary);
4888 if (account->enable_default_dictionary
4889 || account->enable_default_alt_dictionary)
4890 compose_spell_menu_changed(compose);
4895 gboolean compose_check_for_valid_recipient(Compose *compose) {
4896 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4897 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4898 gboolean recipient_found = FALSE;
4902 /* free to and newsgroup list */
4903 slist_free_strings_full(compose->to_list);
4904 compose->to_list = NULL;
4906 slist_free_strings_full(compose->newsgroup_list);
4907 compose->newsgroup_list = NULL;
4909 /* search header entries for to and newsgroup entries */
4910 for (list = compose->header_list; list; list = list->next) {
4913 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4914 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4917 if (entry[0] != '\0') {
4918 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4919 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4920 compose->to_list = address_list_append(compose->to_list, entry);
4921 recipient_found = TRUE;
4924 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4925 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4926 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4927 recipient_found = TRUE;
4934 return recipient_found;
4937 static gboolean compose_check_for_set_recipients(Compose *compose)
4939 if (compose->account->set_autocc && compose->account->auto_cc) {
4940 gboolean found_other = FALSE;
4942 /* search header entries for to and newsgroup entries */
4943 for (list = compose->header_list; list; list = list->next) {
4946 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4947 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4950 if (strcmp(entry, compose->account->auto_cc)
4951 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4961 if (compose->batch) {
4962 gtk_widget_show_all(compose->window);
4964 aval = alertpanel(_("Send"),
4965 _("The only recipient is the default CC address. Send anyway?"),
4966 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4967 if (aval != G_ALERTALTERNATE)
4971 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4972 gboolean found_other = FALSE;
4974 /* search header entries for to and newsgroup entries */
4975 for (list = compose->header_list; list; list = list->next) {
4978 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4979 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4982 if (strcmp(entry, compose->account->auto_bcc)
4983 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4993 if (compose->batch) {
4994 gtk_widget_show_all(compose->window);
4996 aval = alertpanel(_("Send"),
4997 _("The only recipient is the default BCC address. Send anyway?"),
4998 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4999 if (aval != G_ALERTALTERNATE)
5006 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5010 if (compose_check_for_valid_recipient(compose) == FALSE) {
5011 if (compose->batch) {
5012 gtk_widget_show_all(compose->window);
5014 alertpanel_error(_("Recipient is not specified."));
5018 if (compose_check_for_set_recipients(compose) == FALSE) {
5022 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5023 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5024 if (*str == '\0' && check_everything == TRUE &&
5025 compose->mode != COMPOSE_REDIRECT) {
5027 gchar *button_label;
5030 if (compose->sending)
5031 button_label = _("+_Send");
5033 button_label = _("+_Queue");
5034 message = g_strdup_printf(_("Subject is empty. %s"),
5035 compose->sending?_("Send it anyway?"):
5036 _("Queue it anyway?"));
5038 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5039 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5040 ALERT_QUESTION, G_ALERTDEFAULT);
5042 if (aval & G_ALERTDISABLE) {
5043 aval &= ~G_ALERTDISABLE;
5044 prefs_common.warn_empty_subj = FALSE;
5046 if (aval != G_ALERTALTERNATE)
5051 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5057 gint compose_send(Compose *compose)
5060 FolderItem *folder = NULL;
5062 gchar *msgpath = NULL;
5063 gboolean discard_window = FALSE;
5064 gchar *errstr = NULL;
5065 gchar *tmsgid = NULL;
5066 MainWindow *mainwin = mainwindow_get_mainwindow();
5067 gboolean queued_removed = FALSE;
5069 if (prefs_common.send_dialog_invisible
5070 || compose->batch == TRUE)
5071 discard_window = TRUE;
5073 compose_allow_user_actions (compose, FALSE);
5074 compose->sending = TRUE;
5076 if (compose_check_entries(compose, TRUE) == FALSE) {
5077 if (compose->batch) {
5078 gtk_widget_show_all(compose->window);
5084 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5087 if (compose->batch) {
5088 gtk_widget_show_all(compose->window);
5091 alertpanel_error(_("Could not queue message for sending:\n\n"
5092 "Charset conversion failed."));
5093 } else if (val == -5) {
5094 alertpanel_error(_("Could not queue message for sending:\n\n"
5095 "Couldn't get recipient encryption key."));
5096 } else if (val == -6) {
5098 } else if (val == -3) {
5099 if (privacy_peek_error())
5100 alertpanel_error(_("Could not queue message for sending:\n\n"
5101 "Signature failed: %s"), privacy_get_error());
5102 } else if (val == -2 && errno != 0) {
5103 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5105 alertpanel_error(_("Could not queue message for sending."));
5110 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5111 if (discard_window) {
5112 compose->sending = FALSE;
5113 compose_close(compose);
5114 /* No more compose access in the normal codepath
5115 * after this point! */
5120 alertpanel_error(_("The message was queued but could not be "
5121 "sent.\nUse \"Send queued messages\" from "
5122 "the main window to retry."));
5123 if (!discard_window) {
5130 if (msgpath == NULL) {
5131 msgpath = folder_item_fetch_msg(folder, msgnum);
5132 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5135 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5136 claws_unlink(msgpath);
5139 if (!discard_window) {
5141 if (!queued_removed)
5142 folder_item_remove_msg(folder, msgnum);
5143 folder_item_scan(folder);
5145 /* make sure we delete that */
5146 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5148 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5149 folder_item_remove_msg(folder, tmp->msgnum);
5150 procmsg_msginfo_free(tmp);
5157 if (!queued_removed)
5158 folder_item_remove_msg(folder, msgnum);
5159 folder_item_scan(folder);
5161 /* make sure we delete that */
5162 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5164 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5165 folder_item_remove_msg(folder, tmp->msgnum);
5166 procmsg_msginfo_free(tmp);
5169 if (!discard_window) {
5170 compose->sending = FALSE;
5171 compose_allow_user_actions (compose, TRUE);
5172 compose_close(compose);
5176 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5177 "the main window to retry."), errstr);
5180 alertpanel_error_log(_("The message was queued but could not be "
5181 "sent.\nUse \"Send queued messages\" from "
5182 "the main window to retry."));
5184 if (!discard_window) {
5193 toolbar_main_set_sensitive(mainwin);
5194 main_window_set_menu_sensitive(mainwin);
5200 compose_allow_user_actions (compose, TRUE);
5201 compose->sending = FALSE;
5202 compose->modified = TRUE;
5203 toolbar_main_set_sensitive(mainwin);
5204 main_window_set_menu_sensitive(mainwin);
5209 static gboolean compose_use_attach(Compose *compose)
5211 GtkTreeModel *model = gtk_tree_view_get_model
5212 (GTK_TREE_VIEW(compose->attach_clist));
5213 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5216 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5219 gchar buf[BUFFSIZE];
5221 gboolean first_to_address;
5222 gboolean first_cc_address;
5224 ComposeHeaderEntry *headerentry;
5225 const gchar *headerentryname;
5226 const gchar *cc_hdr;
5227 const gchar *to_hdr;
5228 gboolean err = FALSE;
5230 debug_print("Writing redirect header\n");
5232 cc_hdr = prefs_common_translated_header_name("Cc:");
5233 to_hdr = prefs_common_translated_header_name("To:");
5235 first_to_address = TRUE;
5236 for (list = compose->header_list; list; list = list->next) {
5237 headerentry = ((ComposeHeaderEntry *)list->data);
5238 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5240 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5241 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5242 Xstrdup_a(str, entstr, return -1);
5244 if (str[0] != '\0') {
5245 compose_convert_header
5246 (compose, buf, sizeof(buf), str,
5247 strlen("Resent-To") + 2, TRUE);
5249 if (first_to_address) {
5250 err |= (fprintf(fp, "Resent-To: ") < 0);
5251 first_to_address = FALSE;
5253 err |= (fprintf(fp, ",") < 0);
5255 err |= (fprintf(fp, "%s", buf) < 0);
5259 if (!first_to_address) {
5260 err |= (fprintf(fp, "\n") < 0);
5263 first_cc_address = TRUE;
5264 for (list = compose->header_list; list; list = list->next) {
5265 headerentry = ((ComposeHeaderEntry *)list->data);
5266 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5268 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5269 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5270 Xstrdup_a(str, strg, return -1);
5272 if (str[0] != '\0') {
5273 compose_convert_header
5274 (compose, buf, sizeof(buf), str,
5275 strlen("Resent-Cc") + 2, TRUE);
5277 if (first_cc_address) {
5278 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5279 first_cc_address = FALSE;
5281 err |= (fprintf(fp, ",") < 0);
5283 err |= (fprintf(fp, "%s", buf) < 0);
5287 if (!first_cc_address) {
5288 err |= (fprintf(fp, "\n") < 0);
5291 return (err ? -1:0);
5294 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5296 gchar buf[BUFFSIZE];
5298 const gchar *entstr;
5299 /* struct utsname utsbuf; */
5300 gboolean err = FALSE;
5302 cm_return_val_if_fail(fp != NULL, -1);
5303 cm_return_val_if_fail(compose->account != NULL, -1);
5304 cm_return_val_if_fail(compose->account->address != NULL, -1);
5307 get_rfc822_date(buf, sizeof(buf));
5308 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5311 if (compose->account->name && *compose->account->name) {
5312 compose_convert_header
5313 (compose, buf, sizeof(buf), compose->account->name,
5314 strlen("From: "), TRUE);
5315 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5316 buf, compose->account->address) < 0);
5318 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5321 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5322 if (*entstr != '\0') {
5323 Xstrdup_a(str, entstr, return -1);
5326 compose_convert_header(compose, buf, sizeof(buf), str,
5327 strlen("Subject: "), FALSE);
5328 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5332 /* Resent-Message-ID */
5333 if (compose->account->set_domain && compose->account->domain) {
5334 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5335 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5336 g_snprintf(buf, sizeof(buf), "%s",
5337 strchr(compose->account->address, '@') ?
5338 strchr(compose->account->address, '@')+1 :
5339 compose->account->address);
5341 g_snprintf(buf, sizeof(buf), "%s", "");
5344 if (compose->account->gen_msgid) {
5346 if (compose->account->msgid_with_addr) {
5347 addr = compose->account->address;
5349 generate_msgid(buf, sizeof(buf), addr);
5350 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5352 g_free(compose->msgid);
5353 compose->msgid = g_strdup(buf);
5355 compose->msgid = NULL;
5358 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5361 /* separator between header and body */
5362 err |= (fputs("\n", fp) == EOF);
5364 return (err ? -1:0);
5367 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5371 gchar buf[BUFFSIZE];
5373 gboolean skip = FALSE;
5374 gboolean err = FALSE;
5375 gchar *not_included[]={
5376 "Return-Path:", "Delivered-To:", "Received:",
5377 "Subject:", "X-UIDL:", "AF:",
5378 "NF:", "PS:", "SRH:",
5379 "SFN:", "DSR:", "MID:",
5380 "CFG:", "PT:", "S:",
5381 "RQ:", "SSV:", "NSV:",
5382 "SSH:", "R:", "MAID:",
5383 "NAID:", "RMID:", "FMID:",
5384 "SCF:", "RRCPT:", "NG:",
5385 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5386 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5387 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5388 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5389 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5392 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5393 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5397 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5399 for (i = 0; not_included[i] != NULL; i++) {
5400 if (g_ascii_strncasecmp(buf, not_included[i],
5401 strlen(not_included[i])) == 0) {
5408 if (fputs(buf, fdest) == -1)
5411 if (!prefs_common.redirect_keep_from) {
5412 if (g_ascii_strncasecmp(buf, "From:",
5413 strlen("From:")) == 0) {
5414 err |= (fputs(" (by way of ", fdest) == EOF);
5415 if (compose->account->name
5416 && *compose->account->name) {
5417 compose_convert_header
5418 (compose, buf, sizeof(buf),
5419 compose->account->name,
5422 err |= (fprintf(fdest, "%s <%s>",
5424 compose->account->address) < 0);
5426 err |= (fprintf(fdest, "%s",
5427 compose->account->address) < 0);
5428 err |= (fputs(")", fdest) == EOF);
5432 if (fputs("\n", fdest) == -1)
5439 if (compose_redirect_write_headers(compose, fdest))
5442 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5443 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5456 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5458 GtkTextBuffer *buffer;
5459 GtkTextIter start, end;
5462 const gchar *out_codeset;
5463 EncodingType encoding = ENC_UNKNOWN;
5464 MimeInfo *mimemsg, *mimetext;
5466 const gchar *src_codeset = CS_INTERNAL;
5467 gchar *from_addr = NULL;
5468 gchar *from_name = NULL;
5470 if (action == COMPOSE_WRITE_FOR_SEND)
5471 attach_parts = TRUE;
5473 /* create message MimeInfo */
5474 mimemsg = procmime_mimeinfo_new();
5475 mimemsg->type = MIMETYPE_MESSAGE;
5476 mimemsg->subtype = g_strdup("rfc822");
5477 mimemsg->content = MIMECONTENT_MEM;
5478 mimemsg->tmp = TRUE; /* must free content later */
5479 mimemsg->data.mem = compose_get_header(compose);
5481 /* Create text part MimeInfo */
5482 /* get all composed text */
5483 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5484 gtk_text_buffer_get_start_iter(buffer, &start);
5485 gtk_text_buffer_get_end_iter(buffer, &end);
5486 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5488 out_codeset = conv_get_charset_str(compose->out_encoding);
5490 if (!out_codeset && is_ascii_str(chars)) {
5491 out_codeset = CS_US_ASCII;
5492 } else if (prefs_common.outgoing_fallback_to_ascii &&
5493 is_ascii_str(chars)) {
5494 out_codeset = CS_US_ASCII;
5495 encoding = ENC_7BIT;
5499 gchar *test_conv_global_out = NULL;
5500 gchar *test_conv_reply = NULL;
5502 /* automatic mode. be automatic. */
5503 codeconv_set_strict(TRUE);
5505 out_codeset = conv_get_outgoing_charset_str();
5507 debug_print("trying to convert to %s\n", out_codeset);
5508 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5511 if (!test_conv_global_out && compose->orig_charset
5512 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5513 out_codeset = compose->orig_charset;
5514 debug_print("failure; trying to convert to %s\n", out_codeset);
5515 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5518 if (!test_conv_global_out && !test_conv_reply) {
5520 out_codeset = CS_INTERNAL;
5521 debug_print("failure; finally using %s\n", out_codeset);
5523 g_free(test_conv_global_out);
5524 g_free(test_conv_reply);
5525 codeconv_set_strict(FALSE);
5528 if (encoding == ENC_UNKNOWN) {
5529 if (prefs_common.encoding_method == CTE_BASE64)
5530 encoding = ENC_BASE64;
5531 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5532 encoding = ENC_QUOTED_PRINTABLE;
5533 else if (prefs_common.encoding_method == CTE_8BIT)
5534 encoding = ENC_8BIT;
5536 encoding = procmime_get_encoding_for_charset(out_codeset);
5539 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5540 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5542 if (action == COMPOSE_WRITE_FOR_SEND) {
5543 codeconv_set_strict(TRUE);
5544 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5545 codeconv_set_strict(FALSE);
5551 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5552 "to the specified %s charset.\n"
5553 "Send it as %s?"), out_codeset, src_codeset);
5554 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5555 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5558 if (aval != G_ALERTALTERNATE) {
5563 out_codeset = src_codeset;
5569 out_codeset = src_codeset;
5574 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5575 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5576 strstr(buf, "\nFrom ") != NULL) {
5577 encoding = ENC_QUOTED_PRINTABLE;
5581 mimetext = procmime_mimeinfo_new();
5582 mimetext->content = MIMECONTENT_MEM;
5583 mimetext->tmp = TRUE; /* must free content later */
5584 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5585 * and free the data, which we need later. */
5586 mimetext->data.mem = g_strdup(buf);
5587 mimetext->type = MIMETYPE_TEXT;
5588 mimetext->subtype = g_strdup("plain");
5589 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5590 g_strdup(out_codeset));
5592 /* protect trailing spaces when signing message */
5593 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5594 privacy_system_can_sign(compose->privacy_system)) {
5595 encoding = ENC_QUOTED_PRINTABLE;
5598 debug_print("main text: %zd bytes encoded as %s in %d\n",
5599 strlen(buf), out_codeset, encoding);
5601 /* check for line length limit */
5602 if (action == COMPOSE_WRITE_FOR_SEND &&
5603 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5604 check_line_length(buf, 1000, &line) < 0) {
5608 msg = g_strdup_printf
5609 (_("Line %d exceeds the line length limit (998 bytes).\n"
5610 "The contents of the message might be broken on the way to the delivery.\n"
5612 "Send it anyway?"), line + 1);
5613 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5615 if (aval != G_ALERTALTERNATE) {
5621 if (encoding != ENC_UNKNOWN)
5622 procmime_encode_content(mimetext, encoding);
5624 /* append attachment parts */
5625 if (compose_use_attach(compose) && attach_parts) {
5626 MimeInfo *mimempart;
5627 gchar *boundary = NULL;
5628 mimempart = procmime_mimeinfo_new();
5629 mimempart->content = MIMECONTENT_EMPTY;
5630 mimempart->type = MIMETYPE_MULTIPART;
5631 mimempart->subtype = g_strdup("mixed");
5635 boundary = generate_mime_boundary(NULL);
5636 } while (strstr(buf, boundary) != NULL);
5638 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5641 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5643 g_node_append(mimempart->node, mimetext->node);
5644 g_node_append(mimemsg->node, mimempart->node);
5646 if (compose_add_attachments(compose, mimempart) < 0)
5649 g_node_append(mimemsg->node, mimetext->node);
5653 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5654 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5655 /* extract name and address */
5656 if (strstr(spec, " <") && strstr(spec, ">")) {
5657 from_addr = g_strdup(strrchr(spec, '<')+1);
5658 *(strrchr(from_addr, '>')) = '\0';
5659 from_name = g_strdup(spec);
5660 *(strrchr(from_name, '<')) = '\0';
5667 /* sign message if sending */
5668 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5669 privacy_system_can_sign(compose->privacy_system))
5670 if (!privacy_sign(compose->privacy_system, mimemsg,
5671 compose->account, from_addr)) {
5678 procmime_write_mimeinfo(mimemsg, fp);
5680 procmime_mimeinfo_free_all(mimemsg);
5685 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5687 GtkTextBuffer *buffer;
5688 GtkTextIter start, end;
5693 if ((fp = g_fopen(file, "wb")) == NULL) {
5694 FILE_OP_ERROR(file, "fopen");
5698 /* chmod for security */
5699 if (change_file_mode_rw(fp, file) < 0) {
5700 FILE_OP_ERROR(file, "chmod");
5701 g_warning("can't change file mode\n");
5704 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5705 gtk_text_buffer_get_start_iter(buffer, &start);
5706 gtk_text_buffer_get_end_iter(buffer, &end);
5707 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5709 chars = conv_codeset_strdup
5710 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5713 if (!chars) return -1;
5716 len = strlen(chars);
5717 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5718 FILE_OP_ERROR(file, "fwrite");
5727 if (fclose(fp) == EOF) {
5728 FILE_OP_ERROR(file, "fclose");
5735 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5738 MsgInfo *msginfo = compose->targetinfo;
5740 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5741 if (!msginfo) return -1;
5743 if (!force && MSG_IS_LOCKED(msginfo->flags))
5746 item = msginfo->folder;
5747 cm_return_val_if_fail(item != NULL, -1);
5749 if (procmsg_msg_exist(msginfo) &&
5750 (folder_has_parent_of_type(item, F_QUEUE) ||
5751 folder_has_parent_of_type(item, F_DRAFT)
5752 || msginfo == compose->autosaved_draft)) {
5753 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5754 g_warning("can't remove the old message\n");
5757 debug_print("removed reedit target %d\n", msginfo->msgnum);
5764 static void compose_remove_draft(Compose *compose)
5767 MsgInfo *msginfo = compose->targetinfo;
5768 drafts = account_get_special_folder(compose->account, F_DRAFT);
5770 if (procmsg_msg_exist(msginfo)) {
5771 folder_item_remove_msg(drafts, msginfo->msgnum);
5776 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5777 gboolean remove_reedit_target)
5779 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5782 static gboolean compose_warn_encryption(Compose *compose)
5784 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5785 AlertValue val = G_ALERTALTERNATE;
5787 if (warning == NULL)
5790 val = alertpanel_full(_("Encryption warning"), warning,
5791 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5792 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5793 if (val & G_ALERTDISABLE) {
5794 val &= ~G_ALERTDISABLE;
5795 if (val == G_ALERTALTERNATE)
5796 privacy_inhibit_encrypt_warning(compose->privacy_system,
5800 if (val == G_ALERTALTERNATE) {
5807 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5808 gchar **msgpath, gboolean check_subject,
5809 gboolean remove_reedit_target)
5816 PrefsAccount *mailac = NULL, *newsac = NULL;
5817 gboolean err = FALSE;
5819 debug_print("queueing message...\n");
5820 cm_return_val_if_fail(compose->account != NULL, -1);
5822 if (compose_check_entries(compose, check_subject) == FALSE) {
5823 if (compose->batch) {
5824 gtk_widget_show_all(compose->window);
5829 if (!compose->to_list && !compose->newsgroup_list) {
5830 g_warning("can't get recipient list.");
5834 if (compose->to_list) {
5835 if (compose->account->protocol != A_NNTP)
5836 mailac = compose->account;
5837 else if (cur_account && cur_account->protocol != A_NNTP)
5838 mailac = cur_account;
5839 else if (!(mailac = compose_current_mail_account())) {
5840 alertpanel_error(_("No account for sending mails available!"));
5845 if (compose->newsgroup_list) {
5846 if (compose->account->protocol == A_NNTP)
5847 newsac = compose->account;
5849 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5854 /* write queue header */
5855 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5856 G_DIR_SEPARATOR, compose, (guint) rand());
5857 debug_print("queuing to %s\n", tmp);
5858 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5859 FILE_OP_ERROR(tmp, "fopen");
5864 if (change_file_mode_rw(fp, tmp) < 0) {
5865 FILE_OP_ERROR(tmp, "chmod");
5866 g_warning("can't change file mode\n");
5869 /* queueing variables */
5870 err |= (fprintf(fp, "AF:\n") < 0);
5871 err |= (fprintf(fp, "NF:0\n") < 0);
5872 err |= (fprintf(fp, "PS:10\n") < 0);
5873 err |= (fprintf(fp, "SRH:1\n") < 0);
5874 err |= (fprintf(fp, "SFN:\n") < 0);
5875 err |= (fprintf(fp, "DSR:\n") < 0);
5877 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5879 err |= (fprintf(fp, "MID:\n") < 0);
5880 err |= (fprintf(fp, "CFG:\n") < 0);
5881 err |= (fprintf(fp, "PT:0\n") < 0);
5882 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5883 err |= (fprintf(fp, "RQ:\n") < 0);
5885 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5887 err |= (fprintf(fp, "SSV:\n") < 0);
5889 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5891 err |= (fprintf(fp, "NSV:\n") < 0);
5892 err |= (fprintf(fp, "SSH:\n") < 0);
5893 /* write recepient list */
5894 if (compose->to_list) {
5895 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5896 for (cur = compose->to_list->next; cur != NULL;
5898 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5899 err |= (fprintf(fp, "\n") < 0);
5901 /* write newsgroup list */
5902 if (compose->newsgroup_list) {
5903 err |= (fprintf(fp, "NG:") < 0);
5904 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5905 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5906 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5907 err |= (fprintf(fp, "\n") < 0);
5909 /* Sylpheed account IDs */
5911 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5913 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5916 if (compose->privacy_system != NULL) {
5917 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5918 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5919 if (compose->use_encryption) {
5921 if (!compose_warn_encryption(compose)) {
5927 if (mailac && mailac->encrypt_to_self) {
5928 GSList *tmp_list = g_slist_copy(compose->to_list);
5929 tmp_list = g_slist_append(tmp_list, compose->account->address);
5930 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5931 g_slist_free(tmp_list);
5933 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5935 if (encdata != NULL) {
5936 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5937 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5938 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5940 } /* else we finally dont want to encrypt */
5942 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5943 /* and if encdata was null, it means there's been a problem in
5946 g_warning("failed to write queue message");
5956 /* Save copy folder */
5957 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5958 gchar *savefolderid;
5960 savefolderid = compose_get_save_to(compose);
5961 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5962 g_free(savefolderid);
5964 /* Save copy folder */
5965 if (compose->return_receipt) {
5966 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5968 /* Message-ID of message replying to */
5969 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5972 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5973 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5976 /* Message-ID of message forwarding to */
5977 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5980 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5981 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5985 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5986 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5988 /* end of headers */
5989 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5991 if (compose->redirect_filename != NULL) {
5992 if (compose_redirect_write_to_file(compose, fp) < 0) {
6000 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6004 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6008 g_warning("failed to write queue message\n");
6014 if (fclose(fp) == EOF) {
6015 FILE_OP_ERROR(tmp, "fclose");
6021 if (item && *item) {
6024 queue = account_get_special_folder(compose->account, F_QUEUE);
6027 g_warning("can't find queue folder\n");
6032 folder_item_scan(queue);
6033 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6034 g_warning("can't queue the message\n");
6040 if (msgpath == NULL) {
6046 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6047 compose_remove_reedit_target(compose, FALSE);
6050 if ((msgnum != NULL) && (item != NULL)) {
6058 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6061 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6063 struct stat statbuf;
6064 gchar *type, *subtype;
6065 GtkTreeModel *model;
6068 model = gtk_tree_view_get_model(tree_view);
6070 if (!gtk_tree_model_get_iter_first(model, &iter))
6073 gtk_tree_model_get(model, &iter,
6077 if (!is_file_exist(ainfo->file)) {
6078 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6079 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6080 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6082 if (val == G_ALERTDEFAULT) {
6087 mimepart = procmime_mimeinfo_new();
6088 mimepart->content = MIMECONTENT_FILE;
6089 mimepart->data.filename = g_strdup(ainfo->file);
6090 mimepart->tmp = FALSE; /* or we destroy our attachment */
6091 mimepart->offset = 0;
6093 g_stat(ainfo->file, &statbuf);
6094 mimepart->length = statbuf.st_size;
6096 type = g_strdup(ainfo->content_type);
6098 if (!strchr(type, '/')) {
6100 type = g_strdup("application/octet-stream");
6103 subtype = strchr(type, '/') + 1;
6104 *(subtype - 1) = '\0';
6105 mimepart->type = procmime_get_media_type(type);
6106 mimepart->subtype = g_strdup(subtype);
6109 if (mimepart->type == MIMETYPE_MESSAGE &&
6110 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6111 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6112 } else if (mimepart->type == MIMETYPE_TEXT) {
6113 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6114 /* Text parts with no name come from multipart/alternative
6115 * forwards. Make sure the recipient won't look at the
6116 * original HTML part by mistake. */
6117 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6118 ainfo->name = g_strdup_printf(_("Original %s part"),
6122 g_hash_table_insert(mimepart->typeparameters,
6123 g_strdup("charset"), g_strdup(ainfo->charset));
6125 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6126 if (mimepart->type == MIMETYPE_APPLICATION &&
6127 !strcmp2(mimepart->subtype, "octet-stream"))
6128 g_hash_table_insert(mimepart->typeparameters,
6129 g_strdup("name"), g_strdup(ainfo->name));
6130 g_hash_table_insert(mimepart->dispositionparameters,
6131 g_strdup("filename"), g_strdup(ainfo->name));
6132 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6135 if (mimepart->type == MIMETYPE_MESSAGE
6136 || mimepart->type == MIMETYPE_MULTIPART)
6137 ainfo->encoding = ENC_BINARY;
6138 else if (compose->use_signing) {
6139 if (ainfo->encoding == ENC_7BIT)
6140 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6141 else if (ainfo->encoding == ENC_8BIT)
6142 ainfo->encoding = ENC_BASE64;
6147 procmime_encode_content(mimepart, ainfo->encoding);
6149 g_node_append(parent->node, mimepart->node);
6150 } while (gtk_tree_model_iter_next(model, &iter));
6155 static gchar *compose_quote_list_of_addresses(gchar *str)
6157 GSList *list = NULL, *item = NULL;
6158 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6160 list = address_list_append_with_comments(list, str);
6161 for (item = list; item != NULL; item = item->next) {
6162 gchar *spec = item->data;
6163 gchar *endofname = strstr(spec, " <");
6164 if (endofname != NULL) {
6167 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6168 qqname = escape_internal_quotes(qname, '"');
6170 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6171 gchar *addr = g_strdup(endofname);
6172 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6173 faddr = g_strconcat(name, addr, NULL);
6176 debug_print("new auto-quoted address: '%s'", faddr);
6180 result = g_strdup((faddr != NULL)? faddr: spec);
6182 result = g_strconcat(result,
6184 (faddr != NULL)? faddr: spec,
6187 if (faddr != NULL) {
6192 slist_free_strings_full(list);
6197 #define IS_IN_CUSTOM_HEADER(header) \
6198 (compose->account->add_customhdr && \
6199 custom_header_find(compose->account->customhdr_list, header) != NULL)
6201 static void compose_add_headerfield_from_headerlist(Compose *compose,
6203 const gchar *fieldname,
6204 const gchar *seperator)
6206 gchar *str, *fieldname_w_colon;
6207 gboolean add_field = FALSE;
6209 ComposeHeaderEntry *headerentry;
6210 const gchar *headerentryname;
6211 const gchar *trans_fieldname;
6214 if (IS_IN_CUSTOM_HEADER(fieldname))
6217 debug_print("Adding %s-fields\n", fieldname);
6219 fieldstr = g_string_sized_new(64);
6221 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6222 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6224 for (list = compose->header_list; list; list = list->next) {
6225 headerentry = ((ComposeHeaderEntry *)list->data);
6226 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6228 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6229 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6231 str = compose_quote_list_of_addresses(ustr);
6233 if (str != NULL && str[0] != '\0') {
6235 g_string_append(fieldstr, seperator);
6236 g_string_append(fieldstr, str);
6245 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6246 compose_convert_header
6247 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6248 strlen(fieldname) + 2, TRUE);
6249 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6253 g_free(fieldname_w_colon);
6254 g_string_free(fieldstr, TRUE);
6259 static gchar *compose_get_manual_headers_info(Compose *compose)
6261 GString *sh_header = g_string_new(" ");
6263 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6265 for (list = compose->header_list; list; list = list->next) {
6266 ComposeHeaderEntry *headerentry;
6269 gchar *headername_wcolon;
6270 const gchar *headername_trans;
6272 gboolean standard_header = FALSE;
6274 headerentry = ((ComposeHeaderEntry *)list->data);
6276 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6278 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6283 if (!strstr(tmp, ":")) {
6284 headername_wcolon = g_strconcat(tmp, ":", NULL);
6285 headername = g_strdup(tmp);
6287 headername_wcolon = g_strdup(tmp);
6288 headername = g_strdup(strtok(tmp, ":"));
6292 string = std_headers;
6293 while (*string != NULL) {
6294 headername_trans = prefs_common_translated_header_name(*string);
6295 if (!strcmp(headername_trans, headername_wcolon))
6296 standard_header = TRUE;
6299 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6300 g_string_append_printf(sh_header, "%s ", headername);
6302 g_free(headername_wcolon);
6304 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6305 return g_string_free(sh_header, FALSE);
6308 static gchar *compose_get_header(Compose *compose)
6310 gchar buf[BUFFSIZE];
6311 const gchar *entry_str;
6315 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6317 gchar *from_name = NULL, *from_address = NULL;
6320 cm_return_val_if_fail(compose->account != NULL, NULL);
6321 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6323 header = g_string_sized_new(64);
6326 get_rfc822_date(buf, sizeof(buf));
6327 g_string_append_printf(header, "Date: %s\n", buf);
6331 if (compose->account->name && *compose->account->name) {
6333 QUOTE_IF_REQUIRED(buf, compose->account->name);
6334 tmp = g_strdup_printf("%s <%s>",
6335 buf, compose->account->address);
6337 tmp = g_strdup_printf("%s",
6338 compose->account->address);
6340 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6341 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6343 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6344 from_address = g_strdup(compose->account->address);
6346 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6347 /* extract name and address */
6348 if (strstr(spec, " <") && strstr(spec, ">")) {
6349 from_address = g_strdup(strrchr(spec, '<')+1);
6350 *(strrchr(from_address, '>')) = '\0';
6351 from_name = g_strdup(spec);
6352 *(strrchr(from_name, '<')) = '\0';
6355 from_address = g_strdup(spec);
6362 if (from_name && *from_name) {
6364 compose_convert_header
6365 (compose, buf, sizeof(buf), from_name,
6366 strlen("From: "), TRUE);
6367 QUOTE_IF_REQUIRED(name, buf);
6368 qname = escape_internal_quotes(name, '"');
6370 g_string_append_printf(header, "From: %s <%s>\n",
6371 qname, from_address);
6375 g_string_append_printf(header, "From: %s\n", from_address);
6378 g_free(from_address);
6381 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6384 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6387 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6391 * If this account is a NNTP account remove Bcc header from
6392 * message body since it otherwise will be publicly shown
6394 if (compose->account->protocol != A_NNTP)
6395 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6398 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6400 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6403 compose_convert_header(compose, buf, sizeof(buf), str,
6404 strlen("Subject: "), FALSE);
6405 g_string_append_printf(header, "Subject: %s\n", buf);
6411 if (compose->account->set_domain && compose->account->domain) {
6412 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6413 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6414 g_snprintf(buf, sizeof(buf), "%s",
6415 strchr(compose->account->address, '@') ?
6416 strchr(compose->account->address, '@')+1 :
6417 compose->account->address);
6419 g_snprintf(buf, sizeof(buf), "%s", "");
6422 if (compose->account->gen_msgid) {
6424 if (compose->account->msgid_with_addr) {
6425 addr = compose->account->address;
6427 generate_msgid(buf, sizeof(buf), addr);
6428 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6430 g_free(compose->msgid);
6431 compose->msgid = g_strdup(buf);
6433 compose->msgid = NULL;
6436 if (compose->remove_references == FALSE) {
6438 if (compose->inreplyto && compose->to_list)
6439 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6442 if (compose->references)
6443 g_string_append_printf(header, "References: %s\n", compose->references);
6447 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6450 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6453 if (compose->account->organization &&
6454 strlen(compose->account->organization) &&
6455 !IS_IN_CUSTOM_HEADER("Organization")) {
6456 compose_convert_header(compose, buf, sizeof(buf),
6457 compose->account->organization,
6458 strlen("Organization: "), FALSE);
6459 g_string_append_printf(header, "Organization: %s\n", buf);
6462 /* Program version and system info */
6463 if (compose->account->gen_xmailer &&
6464 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6465 !compose->newsgroup_list) {
6466 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6468 gtk_major_version, gtk_minor_version, gtk_micro_version,
6471 if (compose->account->gen_xmailer &&
6472 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6473 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6475 gtk_major_version, gtk_minor_version, gtk_micro_version,
6479 /* custom headers */
6480 if (compose->account->add_customhdr) {
6483 for (cur = compose->account->customhdr_list; cur != NULL;
6485 CustomHeader *chdr = (CustomHeader *)cur->data;
6487 if (custom_header_is_allowed(chdr->name)
6488 && chdr->value != NULL
6489 && *(chdr->value) != '\0') {
6490 compose_convert_header
6491 (compose, buf, sizeof(buf),
6493 strlen(chdr->name) + 2, FALSE);
6494 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6499 /* Automatic Faces and X-Faces */
6500 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6501 g_string_append_printf(header, "X-Face: %s\n", buf);
6503 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6504 g_string_append_printf(header, "X-Face: %s\n", buf);
6506 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6507 g_string_append_printf(header, "Face: %s\n", buf);
6509 else if (get_default_face (buf, sizeof(buf)) == 0) {
6510 g_string_append_printf(header, "Face: %s\n", buf);
6514 switch (compose->priority) {
6515 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6516 "X-Priority: 1 (Highest)\n");
6518 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6519 "X-Priority: 2 (High)\n");
6521 case PRIORITY_NORMAL: break;
6522 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6523 "X-Priority: 4 (Low)\n");
6525 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6526 "X-Priority: 5 (Lowest)\n");
6528 default: debug_print("compose: priority unknown : %d\n",
6532 /* Request Return Receipt */
6533 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6534 if (compose->return_receipt) {
6535 if (compose->account->name
6536 && *compose->account->name) {
6537 compose_convert_header(compose, buf, sizeof(buf),
6538 compose->account->name,
6539 strlen("Disposition-Notification-To: "),
6541 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6543 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6547 /* get special headers */
6548 for (list = compose->header_list; list; list = list->next) {
6549 ComposeHeaderEntry *headerentry;
6552 gchar *headername_wcolon;
6553 const gchar *headername_trans;
6556 gboolean standard_header = FALSE;
6558 headerentry = ((ComposeHeaderEntry *)list->data);
6560 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6562 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6567 if (!strstr(tmp, ":")) {
6568 headername_wcolon = g_strconcat(tmp, ":", NULL);
6569 headername = g_strdup(tmp);
6571 headername_wcolon = g_strdup(tmp);
6572 headername = g_strdup(strtok(tmp, ":"));
6576 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6577 Xstrdup_a(headervalue, entry_str, return NULL);
6578 subst_char(headervalue, '\r', ' ');
6579 subst_char(headervalue, '\n', ' ');
6580 string = std_headers;
6581 while (*string != NULL) {
6582 headername_trans = prefs_common_translated_header_name(*string);
6583 if (!strcmp(headername_trans, headername_wcolon))
6584 standard_header = TRUE;
6587 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6588 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6591 g_free(headername_wcolon);
6595 g_string_free(header, FALSE);
6600 #undef IS_IN_CUSTOM_HEADER
6602 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6603 gint header_len, gboolean addr_field)
6605 gchar *tmpstr = NULL;
6606 const gchar *out_codeset = NULL;
6608 cm_return_if_fail(src != NULL);
6609 cm_return_if_fail(dest != NULL);
6611 if (len < 1) return;
6613 tmpstr = g_strdup(src);
6615 subst_char(tmpstr, '\n', ' ');
6616 subst_char(tmpstr, '\r', ' ');
6619 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6620 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6621 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6626 codeconv_set_strict(TRUE);
6627 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6628 conv_get_charset_str(compose->out_encoding));
6629 codeconv_set_strict(FALSE);
6631 if (!dest || *dest == '\0') {
6632 gchar *test_conv_global_out = NULL;
6633 gchar *test_conv_reply = NULL;
6635 /* automatic mode. be automatic. */
6636 codeconv_set_strict(TRUE);
6638 out_codeset = conv_get_outgoing_charset_str();
6640 debug_print("trying to convert to %s\n", out_codeset);
6641 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6644 if (!test_conv_global_out && compose->orig_charset
6645 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6646 out_codeset = compose->orig_charset;
6647 debug_print("failure; trying to convert to %s\n", out_codeset);
6648 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6651 if (!test_conv_global_out && !test_conv_reply) {
6653 out_codeset = CS_INTERNAL;
6654 debug_print("finally using %s\n", out_codeset);
6656 g_free(test_conv_global_out);
6657 g_free(test_conv_reply);
6658 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6660 codeconv_set_strict(FALSE);
6665 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6669 cm_return_if_fail(user_data != NULL);
6671 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6672 g_strstrip(address);
6673 if (*address != '\0') {
6674 gchar *name = procheader_get_fromname(address);
6675 extract_address(address);
6676 #ifndef USE_NEW_ADDRBOOK
6677 addressbook_add_contact(name, address, NULL, NULL);
6679 debug_print("%s: %s\n", name, address);
6680 if (addressadd_selection(name, address, NULL, NULL)) {
6681 debug_print( "addressbook_add_contact - added\n" );
6688 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6690 GtkWidget *menuitem;
6693 cm_return_if_fail(menu != NULL);
6694 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6696 menuitem = gtk_separator_menu_item_new();
6697 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6698 gtk_widget_show(menuitem);
6700 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6701 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6703 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6704 g_strstrip(address);
6705 if (*address == '\0') {
6706 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6709 g_signal_connect(G_OBJECT(menuitem), "activate",
6710 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6711 gtk_widget_show(menuitem);
6714 void compose_add_extra_header(gchar *header, GtkListStore *model)
6717 if (strcmp(header, "")) {
6718 COMBOBOX_ADD(model, header, COMPOSE_TO);
6722 void compose_add_extra_header_entries(GtkListStore *model)
6726 gchar buf[BUFFSIZE];
6729 if (extra_headers == NULL) {
6730 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6731 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6732 debug_print("extra headers file not found\n");
6733 goto extra_headers_done;
6735 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6736 lastc = strlen(buf) - 1; /* remove trailing control chars */
6737 while (lastc >= 0 && buf[lastc] != ':')
6738 buf[lastc--] = '\0';
6739 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6740 buf[lastc] = '\0'; /* remove trailing : for comparison */
6741 if (custom_header_is_allowed(buf)) {
6743 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6746 g_message("disallowed extra header line: %s\n", buf);
6750 g_message("invalid extra header line: %s\n", buf);
6756 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6757 extra_headers = g_slist_reverse(extra_headers);
6759 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6762 static void compose_create_header_entry(Compose *compose)
6764 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6771 const gchar *header = NULL;
6772 ComposeHeaderEntry *headerentry;
6773 gboolean standard_header = FALSE;
6774 GtkListStore *model;
6776 #if !(GTK_CHECK_VERSION(2,12,0))
6777 GtkTooltips *tips = compose->tooltips;
6780 headerentry = g_new0(ComposeHeaderEntry, 1);
6782 /* Combo box model */
6783 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6784 #if !GTK_CHECK_VERSION(2, 24, 0)
6785 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6787 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6789 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6791 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6793 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6794 COMPOSE_NEWSGROUPS);
6795 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6797 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6798 COMPOSE_FOLLOWUPTO);
6799 compose_add_extra_header_entries(model);
6802 #if GTK_CHECK_VERSION(2, 24, 0)
6803 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6804 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6805 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6806 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6807 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6809 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6810 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6811 G_CALLBACK(compose_grab_focus_cb), compose);
6812 gtk_widget_show(combo);
6815 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6816 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6819 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6820 compose->header_nextrow, compose->header_nextrow+1,
6821 GTK_SHRINK, GTK_FILL, 0, 0);
6822 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6823 const gchar *last_header_entry = gtk_entry_get_text(
6824 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6826 while (*string != NULL) {
6827 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6828 standard_header = TRUE;
6831 if (standard_header)
6832 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6834 if (!compose->header_last || !standard_header) {
6835 switch(compose->account->protocol) {
6837 header = prefs_common_translated_header_name("Newsgroups:");
6840 header = prefs_common_translated_header_name("To:");
6845 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6847 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6848 G_CALLBACK(compose_grab_focus_cb), compose);
6850 /* Entry field with cleanup button */
6851 button = gtk_button_new();
6852 gtk_button_set_image(GTK_BUTTON(button),
6853 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6854 gtk_widget_show(button);
6855 CLAWS_SET_TIP(button,
6856 _("Delete entry contents"));
6857 entry = gtk_entry_new();
6858 gtk_widget_show(entry);
6859 CLAWS_SET_TIP(entry,
6860 _("Use <tab> to autocomplete from addressbook"));
6861 hbox = gtk_hbox_new (FALSE, 0);
6862 gtk_widget_show(hbox);
6863 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6864 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6865 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6866 compose->header_nextrow, compose->header_nextrow+1,
6867 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6869 g_signal_connect(G_OBJECT(entry), "key-press-event",
6870 G_CALLBACK(compose_headerentry_key_press_event_cb),
6872 g_signal_connect(G_OBJECT(entry), "changed",
6873 G_CALLBACK(compose_headerentry_changed_cb),
6875 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6876 G_CALLBACK(compose_grab_focus_cb), compose);
6878 g_signal_connect(G_OBJECT(button), "clicked",
6879 G_CALLBACK(compose_headerentry_button_clicked_cb),
6883 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6884 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6885 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6886 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6887 G_CALLBACK(compose_header_drag_received_cb),
6889 g_signal_connect(G_OBJECT(entry), "drag-drop",
6890 G_CALLBACK(compose_drag_drop),
6892 g_signal_connect(G_OBJECT(entry), "populate-popup",
6893 G_CALLBACK(compose_entry_popup_extend),
6896 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6898 headerentry->compose = compose;
6899 headerentry->combo = combo;
6900 headerentry->entry = entry;
6901 headerentry->button = button;
6902 headerentry->hbox = hbox;
6903 headerentry->headernum = compose->header_nextrow;
6904 headerentry->type = PREF_NONE;
6906 compose->header_nextrow++;
6907 compose->header_last = headerentry;
6908 compose->header_list =
6909 g_slist_append(compose->header_list,
6913 static void compose_add_header_entry(Compose *compose, const gchar *header,
6914 gchar *text, ComposePrefType pref_type)
6916 ComposeHeaderEntry *last_header = compose->header_last;
6917 gchar *tmp = g_strdup(text), *email;
6918 gboolean replyto_hdr;
6920 replyto_hdr = (!strcasecmp(header,
6921 prefs_common_translated_header_name("Reply-To:")) ||
6923 prefs_common_translated_header_name("Followup-To:")) ||
6925 prefs_common_translated_header_name("In-Reply-To:")));
6927 extract_address(tmp);
6928 email = g_utf8_strdown(tmp, -1);
6930 if (replyto_hdr == FALSE &&
6931 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6933 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6934 header, text, (gint) pref_type);
6940 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6941 gtk_entry_set_text(GTK_ENTRY(
6942 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6944 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6945 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6946 last_header->type = pref_type;
6948 if (replyto_hdr == FALSE)
6949 g_hash_table_insert(compose->email_hashtable, email,
6950 GUINT_TO_POINTER(1));
6957 static void compose_destroy_headerentry(Compose *compose,
6958 ComposeHeaderEntry *headerentry)
6960 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6963 extract_address(text);
6964 email = g_utf8_strdown(text, -1);
6965 g_hash_table_remove(compose->email_hashtable, email);
6969 gtk_widget_destroy(headerentry->combo);
6970 gtk_widget_destroy(headerentry->entry);
6971 gtk_widget_destroy(headerentry->button);
6972 gtk_widget_destroy(headerentry->hbox);
6973 g_free(headerentry);
6976 static void compose_remove_header_entries(Compose *compose)
6979 for (list = compose->header_list; list; list = list->next)
6980 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6982 compose->header_last = NULL;
6983 g_slist_free(compose->header_list);
6984 compose->header_list = NULL;
6985 compose->header_nextrow = 1;
6986 compose_create_header_entry(compose);
6989 static GtkWidget *compose_create_header(Compose *compose)
6991 GtkWidget *from_optmenu_hbox;
6992 GtkWidget *header_scrolledwin_main;
6993 GtkWidget *header_table_main;
6994 GtkWidget *header_scrolledwin;
6995 GtkWidget *header_table;
6997 /* parent with account selection and from header */
6998 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6999 gtk_widget_show(header_scrolledwin_main);
7000 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7002 header_table_main = gtk_table_new(2, 2, FALSE);
7003 gtk_widget_show(header_table_main);
7004 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7005 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7006 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7008 from_optmenu_hbox = compose_account_option_menu_create(compose);
7009 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7010 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7012 /* child with header labels and entries */
7013 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7014 gtk_widget_show(header_scrolledwin);
7015 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7017 header_table = gtk_table_new(2, 2, FALSE);
7018 gtk_widget_show(header_table);
7019 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7020 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7021 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7023 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7024 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7026 compose->header_table = header_table;
7027 compose->header_list = NULL;
7028 compose->header_nextrow = 0;
7030 compose_create_header_entry(compose);
7032 compose->table = NULL;
7034 return header_scrolledwin_main;
7037 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7039 Compose *compose = (Compose *)data;
7040 GdkEventButton event;
7043 event.time = gtk_get_current_event_time();
7045 return attach_button_pressed(compose->attach_clist, &event, compose);
7048 static GtkWidget *compose_create_attach(Compose *compose)
7050 GtkWidget *attach_scrwin;
7051 GtkWidget *attach_clist;
7053 GtkListStore *store;
7054 GtkCellRenderer *renderer;
7055 GtkTreeViewColumn *column;
7056 GtkTreeSelection *selection;
7058 /* attachment list */
7059 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7060 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7061 GTK_POLICY_AUTOMATIC,
7062 GTK_POLICY_AUTOMATIC);
7063 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7065 store = gtk_list_store_new(N_ATTACH_COLS,
7071 G_TYPE_AUTO_POINTER,
7073 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7074 (GTK_TREE_MODEL(store)));
7075 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7076 g_object_unref(store);
7078 renderer = gtk_cell_renderer_text_new();
7079 column = gtk_tree_view_column_new_with_attributes
7080 (_("Mime type"), renderer, "text",
7081 COL_MIMETYPE, NULL);
7082 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7084 renderer = gtk_cell_renderer_text_new();
7085 column = gtk_tree_view_column_new_with_attributes
7086 (_("Size"), renderer, "text",
7088 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7090 renderer = gtk_cell_renderer_text_new();
7091 column = gtk_tree_view_column_new_with_attributes
7092 (_("Name"), renderer, "text",
7094 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7096 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7097 prefs_common.use_stripes_everywhere);
7098 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7099 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7101 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7102 G_CALLBACK(attach_selected), compose);
7103 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7104 G_CALLBACK(attach_button_pressed), compose);
7105 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7106 G_CALLBACK(popup_attach_button_pressed), compose);
7107 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7108 G_CALLBACK(attach_key_pressed), compose);
7111 gtk_drag_dest_set(attach_clist,
7112 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7113 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7114 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7115 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7116 G_CALLBACK(compose_attach_drag_received_cb),
7118 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7119 G_CALLBACK(compose_drag_drop),
7122 compose->attach_scrwin = attach_scrwin;
7123 compose->attach_clist = attach_clist;
7125 return attach_scrwin;
7128 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7129 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7131 static GtkWidget *compose_create_others(Compose *compose)
7134 GtkWidget *savemsg_checkbtn;
7135 GtkWidget *savemsg_combo;
7136 GtkWidget *savemsg_select;
7139 gchar *folderidentifier;
7141 /* Table for settings */
7142 table = gtk_table_new(3, 1, FALSE);
7143 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7144 gtk_widget_show(table);
7145 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7148 /* Save Message to folder */
7149 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7150 gtk_widget_show(savemsg_checkbtn);
7151 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7152 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7153 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7155 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7156 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7158 #if !GTK_CHECK_VERSION(2, 24, 0)
7159 savemsg_combo = gtk_combo_box_entry_new_text();
7161 savemsg_combo = gtk_combo_box_text_new_with_entry();
7163 compose->savemsg_checkbtn = savemsg_checkbtn;
7164 compose->savemsg_combo = savemsg_combo;
7165 gtk_widget_show(savemsg_combo);
7167 if (prefs_common.compose_save_to_history)
7168 #if !GTK_CHECK_VERSION(2, 24, 0)
7169 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7170 prefs_common.compose_save_to_history);
7172 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7173 prefs_common.compose_save_to_history);
7175 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7176 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7177 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7178 G_CALLBACK(compose_grab_focus_cb), compose);
7179 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7180 folderidentifier = folder_item_get_identifier(account_get_special_folder
7181 (compose->account, F_OUTBOX));
7182 compose_set_save_to(compose, folderidentifier);
7183 g_free(folderidentifier);
7186 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7187 gtk_widget_show(savemsg_select);
7188 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7189 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7190 G_CALLBACK(compose_savemsg_select_cb),
7196 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7198 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7199 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7202 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7207 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7210 path = folder_item_get_identifier(dest);
7212 compose_set_save_to(compose, path);
7216 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7217 GdkAtom clip, GtkTextIter *insert_place);
7220 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7224 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7226 if (event->button == 3) {
7228 GtkTextIter sel_start, sel_end;
7229 gboolean stuff_selected;
7231 /* move the cursor to allow GtkAspell to check the word
7232 * under the mouse */
7233 if (event->x && event->y) {
7234 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7235 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7237 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7240 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7241 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7244 stuff_selected = gtk_text_buffer_get_selection_bounds(
7246 &sel_start, &sel_end);
7248 gtk_text_buffer_place_cursor (buffer, &iter);
7249 /* reselect stuff */
7251 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7252 gtk_text_buffer_select_range(buffer,
7253 &sel_start, &sel_end);
7255 return FALSE; /* pass the event so that the right-click goes through */
7258 if (event->button == 2) {
7263 /* get the middle-click position to paste at the correct place */
7264 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7265 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7267 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7270 entry_paste_clipboard(compose, text,
7271 prefs_common.linewrap_pastes,
7272 GDK_SELECTION_PRIMARY, &iter);
7280 static void compose_spell_menu_changed(void *data)
7282 Compose *compose = (Compose *)data;
7284 GtkWidget *menuitem;
7285 GtkWidget *parent_item;
7286 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7289 if (compose->gtkaspell == NULL)
7292 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7293 "/Menu/Spelling/Options");
7295 /* setting the submenu removes /Spelling/Options from the factory
7296 * so we need to save it */
7298 if (parent_item == NULL) {
7299 parent_item = compose->aspell_options_menu;
7300 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7302 compose->aspell_options_menu = parent_item;
7304 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7306 spell_menu = g_slist_reverse(spell_menu);
7307 for (items = spell_menu;
7308 items; items = items->next) {
7309 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7310 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7311 gtk_widget_show(GTK_WIDGET(menuitem));
7313 g_slist_free(spell_menu);
7315 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7316 gtk_widget_show(parent_item);
7319 static void compose_dict_changed(void *data)
7321 Compose *compose = (Compose *) data;
7323 if(compose->gtkaspell &&
7324 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7327 gtkaspell_highlight_all(compose->gtkaspell);
7328 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7332 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7334 Compose *compose = (Compose *)data;
7335 GdkEventButton event;
7338 event.time = gtk_get_current_event_time();
7342 return text_clicked(compose->text, &event, compose);
7345 static gboolean compose_force_window_origin = TRUE;
7346 static Compose *compose_create(PrefsAccount *account,
7355 GtkWidget *handlebox;
7357 GtkWidget *notebook;
7359 GtkWidget *attach_hbox;
7360 GtkWidget *attach_lab1;
7361 GtkWidget *attach_lab2;
7366 GtkWidget *subject_hbox;
7367 GtkWidget *subject_frame;
7368 GtkWidget *subject_entry;
7372 GtkWidget *edit_vbox;
7373 GtkWidget *ruler_hbox;
7375 GtkWidget *scrolledwin;
7377 GtkTextBuffer *buffer;
7378 GtkClipboard *clipboard;
7380 UndoMain *undostruct;
7382 GtkWidget *popupmenu;
7383 GtkWidget *tmpl_menu;
7384 GtkActionGroup *action_group = NULL;
7387 GtkAspell * gtkaspell = NULL;
7390 static GdkGeometry geometry;
7392 cm_return_val_if_fail(account != NULL, NULL);
7394 debug_print("Creating compose window...\n");
7395 compose = g_new0(Compose, 1);
7397 compose->batch = batch;
7398 compose->account = account;
7399 compose->folder = folder;
7401 compose->mutex = cm_mutex_new();
7402 compose->set_cursor_pos = -1;
7404 #if !(GTK_CHECK_VERSION(2,12,0))
7405 compose->tooltips = tips;
7408 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7410 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7411 gtk_widget_set_size_request(window, prefs_common.compose_width,
7412 prefs_common.compose_height);
7414 if (!geometry.max_width) {
7415 geometry.max_width = gdk_screen_width();
7416 geometry.max_height = gdk_screen_height();
7419 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7420 &geometry, GDK_HINT_MAX_SIZE);
7421 if (!geometry.min_width) {
7422 geometry.min_width = 600;
7423 geometry.min_height = 440;
7425 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7426 &geometry, GDK_HINT_MIN_SIZE);
7428 #ifndef GENERIC_UMPC
7429 if (compose_force_window_origin)
7430 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7431 prefs_common.compose_y);
7433 g_signal_connect(G_OBJECT(window), "delete_event",
7434 G_CALLBACK(compose_delete_cb), compose);
7435 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7436 gtk_widget_realize(window);
7438 gtkut_widget_set_composer_icon(window);
7440 vbox = gtk_vbox_new(FALSE, 0);
7441 gtk_container_add(GTK_CONTAINER(window), vbox);
7443 compose->ui_manager = gtk_ui_manager_new();
7444 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7445 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7446 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7447 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7448 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7449 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7450 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7451 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7452 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7453 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7470 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7484 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7492 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7493 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7497 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7498 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7499 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7500 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7505 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7572 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)
7573 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)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7579 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)
7580 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)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7585 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)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7589 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)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7595 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)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7601 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)
7602 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)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7615 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)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7625 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7633 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7634 gtk_widget_show_all(menubar);
7636 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7637 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7639 if (prefs_common.toolbar_detachable) {
7640 handlebox = gtk_handle_box_new();
7642 handlebox = gtk_hbox_new(FALSE, 0);
7644 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7646 gtk_widget_realize(handlebox);
7647 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7650 vbox2 = gtk_vbox_new(FALSE, 2);
7651 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7652 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7655 notebook = gtk_notebook_new();
7656 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7657 gtk_widget_show(notebook);
7659 /* header labels and entries */
7660 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7661 compose_create_header(compose),
7662 gtk_label_new_with_mnemonic(_("Hea_der")));
7663 /* attachment list */
7664 attach_hbox = gtk_hbox_new(FALSE, 0);
7665 gtk_widget_show(attach_hbox);
7667 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7668 gtk_widget_show(attach_lab1);
7669 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7671 attach_lab2 = gtk_label_new("");
7672 gtk_widget_show(attach_lab2);
7673 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7675 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7676 compose_create_attach(compose),
7679 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7680 compose_create_others(compose),
7681 gtk_label_new_with_mnemonic(_("Othe_rs")));
7684 subject_hbox = gtk_hbox_new(FALSE, 0);
7685 gtk_widget_show(subject_hbox);
7687 subject_frame = gtk_frame_new(NULL);
7688 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7689 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7690 gtk_widget_show(subject_frame);
7692 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7693 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7694 gtk_widget_show(subject);
7696 label = gtk_label_new(_("Subject:"));
7697 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7698 gtk_widget_show(label);
7701 subject_entry = claws_spell_entry_new();
7703 subject_entry = gtk_entry_new();
7705 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7706 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7707 G_CALLBACK(compose_grab_focus_cb), compose);
7708 gtk_widget_show(subject_entry);
7709 compose->subject_entry = subject_entry;
7710 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7712 edit_vbox = gtk_vbox_new(FALSE, 0);
7714 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7717 ruler_hbox = gtk_hbox_new(FALSE, 0);
7718 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7720 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7721 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7722 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7726 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7727 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7728 GTK_POLICY_AUTOMATIC,
7729 GTK_POLICY_AUTOMATIC);
7730 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7732 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7734 text = gtk_text_view_new();
7735 if (prefs_common.show_compose_margin) {
7736 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7737 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7739 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7740 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7741 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7742 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7743 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7745 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7746 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7747 G_CALLBACK(compose_notebook_size_alloc), compose);
7748 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7749 G_CALLBACK(compose_edit_size_alloc),
7751 g_signal_connect(G_OBJECT(buffer), "changed",
7752 G_CALLBACK(compose_changed_cb), compose);
7753 g_signal_connect(G_OBJECT(text), "grab_focus",
7754 G_CALLBACK(compose_grab_focus_cb), compose);
7755 g_signal_connect(G_OBJECT(buffer), "insert_text",
7756 G_CALLBACK(text_inserted), compose);
7757 g_signal_connect(G_OBJECT(text), "button_press_event",
7758 G_CALLBACK(text_clicked), compose);
7759 g_signal_connect(G_OBJECT(text), "popup-menu",
7760 G_CALLBACK(compose_popup_menu), compose);
7761 g_signal_connect(G_OBJECT(subject_entry), "changed",
7762 G_CALLBACK(compose_changed_cb), compose);
7763 g_signal_connect(G_OBJECT(subject_entry), "activate",
7764 G_CALLBACK(compose_subject_entry_activated), compose);
7767 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7768 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7769 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7770 g_signal_connect(G_OBJECT(text), "drag_data_received",
7771 G_CALLBACK(compose_insert_drag_received_cb),
7773 g_signal_connect(G_OBJECT(text), "drag-drop",
7774 G_CALLBACK(compose_drag_drop),
7776 g_signal_connect(G_OBJECT(text), "key-press-event",
7777 G_CALLBACK(completion_set_focus_to_subject),
7779 gtk_widget_show_all(vbox);
7781 /* pane between attach clist and text */
7782 paned = gtk_vpaned_new();
7783 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7784 gtk_paned_add1(GTK_PANED(paned), notebook);
7785 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7786 gtk_widget_show_all(paned);
7789 if (prefs_common.textfont) {
7790 PangoFontDescription *font_desc;
7792 font_desc = pango_font_description_from_string
7793 (prefs_common.textfont);
7795 gtk_widget_modify_font(text, font_desc);
7796 pango_font_description_free(font_desc);
7800 gtk_action_group_add_actions(action_group, compose_popup_entries,
7801 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7802 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7803 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7804 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7805 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7809 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7811 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7812 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7813 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7815 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7817 undostruct = undo_init(text);
7818 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7821 address_completion_start(window);
7823 compose->window = window;
7824 compose->vbox = vbox;
7825 compose->menubar = menubar;
7826 compose->handlebox = handlebox;
7828 compose->vbox2 = vbox2;
7830 compose->paned = paned;
7832 compose->attach_label = attach_lab2;
7834 compose->notebook = notebook;
7835 compose->edit_vbox = edit_vbox;
7836 compose->ruler_hbox = ruler_hbox;
7837 compose->ruler = ruler;
7838 compose->scrolledwin = scrolledwin;
7839 compose->text = text;
7841 compose->focused_editable = NULL;
7843 compose->popupmenu = popupmenu;
7845 compose->tmpl_menu = tmpl_menu;
7847 compose->mode = mode;
7848 compose->rmode = mode;
7850 compose->targetinfo = NULL;
7851 compose->replyinfo = NULL;
7852 compose->fwdinfo = NULL;
7854 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7855 g_str_equal, (GDestroyNotify) g_free, NULL);
7857 compose->replyto = NULL;
7859 compose->bcc = NULL;
7860 compose->followup_to = NULL;
7862 compose->ml_post = NULL;
7864 compose->inreplyto = NULL;
7865 compose->references = NULL;
7866 compose->msgid = NULL;
7867 compose->boundary = NULL;
7869 compose->autowrap = prefs_common.autowrap;
7870 compose->autoindent = prefs_common.auto_indent;
7871 compose->use_signing = FALSE;
7872 compose->use_encryption = FALSE;
7873 compose->privacy_system = NULL;
7875 compose->modified = FALSE;
7877 compose->return_receipt = FALSE;
7879 compose->to_list = NULL;
7880 compose->newsgroup_list = NULL;
7882 compose->undostruct = undostruct;
7884 compose->sig_str = NULL;
7886 compose->exteditor_file = NULL;
7887 compose->exteditor_pid = -1;
7888 compose->exteditor_tag = -1;
7889 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7892 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7893 if (mode != COMPOSE_REDIRECT) {
7894 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7895 strcmp(prefs_common.dictionary, "")) {
7896 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7897 prefs_common.alt_dictionary,
7898 conv_get_locale_charset_str(),
7899 prefs_common.misspelled_col,
7900 prefs_common.check_while_typing,
7901 prefs_common.recheck_when_changing_dict,
7902 prefs_common.use_alternate,
7903 prefs_common.use_both_dicts,
7904 GTK_TEXT_VIEW(text),
7905 GTK_WINDOW(compose->window),
7906 compose_dict_changed,
7907 compose_spell_menu_changed,
7910 alertpanel_error(_("Spell checker could not "
7912 gtkaspell_checkers_strerror());
7913 gtkaspell_checkers_reset_error();
7915 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7919 compose->gtkaspell = gtkaspell;
7920 compose_spell_menu_changed(compose);
7921 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7924 compose_select_account(compose, account, TRUE);
7926 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7927 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7929 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7930 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7932 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7933 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7935 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7936 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7938 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7939 if (account->protocol != A_NNTP)
7940 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7941 prefs_common_translated_header_name("To:"));
7943 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7944 prefs_common_translated_header_name("Newsgroups:"));
7946 #ifndef USE_NEW_ADDRBOOK
7947 addressbook_set_target_compose(compose);
7949 if (mode != COMPOSE_REDIRECT)
7950 compose_set_template_menu(compose);
7952 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7955 compose_list = g_list_append(compose_list, compose);
7957 if (!prefs_common.show_ruler)
7958 gtk_widget_hide(ruler_hbox);
7960 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7963 compose->priority = PRIORITY_NORMAL;
7964 compose_update_priority_menu_item(compose);
7966 compose_set_out_encoding(compose);
7969 compose_update_actions_menu(compose);
7971 /* Privacy Systems menu */
7972 compose_update_privacy_systems_menu(compose);
7974 activate_privacy_system(compose, account, TRUE);
7975 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7977 gtk_widget_realize(window);
7979 gtk_widget_show(window);
7985 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7990 GtkWidget *optmenubox;
7993 GtkWidget *from_name = NULL;
7994 #if !(GTK_CHECK_VERSION(2,12,0))
7995 GtkTooltips *tips = compose->tooltips;
7998 gint num = 0, def_menu = 0;
8000 accounts = account_get_list();
8001 cm_return_val_if_fail(accounts != NULL, NULL);
8003 optmenubox = gtk_event_box_new();
8004 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8005 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8007 hbox = gtk_hbox_new(FALSE, 6);
8008 from_name = gtk_entry_new();
8010 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8011 G_CALLBACK(compose_grab_focus_cb), compose);
8013 for (; accounts != NULL; accounts = accounts->next, num++) {
8014 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8015 gchar *name, *from = NULL;
8017 if (ac == compose->account) def_menu = num;
8019 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8022 if (ac == compose->account) {
8023 if (ac->name && *ac->name) {
8025 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8026 from = g_strdup_printf("%s <%s>",
8028 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8030 from = g_strdup_printf("%s",
8032 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8035 COMBOBOX_ADD(menu, name, ac->account_id);
8040 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8042 g_signal_connect(G_OBJECT(optmenu), "changed",
8043 G_CALLBACK(account_activated),
8045 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8046 G_CALLBACK(compose_entry_popup_extend),
8049 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8050 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8052 CLAWS_SET_TIP(optmenubox,
8053 _("Account to use for this email"));
8054 CLAWS_SET_TIP(from_name,
8055 _("Sender address to be used"));
8057 compose->account_combo = optmenu;
8058 compose->from_name = from_name;
8063 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8065 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8066 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8067 Compose *compose = (Compose *) data;
8069 compose->priority = value;
8073 static void compose_reply_change_mode(Compose *compose,
8076 gboolean was_modified = compose->modified;
8078 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8080 cm_return_if_fail(compose->replyinfo != NULL);
8082 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8084 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8086 if (action == COMPOSE_REPLY_TO_ALL)
8088 if (action == COMPOSE_REPLY_TO_SENDER)
8090 if (action == COMPOSE_REPLY_TO_LIST)
8093 compose_remove_header_entries(compose);
8094 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8095 if (compose->account->set_autocc && compose->account->auto_cc)
8096 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8098 if (compose->account->set_autobcc && compose->account->auto_bcc)
8099 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8101 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8102 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8103 compose_show_first_last_header(compose, TRUE);
8104 compose->modified = was_modified;
8105 compose_set_title(compose);
8108 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8110 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8111 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8112 Compose *compose = (Compose *) data;
8115 compose_reply_change_mode(compose, value);
8118 static void compose_update_priority_menu_item(Compose * compose)
8120 GtkWidget *menuitem = NULL;
8121 switch (compose->priority) {
8122 case PRIORITY_HIGHEST:
8123 menuitem = gtk_ui_manager_get_widget
8124 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8127 menuitem = gtk_ui_manager_get_widget
8128 (compose->ui_manager, "/Menu/Options/Priority/High");
8130 case PRIORITY_NORMAL:
8131 menuitem = gtk_ui_manager_get_widget
8132 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8135 menuitem = gtk_ui_manager_get_widget
8136 (compose->ui_manager, "/Menu/Options/Priority/Low");
8138 case PRIORITY_LOWEST:
8139 menuitem = gtk_ui_manager_get_widget
8140 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8143 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8146 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8148 Compose *compose = (Compose *) data;
8150 gboolean can_sign = FALSE, can_encrypt = FALSE;
8152 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8154 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8157 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8158 g_free(compose->privacy_system);
8159 compose->privacy_system = NULL;
8160 if (systemid != NULL) {
8161 compose->privacy_system = g_strdup(systemid);
8163 can_sign = privacy_system_can_sign(systemid);
8164 can_encrypt = privacy_system_can_encrypt(systemid);
8167 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8169 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8170 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8173 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8175 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8176 GtkWidget *menuitem = NULL;
8177 GList *children, *amenu;
8178 gboolean can_sign = FALSE, can_encrypt = FALSE;
8179 gboolean found = FALSE;
8181 if (compose->privacy_system != NULL) {
8183 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8184 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8185 cm_return_if_fail(menuitem != NULL);
8187 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8190 while (amenu != NULL) {
8191 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8192 if (systemid != NULL) {
8193 if (strcmp(systemid, compose->privacy_system) == 0 &&
8194 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8195 menuitem = GTK_WIDGET(amenu->data);
8197 can_sign = privacy_system_can_sign(systemid);
8198 can_encrypt = privacy_system_can_encrypt(systemid);
8202 } else if (strlen(compose->privacy_system) == 0 &&
8203 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8204 menuitem = GTK_WIDGET(amenu->data);
8207 can_encrypt = FALSE;
8212 amenu = amenu->next;
8214 g_list_free(children);
8215 if (menuitem != NULL)
8216 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8218 if (warn && !found && strlen(compose->privacy_system)) {
8219 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8220 "will not be able to sign or encrypt this message."),
8221 compose->privacy_system);
8225 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8226 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8229 static void compose_set_out_encoding(Compose *compose)
8231 CharSet out_encoding;
8232 const gchar *branch = NULL;
8233 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8235 switch(out_encoding) {
8236 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8237 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8238 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8239 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8240 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8241 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8242 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8243 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8244 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8245 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8246 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8247 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8248 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8249 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8250 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8251 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8252 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8253 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8254 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8255 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8256 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8257 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8258 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8259 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8260 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8261 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8262 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8263 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8264 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8265 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8266 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8267 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8268 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8270 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8273 static void compose_set_template_menu(Compose *compose)
8275 GSList *tmpl_list, *cur;
8279 tmpl_list = template_get_config();
8281 menu = gtk_menu_new();
8283 gtk_menu_set_accel_group (GTK_MENU (menu),
8284 gtk_ui_manager_get_accel_group(compose->ui_manager));
8285 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8286 Template *tmpl = (Template *)cur->data;
8287 gchar *accel_path = NULL;
8288 item = gtk_menu_item_new_with_label(tmpl->name);
8289 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8290 g_signal_connect(G_OBJECT(item), "activate",
8291 G_CALLBACK(compose_template_activate_cb),
8293 g_object_set_data(G_OBJECT(item), "template", tmpl);
8294 gtk_widget_show(item);
8295 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8296 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8300 gtk_widget_show(menu);
8301 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8304 void compose_update_actions_menu(Compose *compose)
8306 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8309 static void compose_update_privacy_systems_menu(Compose *compose)
8311 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8312 GSList *systems, *cur;
8314 GtkWidget *system_none;
8316 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8317 GtkWidget *privacy_menu = gtk_menu_new();
8319 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8320 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8322 g_signal_connect(G_OBJECT(system_none), "activate",
8323 G_CALLBACK(compose_set_privacy_system_cb), compose);
8325 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8326 gtk_widget_show(system_none);
8328 systems = privacy_get_system_ids();
8329 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8330 gchar *systemid = cur->data;
8332 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8333 widget = gtk_radio_menu_item_new_with_label(group,
8334 privacy_system_get_name(systemid));
8335 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8336 g_strdup(systemid), g_free);
8337 g_signal_connect(G_OBJECT(widget), "activate",
8338 G_CALLBACK(compose_set_privacy_system_cb), compose);
8340 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8341 gtk_widget_show(widget);
8344 g_slist_free(systems);
8345 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8346 gtk_widget_show_all(privacy_menu);
8347 gtk_widget_show_all(privacy_menuitem);
8350 void compose_reflect_prefs_all(void)
8355 for (cur = compose_list; cur != NULL; cur = cur->next) {
8356 compose = (Compose *)cur->data;
8357 compose_set_template_menu(compose);
8361 void compose_reflect_prefs_pixmap_theme(void)
8366 for (cur = compose_list; cur != NULL; cur = cur->next) {
8367 compose = (Compose *)cur->data;
8368 toolbar_update(TOOLBAR_COMPOSE, compose);
8372 static const gchar *compose_quote_char_from_context(Compose *compose)
8374 const gchar *qmark = NULL;
8376 cm_return_val_if_fail(compose != NULL, NULL);
8378 switch (compose->mode) {
8379 /* use forward-specific quote char */
8380 case COMPOSE_FORWARD:
8381 case COMPOSE_FORWARD_AS_ATTACH:
8382 case COMPOSE_FORWARD_INLINE:
8383 if (compose->folder && compose->folder->prefs &&
8384 compose->folder->prefs->forward_with_format)
8385 qmark = compose->folder->prefs->forward_quotemark;
8386 else if (compose->account->forward_with_format)
8387 qmark = compose->account->forward_quotemark;
8389 qmark = prefs_common.fw_quotemark;
8392 /* use reply-specific quote char in all other modes */
8394 if (compose->folder && compose->folder->prefs &&
8395 compose->folder->prefs->reply_with_format)
8396 qmark = compose->folder->prefs->reply_quotemark;
8397 else if (compose->account->reply_with_format)
8398 qmark = compose->account->reply_quotemark;
8400 qmark = prefs_common.quotemark;
8404 if (qmark == NULL || *qmark == '\0')
8410 static void compose_template_apply(Compose *compose, Template *tmpl,
8414 GtkTextBuffer *buffer;
8418 gchar *parsed_str = NULL;
8419 gint cursor_pos = 0;
8420 const gchar *err_msg = _("The body of the template has an error at line %d.");
8423 /* process the body */
8425 text = GTK_TEXT_VIEW(compose->text);
8426 buffer = gtk_text_view_get_buffer(text);
8429 qmark = compose_quote_char_from_context(compose);
8431 if (compose->replyinfo != NULL) {
8434 gtk_text_buffer_set_text(buffer, "", -1);
8435 mark = gtk_text_buffer_get_insert(buffer);
8436 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8438 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8439 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8441 } else if (compose->fwdinfo != NULL) {
8444 gtk_text_buffer_set_text(buffer, "", -1);
8445 mark = gtk_text_buffer_get_insert(buffer);
8446 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8448 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8449 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8452 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8454 GtkTextIter start, end;
8457 gtk_text_buffer_get_start_iter(buffer, &start);
8458 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8459 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8461 /* clear the buffer now */
8463 gtk_text_buffer_set_text(buffer, "", -1);
8465 parsed_str = compose_quote_fmt(compose, dummyinfo,
8466 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8467 procmsg_msginfo_free( dummyinfo );
8473 gtk_text_buffer_set_text(buffer, "", -1);
8474 mark = gtk_text_buffer_get_insert(buffer);
8475 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8478 if (replace && parsed_str && compose->account->auto_sig)
8479 compose_insert_sig(compose, FALSE);
8481 if (replace && parsed_str) {
8482 gtk_text_buffer_get_start_iter(buffer, &iter);
8483 gtk_text_buffer_place_cursor(buffer, &iter);
8487 cursor_pos = quote_fmt_get_cursor_pos();
8488 compose->set_cursor_pos = cursor_pos;
8489 if (cursor_pos == -1)
8491 gtk_text_buffer_get_start_iter(buffer, &iter);
8492 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8493 gtk_text_buffer_place_cursor(buffer, &iter);
8496 /* process the other fields */
8498 compose_template_apply_fields(compose, tmpl);
8499 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8500 quote_fmt_reset_vartable();
8501 compose_changed_cb(NULL, compose);
8504 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8505 gtkaspell_highlight_all(compose->gtkaspell);
8509 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8511 MsgInfo* dummyinfo = NULL;
8512 MsgInfo *msginfo = NULL;
8515 if (compose->replyinfo != NULL)
8516 msginfo = compose->replyinfo;
8517 else if (compose->fwdinfo != NULL)
8518 msginfo = compose->fwdinfo;
8520 dummyinfo = compose_msginfo_new_from_compose(compose);
8521 msginfo = dummyinfo;
8524 if (tmpl->from && *tmpl->from != '\0') {
8526 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8527 compose->gtkaspell);
8529 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8531 quote_fmt_scan_string(tmpl->from);
8534 buf = quote_fmt_get_buffer();
8536 alertpanel_error(_("Template From format error."));
8538 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8542 if (tmpl->to && *tmpl->to != '\0') {
8544 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8545 compose->gtkaspell);
8547 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8549 quote_fmt_scan_string(tmpl->to);
8552 buf = quote_fmt_get_buffer();
8554 alertpanel_error(_("Template To format error."));
8556 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8560 if (tmpl->cc && *tmpl->cc != '\0') {
8562 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8563 compose->gtkaspell);
8565 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8567 quote_fmt_scan_string(tmpl->cc);
8570 buf = quote_fmt_get_buffer();
8572 alertpanel_error(_("Template Cc format error."));
8574 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8578 if (tmpl->bcc && *tmpl->bcc != '\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->bcc);
8588 buf = quote_fmt_get_buffer();
8590 alertpanel_error(_("Template Bcc format error."));
8592 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8596 /* process the subject */
8597 if (tmpl->subject && *tmpl->subject != '\0') {
8599 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8600 compose->gtkaspell);
8602 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8604 quote_fmt_scan_string(tmpl->subject);
8607 buf = quote_fmt_get_buffer();
8609 alertpanel_error(_("Template subject format error."));
8611 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8615 procmsg_msginfo_free( dummyinfo );
8618 static void compose_destroy(Compose *compose)
8620 GtkAllocation allocation;
8621 GtkTextBuffer *buffer;
8622 GtkClipboard *clipboard;
8624 compose_list = g_list_remove(compose_list, compose);
8626 if (compose->updating) {
8627 debug_print("danger, not destroying anything now\n");
8628 compose->deferred_destroy = TRUE;
8631 /* NOTE: address_completion_end() does nothing with the window
8632 * however this may change. */
8633 address_completion_end(compose->window);
8635 slist_free_strings_full(compose->to_list);
8636 slist_free_strings_full(compose->newsgroup_list);
8637 slist_free_strings_full(compose->header_list);
8639 slist_free_strings_full(extra_headers);
8640 extra_headers = NULL;
8642 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8644 g_hash_table_destroy(compose->email_hashtable);
8646 procmsg_msginfo_free(compose->targetinfo);
8647 procmsg_msginfo_free(compose->replyinfo);
8648 procmsg_msginfo_free(compose->fwdinfo);
8650 g_free(compose->replyto);
8651 g_free(compose->cc);
8652 g_free(compose->bcc);
8653 g_free(compose->newsgroups);
8654 g_free(compose->followup_to);
8656 g_free(compose->ml_post);
8658 g_free(compose->inreplyto);
8659 g_free(compose->references);
8660 g_free(compose->msgid);
8661 g_free(compose->boundary);
8663 g_free(compose->redirect_filename);
8664 if (compose->undostruct)
8665 undo_destroy(compose->undostruct);
8667 g_free(compose->sig_str);
8669 g_free(compose->exteditor_file);
8671 g_free(compose->orig_charset);
8673 g_free(compose->privacy_system);
8675 #ifndef USE_NEW_ADDRBOOK
8676 if (addressbook_get_target_compose() == compose)
8677 addressbook_set_target_compose(NULL);
8680 if (compose->gtkaspell) {
8681 gtkaspell_delete(compose->gtkaspell);
8682 compose->gtkaspell = NULL;
8686 if (!compose->batch) {
8687 gtk_widget_get_allocation(compose->window, &allocation);
8688 prefs_common.compose_width = allocation.width;
8689 prefs_common.compose_height = allocation.height;
8692 if (!gtk_widget_get_parent(compose->paned))
8693 gtk_widget_destroy(compose->paned);
8694 gtk_widget_destroy(compose->popupmenu);
8696 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8697 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8698 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8700 gtk_widget_destroy(compose->window);
8701 toolbar_destroy(compose->toolbar);
8702 g_free(compose->toolbar);
8703 cm_mutex_free(compose->mutex);
8707 static void compose_attach_info_free(AttachInfo *ainfo)
8709 g_free(ainfo->file);
8710 g_free(ainfo->content_type);
8711 g_free(ainfo->name);
8712 g_free(ainfo->charset);
8716 static void compose_attach_update_label(Compose *compose)
8721 GtkTreeModel *model;
8726 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8727 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8728 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8732 while(gtk_tree_model_iter_next(model, &iter))
8735 text = g_strdup_printf("(%d)", i);
8736 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8740 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8742 Compose *compose = (Compose *)data;
8743 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8744 GtkTreeSelection *selection;
8746 GtkTreeModel *model;
8748 selection = gtk_tree_view_get_selection(tree_view);
8749 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8754 for (cur = sel; cur != NULL; cur = cur->next) {
8755 GtkTreePath *path = cur->data;
8756 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8759 gtk_tree_path_free(path);
8762 for (cur = sel; cur != NULL; cur = cur->next) {
8763 GtkTreeRowReference *ref = cur->data;
8764 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8767 if (gtk_tree_model_get_iter(model, &iter, path))
8768 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8770 gtk_tree_path_free(path);
8771 gtk_tree_row_reference_free(ref);
8775 compose_attach_update_label(compose);
8778 static struct _AttachProperty
8781 GtkWidget *mimetype_entry;
8782 GtkWidget *encoding_optmenu;
8783 GtkWidget *path_entry;
8784 GtkWidget *filename_entry;
8786 GtkWidget *cancel_btn;
8789 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8791 gtk_tree_path_free((GtkTreePath *)ptr);
8794 static void compose_attach_property(GtkAction *action, gpointer data)
8796 Compose *compose = (Compose *)data;
8797 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8799 GtkComboBox *optmenu;
8800 GtkTreeSelection *selection;
8802 GtkTreeModel *model;
8805 static gboolean cancelled;
8807 /* only if one selected */
8808 selection = gtk_tree_view_get_selection(tree_view);
8809 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8812 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8816 path = (GtkTreePath *) sel->data;
8817 gtk_tree_model_get_iter(model, &iter, path);
8818 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8821 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8827 if (!attach_prop.window)
8828 compose_attach_property_create(&cancelled);
8829 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8830 gtk_widget_grab_focus(attach_prop.ok_btn);
8831 gtk_widget_show(attach_prop.window);
8832 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8833 GTK_WINDOW(compose->window));
8835 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8836 if (ainfo->encoding == ENC_UNKNOWN)
8837 combobox_select_by_data(optmenu, ENC_BASE64);
8839 combobox_select_by_data(optmenu, ainfo->encoding);
8841 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8842 ainfo->content_type ? ainfo->content_type : "");
8843 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8844 ainfo->file ? ainfo->file : "");
8845 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8846 ainfo->name ? ainfo->name : "");
8849 const gchar *entry_text;
8851 gchar *cnttype = NULL;
8858 gtk_widget_hide(attach_prop.window);
8859 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8864 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8865 if (*entry_text != '\0') {
8868 text = g_strstrip(g_strdup(entry_text));
8869 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8870 cnttype = g_strdup(text);
8873 alertpanel_error(_("Invalid MIME type."));
8879 ainfo->encoding = combobox_get_active_data(optmenu);
8881 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8882 if (*entry_text != '\0') {
8883 if (is_file_exist(entry_text) &&
8884 (size = get_file_size(entry_text)) > 0)
8885 file = g_strdup(entry_text);
8888 (_("File doesn't exist or is empty."));
8894 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8895 if (*entry_text != '\0') {
8896 g_free(ainfo->name);
8897 ainfo->name = g_strdup(entry_text);
8901 g_free(ainfo->content_type);
8902 ainfo->content_type = cnttype;
8905 g_free(ainfo->file);
8909 ainfo->size = (goffset)size;
8911 /* update tree store */
8912 text = to_human_readable(ainfo->size);
8913 gtk_tree_model_get_iter(model, &iter, path);
8914 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8915 COL_MIMETYPE, ainfo->content_type,
8917 COL_NAME, ainfo->name,
8918 COL_CHARSET, ainfo->charset,
8924 gtk_tree_path_free(path);
8927 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8929 label = gtk_label_new(str); \
8930 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8931 GTK_FILL, 0, 0, 0); \
8932 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8934 entry = gtk_entry_new(); \
8935 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8936 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8939 static void compose_attach_property_create(gboolean *cancelled)
8945 GtkWidget *mimetype_entry;
8948 GtkListStore *optmenu_menu;
8949 GtkWidget *path_entry;
8950 GtkWidget *filename_entry;
8953 GtkWidget *cancel_btn;
8954 GList *mime_type_list, *strlist;
8957 debug_print("Creating attach_property window...\n");
8959 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8960 gtk_widget_set_size_request(window, 480, -1);
8961 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8962 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8963 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8964 g_signal_connect(G_OBJECT(window), "delete_event",
8965 G_CALLBACK(attach_property_delete_event),
8967 g_signal_connect(G_OBJECT(window), "key_press_event",
8968 G_CALLBACK(attach_property_key_pressed),
8971 vbox = gtk_vbox_new(FALSE, 8);
8972 gtk_container_add(GTK_CONTAINER(window), vbox);
8974 table = gtk_table_new(4, 2, FALSE);
8975 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8976 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8977 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8979 label = gtk_label_new(_("MIME type"));
8980 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8982 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8983 #if !GTK_CHECK_VERSION(2, 24, 0)
8984 mimetype_entry = gtk_combo_box_entry_new_text();
8986 mimetype_entry = gtk_combo_box_text_new_with_entry();
8988 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8989 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8991 /* stuff with list */
8992 mime_type_list = procmime_get_mime_type_list();
8994 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8995 MimeType *type = (MimeType *) mime_type_list->data;
8998 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9000 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9003 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9004 (GCompareFunc)strcmp2);
9007 for (mime_type_list = strlist; mime_type_list != NULL;
9008 mime_type_list = mime_type_list->next) {
9009 #if !GTK_CHECK_VERSION(2, 24, 0)
9010 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9012 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9014 g_free(mime_type_list->data);
9016 g_list_free(strlist);
9017 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9018 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9020 label = gtk_label_new(_("Encoding"));
9021 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9023 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9025 hbox = gtk_hbox_new(FALSE, 0);
9026 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9027 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9029 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9030 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9032 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9033 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9034 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9035 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9036 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9038 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9040 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9041 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9043 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9044 &ok_btn, GTK_STOCK_OK,
9046 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9047 gtk_widget_grab_default(ok_btn);
9049 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9050 G_CALLBACK(attach_property_ok),
9052 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9053 G_CALLBACK(attach_property_cancel),
9056 gtk_widget_show_all(vbox);
9058 attach_prop.window = window;
9059 attach_prop.mimetype_entry = mimetype_entry;
9060 attach_prop.encoding_optmenu = optmenu;
9061 attach_prop.path_entry = path_entry;
9062 attach_prop.filename_entry = filename_entry;
9063 attach_prop.ok_btn = ok_btn;
9064 attach_prop.cancel_btn = cancel_btn;
9067 #undef SET_LABEL_AND_ENTRY
9069 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9075 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9081 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9082 gboolean *cancelled)
9090 static gboolean attach_property_key_pressed(GtkWidget *widget,
9092 gboolean *cancelled)
9094 if (event && event->keyval == GDK_KEY_Escape) {
9098 if (event && event->keyval == GDK_KEY_Return) {
9106 static void compose_exec_ext_editor(Compose *compose)
9113 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9114 G_DIR_SEPARATOR, compose);
9116 if (pipe(pipe_fds) < 0) {
9122 if ((pid = fork()) < 0) {
9129 /* close the write side of the pipe */
9132 compose->exteditor_file = g_strdup(tmp);
9133 compose->exteditor_pid = pid;
9135 compose_set_ext_editor_sensitive(compose, FALSE);
9138 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9140 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9142 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9146 } else { /* process-monitoring process */
9152 /* close the read side of the pipe */
9155 if (compose_write_body_to_file(compose, tmp) < 0) {
9156 fd_write_all(pipe_fds[1], "2\n", 2);
9160 pid_ed = compose_exec_ext_editor_real(tmp);
9162 fd_write_all(pipe_fds[1], "1\n", 2);
9166 /* wait until editor is terminated */
9167 waitpid(pid_ed, NULL, 0);
9169 fd_write_all(pipe_fds[1], "0\n", 2);
9176 #endif /* G_OS_UNIX */
9180 static gint compose_exec_ext_editor_real(const gchar *file)
9187 cm_return_val_if_fail(file != NULL, -1);
9189 if ((pid = fork()) < 0) {
9194 if (pid != 0) return pid;
9196 /* grandchild process */
9198 if (setpgid(0, getppid()))
9201 if (prefs_common_get_ext_editor_cmd() &&
9202 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9203 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9204 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9206 if (prefs_common_get_ext_editor_cmd())
9207 g_warning("External editor command-line is invalid: '%s'\n",
9208 prefs_common_get_ext_editor_cmd());
9209 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9212 cmdline = strsplit_with_quote(buf, " ", 1024);
9213 execvp(cmdline[0], cmdline);
9216 g_strfreev(cmdline);
9221 static gboolean compose_ext_editor_kill(Compose *compose)
9223 pid_t pgid = compose->exteditor_pid * -1;
9226 ret = kill(pgid, 0);
9228 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9232 msg = g_strdup_printf
9233 (_("The external editor is still working.\n"
9234 "Force terminating the process?\n"
9235 "process group id: %d"), -pgid);
9236 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9237 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9241 if (val == G_ALERTALTERNATE) {
9242 g_source_remove(compose->exteditor_tag);
9243 g_io_channel_shutdown(compose->exteditor_ch,
9245 g_io_channel_unref(compose->exteditor_ch);
9247 if (kill(pgid, SIGTERM) < 0) perror("kill");
9248 waitpid(compose->exteditor_pid, NULL, 0);
9250 g_warning("Terminated process group id: %d", -pgid);
9251 g_warning("Temporary file: %s",
9252 compose->exteditor_file);
9254 compose_set_ext_editor_sensitive(compose, TRUE);
9256 g_free(compose->exteditor_file);
9257 compose->exteditor_file = NULL;
9258 compose->exteditor_pid = -1;
9259 compose->exteditor_ch = NULL;
9260 compose->exteditor_tag = -1;
9268 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9272 Compose *compose = (Compose *)data;
9275 debug_print("Compose: input from monitoring process\n");
9277 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9279 g_io_channel_shutdown(source, FALSE, NULL);
9280 g_io_channel_unref(source);
9282 waitpid(compose->exteditor_pid, NULL, 0);
9284 if (buf[0] == '0') { /* success */
9285 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9286 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9288 gtk_text_buffer_set_text(buffer, "", -1);
9289 compose_insert_file(compose, compose->exteditor_file);
9290 compose_changed_cb(NULL, compose);
9291 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9293 if (claws_unlink(compose->exteditor_file) < 0)
9294 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9295 } else if (buf[0] == '1') { /* failed */
9296 g_warning("Couldn't exec external editor\n");
9297 if (claws_unlink(compose->exteditor_file) < 0)
9298 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9299 } else if (buf[0] == '2') {
9300 g_warning("Couldn't write to file\n");
9301 } else if (buf[0] == '3') {
9302 g_warning("Pipe read failed\n");
9305 compose_set_ext_editor_sensitive(compose, TRUE);
9307 g_free(compose->exteditor_file);
9308 compose->exteditor_file = NULL;
9309 compose->exteditor_pid = -1;
9310 compose->exteditor_ch = NULL;
9311 compose->exteditor_tag = -1;
9316 static void compose_set_ext_editor_sensitive(Compose *compose,
9319 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9320 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9321 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9322 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9323 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9325 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9326 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9328 gtk_widget_set_sensitive(compose->text, sensitive);
9329 if (compose->toolbar->send_btn)
9330 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9331 if (compose->toolbar->sendl_btn)
9332 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9333 if (compose->toolbar->draft_btn)
9334 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9335 if (compose->toolbar->insert_btn)
9336 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9337 if (compose->toolbar->sig_btn)
9338 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9339 if (compose->toolbar->exteditor_btn)
9340 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9341 if (compose->toolbar->linewrap_current_btn)
9342 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9343 if (compose->toolbar->linewrap_all_btn)
9344 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9346 #endif /* G_OS_UNIX */
9349 * compose_undo_state_changed:
9351 * Change the sensivity of the menuentries undo and redo
9353 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9354 gint redo_state, gpointer data)
9356 Compose *compose = (Compose *)data;
9358 switch (undo_state) {
9359 case UNDO_STATE_TRUE:
9360 if (!undostruct->undo_state) {
9361 undostruct->undo_state = TRUE;
9362 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9365 case UNDO_STATE_FALSE:
9366 if (undostruct->undo_state) {
9367 undostruct->undo_state = FALSE;
9368 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9371 case UNDO_STATE_UNCHANGED:
9373 case UNDO_STATE_REFRESH:
9374 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9377 g_warning("Undo state not recognized");
9381 switch (redo_state) {
9382 case UNDO_STATE_TRUE:
9383 if (!undostruct->redo_state) {
9384 undostruct->redo_state = TRUE;
9385 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9388 case UNDO_STATE_FALSE:
9389 if (undostruct->redo_state) {
9390 undostruct->redo_state = FALSE;
9391 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9394 case UNDO_STATE_UNCHANGED:
9396 case UNDO_STATE_REFRESH:
9397 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9400 g_warning("Redo state not recognized");
9405 /* callback functions */
9407 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9408 GtkAllocation *allocation,
9411 prefs_common.compose_notebook_height = allocation->height;
9414 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9415 * includes "non-client" (windows-izm) in calculation, so this calculation
9416 * may not be accurate.
9418 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9419 GtkAllocation *allocation,
9420 GtkSHRuler *shruler)
9422 if (prefs_common.show_ruler) {
9423 gint char_width = 0, char_height = 0;
9424 gint line_width_in_chars;
9426 gtkut_get_font_size(GTK_WIDGET(widget),
9427 &char_width, &char_height);
9428 line_width_in_chars =
9429 (allocation->width - allocation->x) / char_width;
9431 /* got the maximum */
9432 gtk_shruler_set_range(GTK_SHRULER(shruler),
9433 0.0, line_width_in_chars, 0);
9442 ComposePrefType type;
9443 gboolean entry_marked;
9446 static void account_activated(GtkComboBox *optmenu, gpointer data)
9448 Compose *compose = (Compose *)data;
9451 gchar *folderidentifier;
9452 gint account_id = 0;
9455 GSList *list, *saved_list = NULL;
9456 HeaderEntryState *state;
9457 GtkRcStyle *style = NULL;
9458 #if !GTK_CHECK_VERSION(3, 0, 0)
9459 static GdkColor yellow;
9460 static gboolean color_set = FALSE;
9462 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9465 /* Get ID of active account in the combo box */
9466 menu = gtk_combo_box_get_model(optmenu);
9467 gtk_combo_box_get_active_iter(optmenu, &iter);
9468 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9470 ac = account_find_from_id(account_id);
9471 cm_return_if_fail(ac != NULL);
9473 if (ac != compose->account) {
9474 compose_select_account(compose, ac, FALSE);
9476 for (list = compose->header_list; list; list = list->next) {
9477 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9479 if (hentry->type == PREF_ACCOUNT || !list->next) {
9480 compose_destroy_headerentry(compose, hentry);
9484 state = g_malloc0(sizeof(HeaderEntryState));
9485 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9486 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9487 state->entry = gtk_editable_get_chars(
9488 GTK_EDITABLE(hentry->entry), 0, -1);
9489 state->type = hentry->type;
9491 #if !GTK_CHECK_VERSION(3, 0, 0)
9493 gdk_color_parse("#f5f6be", &yellow);
9494 color_set = gdk_colormap_alloc_color(
9495 gdk_colormap_get_system(),
9496 &yellow, FALSE, TRUE);
9500 style = gtk_widget_get_modifier_style(hentry->entry);
9501 state->entry_marked = gdk_color_equal(&yellow,
9502 &style->base[GTK_STATE_NORMAL]);
9504 saved_list = g_slist_append(saved_list, state);
9505 compose_destroy_headerentry(compose, hentry);
9508 compose->header_last = NULL;
9509 g_slist_free(compose->header_list);
9510 compose->header_list = NULL;
9511 compose->header_nextrow = 1;
9512 compose_create_header_entry(compose);
9514 if (ac->set_autocc && ac->auto_cc)
9515 compose_entry_append(compose, ac->auto_cc,
9516 COMPOSE_CC, PREF_ACCOUNT);
9518 if (ac->set_autobcc && ac->auto_bcc)
9519 compose_entry_append(compose, ac->auto_bcc,
9520 COMPOSE_BCC, PREF_ACCOUNT);
9522 if (ac->set_autoreplyto && ac->auto_replyto)
9523 compose_entry_append(compose, ac->auto_replyto,
9524 COMPOSE_REPLYTO, PREF_ACCOUNT);
9526 for (list = saved_list; list; list = list->next) {
9527 state = (HeaderEntryState *) list->data;
9529 compose_add_header_entry(compose, state->header,
9530 state->entry, state->type);
9531 if (state->entry_marked)
9532 compose_entry_mark_default_to(compose, state->entry);
9534 g_free(state->header);
9535 g_free(state->entry);
9538 g_slist_free(saved_list);
9540 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9541 (ac->protocol == A_NNTP) ?
9542 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9545 /* Set message save folder */
9546 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9547 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9549 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9550 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9552 compose_set_save_to(compose, NULL);
9553 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9554 folderidentifier = folder_item_get_identifier(account_get_special_folder
9555 (compose->account, F_OUTBOX));
9556 compose_set_save_to(compose, folderidentifier);
9557 g_free(folderidentifier);
9561 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9562 GtkTreeViewColumn *column, Compose *compose)
9564 compose_attach_property(NULL, compose);
9567 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9570 Compose *compose = (Compose *)data;
9571 GtkTreeSelection *attach_selection;
9572 gint attach_nr_selected;
9574 if (!event) return FALSE;
9576 if (event->button == 3) {
9577 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9578 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9580 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9581 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9583 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9584 NULL, NULL, event->button, event->time);
9591 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9594 Compose *compose = (Compose *)data;
9596 if (!event) return FALSE;
9598 switch (event->keyval) {
9599 case GDK_KEY_Delete:
9600 compose_attach_remove_selected(NULL, compose);
9606 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9608 toolbar_comp_set_sensitive(compose, allow);
9609 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9610 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9612 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9614 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9615 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9616 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9618 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9622 static void compose_send_cb(GtkAction *action, gpointer data)
9624 Compose *compose = (Compose *)data;
9626 if (prefs_common.work_offline &&
9627 !inc_offline_should_override(TRUE,
9628 _("Claws Mail needs network access in order "
9629 "to send this email.")))
9632 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9633 g_source_remove(compose->draft_timeout_tag);
9634 compose->draft_timeout_tag = -1;
9637 compose_send(compose);
9640 static void compose_send_later_cb(GtkAction *action, gpointer data)
9642 Compose *compose = (Compose *)data;
9646 compose_allow_user_actions(compose, FALSE);
9647 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9648 compose_allow_user_actions(compose, TRUE);
9652 compose_close(compose);
9653 } else if (val == -1) {
9654 alertpanel_error(_("Could not queue message."));
9655 } else if (val == -2) {
9656 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9657 } else if (val == -3) {
9658 if (privacy_peek_error())
9659 alertpanel_error(_("Could not queue message for sending:\n\n"
9660 "Signature failed: %s"), privacy_get_error());
9661 } else if (val == -4) {
9662 alertpanel_error(_("Could not queue message for sending:\n\n"
9663 "Charset conversion failed."));
9664 } else if (val == -5) {
9665 alertpanel_error(_("Could not queue message for sending:\n\n"
9666 "Couldn't get recipient encryption key."));
9667 } else if (val == -6) {
9670 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9673 #define DRAFTED_AT_EXIT "drafted_at_exit"
9674 static void compose_register_draft(MsgInfo *info)
9676 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9677 DRAFTED_AT_EXIT, NULL);
9678 FILE *fp = g_fopen(filepath, "ab");
9681 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9689 gboolean compose_draft (gpointer data, guint action)
9691 Compose *compose = (Compose *)data;
9696 MsgFlags flag = {0, 0};
9697 static gboolean lock = FALSE;
9698 MsgInfo *newmsginfo;
9700 gboolean target_locked = FALSE;
9701 gboolean err = FALSE;
9703 if (lock) return FALSE;
9705 if (compose->sending)
9708 draft = account_get_special_folder(compose->account, F_DRAFT);
9709 cm_return_val_if_fail(draft != NULL, FALSE);
9711 if (!g_mutex_trylock(compose->mutex)) {
9712 /* we don't want to lock the mutex once it's available,
9713 * because as the only other part of compose.c locking
9714 * it is compose_close - which means once unlocked,
9715 * the compose struct will be freed */
9716 debug_print("couldn't lock mutex, probably sending\n");
9722 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9723 G_DIR_SEPARATOR, compose);
9724 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9725 FILE_OP_ERROR(tmp, "fopen");
9729 /* chmod for security */
9730 if (change_file_mode_rw(fp, tmp) < 0) {
9731 FILE_OP_ERROR(tmp, "chmod");
9732 g_warning("can't change file mode\n");
9735 /* Save draft infos */
9736 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9737 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9739 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9740 gchar *savefolderid;
9742 savefolderid = compose_get_save_to(compose);
9743 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9744 g_free(savefolderid);
9746 if (compose->return_receipt) {
9747 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9749 if (compose->privacy_system) {
9750 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9751 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9752 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9755 /* Message-ID of message replying to */
9756 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9759 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9760 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9763 /* Message-ID of message forwarding to */
9764 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9767 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9768 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9772 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9773 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9775 sheaders = compose_get_manual_headers_info(compose);
9776 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9779 /* end of headers */
9780 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9787 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9791 if (fclose(fp) == EOF) {
9795 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9796 if (compose->targetinfo) {
9797 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9799 flag.perm_flags |= MSG_LOCKED;
9801 flag.tmp_flags = MSG_DRAFT;
9803 folder_item_scan(draft);
9804 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9805 MsgInfo *tmpinfo = NULL;
9806 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9807 if (compose->msgid) {
9808 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9811 msgnum = tmpinfo->msgnum;
9812 procmsg_msginfo_free(tmpinfo);
9813 debug_print("got draft msgnum %d from scanning\n", msgnum);
9815 debug_print("didn't get draft msgnum after scanning\n");
9818 debug_print("got draft msgnum %d from adding\n", msgnum);
9824 if (action != COMPOSE_AUTO_SAVE) {
9825 if (action != COMPOSE_DRAFT_FOR_EXIT)
9826 alertpanel_error(_("Could not save draft."));
9829 gtkut_window_popup(compose->window);
9830 val = alertpanel_full(_("Could not save draft"),
9831 _("Could not save draft.\n"
9832 "Do you want to cancel exit or discard this email?"),
9833 _("_Cancel exit"), _("_Discard email"), NULL,
9834 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9835 if (val == G_ALERTALTERNATE) {
9837 g_mutex_unlock(compose->mutex); /* must be done before closing */
9838 compose_close(compose);
9842 g_mutex_unlock(compose->mutex); /* must be done before closing */
9851 if (compose->mode == COMPOSE_REEDIT) {
9852 compose_remove_reedit_target(compose, TRUE);
9855 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9858 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9860 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9862 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9863 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9864 procmsg_msginfo_set_flags(newmsginfo, 0,
9865 MSG_HAS_ATTACHMENT);
9867 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9868 compose_register_draft(newmsginfo);
9870 procmsg_msginfo_free(newmsginfo);
9873 folder_item_scan(draft);
9875 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9877 g_mutex_unlock(compose->mutex); /* must be done before closing */
9878 compose_close(compose);
9884 path = folder_item_fetch_msg(draft, msgnum);
9886 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9889 if (g_stat(path, &s) < 0) {
9890 FILE_OP_ERROR(path, "stat");
9896 procmsg_msginfo_free(compose->targetinfo);
9897 compose->targetinfo = procmsg_msginfo_new();
9898 compose->targetinfo->msgnum = msgnum;
9899 compose->targetinfo->size = (goffset)s.st_size;
9900 compose->targetinfo->mtime = s.st_mtime;
9901 compose->targetinfo->folder = draft;
9903 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9904 compose->mode = COMPOSE_REEDIT;
9906 if (action == COMPOSE_AUTO_SAVE) {
9907 compose->autosaved_draft = compose->targetinfo;
9909 compose->modified = FALSE;
9910 compose_set_title(compose);
9914 g_mutex_unlock(compose->mutex);
9918 void compose_clear_exit_drafts(void)
9920 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9921 DRAFTED_AT_EXIT, NULL);
9922 if (is_file_exist(filepath))
9923 claws_unlink(filepath);
9928 void compose_reopen_exit_drafts(void)
9930 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9931 DRAFTED_AT_EXIT, NULL);
9932 FILE *fp = g_fopen(filepath, "rb");
9936 while (fgets(buf, sizeof(buf), fp)) {
9937 gchar **parts = g_strsplit(buf, "\t", 2);
9938 const gchar *folder = parts[0];
9939 int msgnum = parts[1] ? atoi(parts[1]):-1;
9941 if (folder && *folder && msgnum > -1) {
9942 FolderItem *item = folder_find_item_from_identifier(folder);
9943 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9945 compose_reedit(info, FALSE);
9952 compose_clear_exit_drafts();
9955 static void compose_save_cb(GtkAction *action, gpointer data)
9957 Compose *compose = (Compose *)data;
9958 compose_draft(compose, COMPOSE_KEEP_EDITING);
9959 compose->rmode = COMPOSE_REEDIT;
9962 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9964 if (compose && file_list) {
9967 for ( tmp = file_list; tmp; tmp = tmp->next) {
9968 gchar *file = (gchar *) tmp->data;
9969 gchar *utf8_filename = conv_filename_to_utf8(file);
9970 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9971 compose_changed_cb(NULL, compose);
9976 g_free(utf8_filename);
9981 static void compose_attach_cb(GtkAction *action, gpointer data)
9983 Compose *compose = (Compose *)data;
9986 if (compose->redirect_filename != NULL)
9989 /* Set focus_window properly, in case we were called via popup menu,
9990 * which unsets it (via focus_out_event callback on compose window). */
9991 manage_window_focus_in(compose->window, NULL, NULL);
9993 file_list = filesel_select_multiple_files_open(_("Select file"));
9996 compose_attach_from_list(compose, file_list, TRUE);
9997 g_list_free(file_list);
10001 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10003 Compose *compose = (Compose *)data;
10005 gint files_inserted = 0;
10007 file_list = filesel_select_multiple_files_open(_("Select file"));
10012 for ( tmp = file_list; tmp; tmp = tmp->next) {
10013 gchar *file = (gchar *) tmp->data;
10014 gchar *filedup = g_strdup(file);
10015 gchar *shortfile = g_path_get_basename(filedup);
10016 ComposeInsertResult res;
10017 /* insert the file if the file is short or if the user confirmed that
10018 he/she wants to insert the large file */
10019 res = compose_insert_file(compose, file);
10020 if (res == COMPOSE_INSERT_READ_ERROR) {
10021 alertpanel_error(_("File '%s' could not be read."), shortfile);
10022 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10023 alertpanel_error(_("File '%s' contained invalid characters\n"
10024 "for the current encoding, insertion may be incorrect."),
10026 } else if (res == COMPOSE_INSERT_SUCCESS)
10033 g_list_free(file_list);
10037 if (files_inserted > 0 && compose->gtkaspell &&
10038 compose->gtkaspell->check_while_typing)
10039 gtkaspell_highlight_all(compose->gtkaspell);
10043 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10045 Compose *compose = (Compose *)data;
10047 compose_insert_sig(compose, FALSE);
10050 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10052 Compose *compose = (Compose *)data;
10054 compose_insert_sig(compose, TRUE);
10057 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10061 Compose *compose = (Compose *)data;
10063 gtkut_widget_get_uposition(widget, &x, &y);
10064 if (!compose->batch) {
10065 prefs_common.compose_x = x;
10066 prefs_common.compose_y = y;
10068 if (compose->sending || compose->updating)
10070 compose_close_cb(NULL, compose);
10074 void compose_close_toolbar(Compose *compose)
10076 compose_close_cb(NULL, compose);
10079 static gboolean compose_can_autosave(Compose *compose)
10081 if (compose->privacy_system && compose->use_encryption)
10082 return prefs_common.autosave && prefs_common.autosave_encrypted;
10084 return prefs_common.autosave;
10087 static void compose_close_cb(GtkAction *action, gpointer data)
10089 Compose *compose = (Compose *)data;
10093 if (compose->exteditor_tag != -1) {
10094 if (!compose_ext_editor_kill(compose))
10099 if (compose->modified) {
10100 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10101 if (!g_mutex_trylock(compose->mutex)) {
10102 /* we don't want to lock the mutex once it's available,
10103 * because as the only other part of compose.c locking
10104 * it is compose_close - which means once unlocked,
10105 * the compose struct will be freed */
10106 debug_print("couldn't lock mutex, probably sending\n");
10110 val = alertpanel(_("Discard message"),
10111 _("This message has been modified. Discard it?"),
10112 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10114 val = alertpanel(_("Save changes"),
10115 _("This message has been modified. Save the latest changes?"),
10116 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10118 g_mutex_unlock(compose->mutex);
10120 case G_ALERTDEFAULT:
10121 if (compose_can_autosave(compose) && !reedit)
10122 compose_remove_draft(compose);
10124 case G_ALERTALTERNATE:
10125 compose_draft(data, COMPOSE_QUIT_EDITING);
10132 compose_close(compose);
10135 static void compose_print_cb(GtkAction *action, gpointer data)
10137 Compose *compose = (Compose *) data;
10139 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10140 if (compose->targetinfo)
10141 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10144 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10146 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10147 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10148 Compose *compose = (Compose *) data;
10151 compose->out_encoding = (CharSet)value;
10154 static void compose_address_cb(GtkAction *action, gpointer data)
10156 Compose *compose = (Compose *)data;
10158 #ifndef USE_NEW_ADDRBOOK
10159 addressbook_open(compose);
10161 GError* error = NULL;
10162 addressbook_connect_signals(compose);
10163 addressbook_dbus_open(TRUE, &error);
10165 g_warning("%s", error->message);
10166 g_error_free(error);
10171 static void about_show_cb(GtkAction *action, gpointer data)
10176 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10178 Compose *compose = (Compose *)data;
10183 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10184 cm_return_if_fail(tmpl != NULL);
10186 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10188 val = alertpanel(_("Apply template"), msg,
10189 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10192 if (val == G_ALERTDEFAULT)
10193 compose_template_apply(compose, tmpl, TRUE);
10194 else if (val == G_ALERTALTERNATE)
10195 compose_template_apply(compose, tmpl, FALSE);
10198 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10200 Compose *compose = (Compose *)data;
10202 compose_exec_ext_editor(compose);
10205 static void compose_undo_cb(GtkAction *action, gpointer data)
10207 Compose *compose = (Compose *)data;
10208 gboolean prev_autowrap = compose->autowrap;
10210 compose->autowrap = FALSE;
10211 undo_undo(compose->undostruct);
10212 compose->autowrap = prev_autowrap;
10215 static void compose_redo_cb(GtkAction *action, gpointer data)
10217 Compose *compose = (Compose *)data;
10218 gboolean prev_autowrap = compose->autowrap;
10220 compose->autowrap = FALSE;
10221 undo_redo(compose->undostruct);
10222 compose->autowrap = prev_autowrap;
10225 static void entry_cut_clipboard(GtkWidget *entry)
10227 if (GTK_IS_EDITABLE(entry))
10228 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10229 else if (GTK_IS_TEXT_VIEW(entry))
10230 gtk_text_buffer_cut_clipboard(
10231 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10232 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10236 static void entry_copy_clipboard(GtkWidget *entry)
10238 if (GTK_IS_EDITABLE(entry))
10239 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10240 else if (GTK_IS_TEXT_VIEW(entry))
10241 gtk_text_buffer_copy_clipboard(
10242 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10243 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10246 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10247 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10249 if (GTK_IS_TEXT_VIEW(entry)) {
10250 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10251 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10252 GtkTextIter start_iter, end_iter;
10254 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10256 if (contents == NULL)
10259 /* we shouldn't delete the selection when middle-click-pasting, or we
10260 * can't mid-click-paste our own selection */
10261 if (clip != GDK_SELECTION_PRIMARY) {
10262 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10263 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10266 if (insert_place == NULL) {
10267 /* if insert_place isn't specified, insert at the cursor.
10268 * used for Ctrl-V pasting */
10269 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10270 start = gtk_text_iter_get_offset(&start_iter);
10271 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10273 /* if insert_place is specified, paste here.
10274 * used for mid-click-pasting */
10275 start = gtk_text_iter_get_offset(insert_place);
10276 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10277 if (prefs_common.primary_paste_unselects)
10278 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10282 /* paste unwrapped: mark the paste so it's not wrapped later */
10283 end = start + strlen(contents);
10284 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10285 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10286 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10287 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10288 /* rewrap paragraph now (after a mid-click-paste) */
10289 mark_start = gtk_text_buffer_get_insert(buffer);
10290 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10291 gtk_text_iter_backward_char(&start_iter);
10292 compose_beautify_paragraph(compose, &start_iter, TRUE);
10294 } else if (GTK_IS_EDITABLE(entry))
10295 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10297 compose->modified = TRUE;
10300 static void entry_allsel(GtkWidget *entry)
10302 if (GTK_IS_EDITABLE(entry))
10303 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10304 else if (GTK_IS_TEXT_VIEW(entry)) {
10305 GtkTextIter startiter, enditer;
10306 GtkTextBuffer *textbuf;
10308 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10309 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10310 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10312 gtk_text_buffer_move_mark_by_name(textbuf,
10313 "selection_bound", &startiter);
10314 gtk_text_buffer_move_mark_by_name(textbuf,
10315 "insert", &enditer);
10319 static void compose_cut_cb(GtkAction *action, gpointer data)
10321 Compose *compose = (Compose *)data;
10322 if (compose->focused_editable
10323 #ifndef GENERIC_UMPC
10324 && gtk_widget_has_focus(compose->focused_editable)
10327 entry_cut_clipboard(compose->focused_editable);
10330 static void compose_copy_cb(GtkAction *action, gpointer data)
10332 Compose *compose = (Compose *)data;
10333 if (compose->focused_editable
10334 #ifndef GENERIC_UMPC
10335 && gtk_widget_has_focus(compose->focused_editable)
10338 entry_copy_clipboard(compose->focused_editable);
10341 static void compose_paste_cb(GtkAction *action, gpointer data)
10343 Compose *compose = (Compose *)data;
10344 gint prev_autowrap;
10345 GtkTextBuffer *buffer;
10347 if (compose->focused_editable &&
10348 #ifndef GENERIC_UMPC
10349 gtk_widget_has_focus(compose->focused_editable)
10352 entry_paste_clipboard(compose, compose->focused_editable,
10353 prefs_common.linewrap_pastes,
10354 GDK_SELECTION_CLIPBOARD, NULL);
10359 #ifndef GENERIC_UMPC
10360 gtk_widget_has_focus(compose->text) &&
10362 compose->gtkaspell &&
10363 compose->gtkaspell->check_while_typing)
10364 gtkaspell_highlight_all(compose->gtkaspell);
10368 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10370 Compose *compose = (Compose *)data;
10371 gint wrap_quote = prefs_common.linewrap_quote;
10372 if (compose->focused_editable
10373 #ifndef GENERIC_UMPC
10374 && gtk_widget_has_focus(compose->focused_editable)
10377 /* let text_insert() (called directly or at a later time
10378 * after the gtk_editable_paste_clipboard) know that
10379 * text is to be inserted as a quotation. implemented
10380 * by using a simple refcount... */
10381 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10382 G_OBJECT(compose->focused_editable),
10383 "paste_as_quotation"));
10384 g_object_set_data(G_OBJECT(compose->focused_editable),
10385 "paste_as_quotation",
10386 GINT_TO_POINTER(paste_as_quotation + 1));
10387 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10388 entry_paste_clipboard(compose, compose->focused_editable,
10389 prefs_common.linewrap_pastes,
10390 GDK_SELECTION_CLIPBOARD, NULL);
10391 prefs_common.linewrap_quote = wrap_quote;
10395 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10397 Compose *compose = (Compose *)data;
10398 gint prev_autowrap;
10399 GtkTextBuffer *buffer;
10401 if (compose->focused_editable
10402 #ifndef GENERIC_UMPC
10403 && gtk_widget_has_focus(compose->focused_editable)
10406 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10407 GDK_SELECTION_CLIPBOARD, NULL);
10412 #ifndef GENERIC_UMPC
10413 gtk_widget_has_focus(compose->text) &&
10415 compose->gtkaspell &&
10416 compose->gtkaspell->check_while_typing)
10417 gtkaspell_highlight_all(compose->gtkaspell);
10421 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10423 Compose *compose = (Compose *)data;
10424 gint prev_autowrap;
10425 GtkTextBuffer *buffer;
10427 if (compose->focused_editable
10428 #ifndef GENERIC_UMPC
10429 && gtk_widget_has_focus(compose->focused_editable)
10432 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10433 GDK_SELECTION_CLIPBOARD, NULL);
10438 #ifndef GENERIC_UMPC
10439 gtk_widget_has_focus(compose->text) &&
10441 compose->gtkaspell &&
10442 compose->gtkaspell->check_while_typing)
10443 gtkaspell_highlight_all(compose->gtkaspell);
10447 static void compose_allsel_cb(GtkAction *action, gpointer data)
10449 Compose *compose = (Compose *)data;
10450 if (compose->focused_editable
10451 #ifndef GENERIC_UMPC
10452 && gtk_widget_has_focus(compose->focused_editable)
10455 entry_allsel(compose->focused_editable);
10458 static void textview_move_beginning_of_line (GtkTextView *text)
10460 GtkTextBuffer *buffer;
10464 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10466 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10467 mark = gtk_text_buffer_get_insert(buffer);
10468 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10469 gtk_text_iter_set_line_offset(&ins, 0);
10470 gtk_text_buffer_place_cursor(buffer, &ins);
10473 static void textview_move_forward_character (GtkTextView *text)
10475 GtkTextBuffer *buffer;
10479 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10481 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10482 mark = gtk_text_buffer_get_insert(buffer);
10483 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10484 if (gtk_text_iter_forward_cursor_position(&ins))
10485 gtk_text_buffer_place_cursor(buffer, &ins);
10488 static void textview_move_backward_character (GtkTextView *text)
10490 GtkTextBuffer *buffer;
10494 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10496 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10497 mark = gtk_text_buffer_get_insert(buffer);
10498 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10499 if (gtk_text_iter_backward_cursor_position(&ins))
10500 gtk_text_buffer_place_cursor(buffer, &ins);
10503 static void textview_move_forward_word (GtkTextView *text)
10505 GtkTextBuffer *buffer;
10510 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10512 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10513 mark = gtk_text_buffer_get_insert(buffer);
10514 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10515 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10516 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10517 gtk_text_iter_backward_word_start(&ins);
10518 gtk_text_buffer_place_cursor(buffer, &ins);
10522 static void textview_move_backward_word (GtkTextView *text)
10524 GtkTextBuffer *buffer;
10528 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10530 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10531 mark = gtk_text_buffer_get_insert(buffer);
10532 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10533 if (gtk_text_iter_backward_word_starts(&ins, 1))
10534 gtk_text_buffer_place_cursor(buffer, &ins);
10537 static void textview_move_end_of_line (GtkTextView *text)
10539 GtkTextBuffer *buffer;
10543 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10545 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10546 mark = gtk_text_buffer_get_insert(buffer);
10547 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10548 if (gtk_text_iter_forward_to_line_end(&ins))
10549 gtk_text_buffer_place_cursor(buffer, &ins);
10552 static void textview_move_next_line (GtkTextView *text)
10554 GtkTextBuffer *buffer;
10559 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10561 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10562 mark = gtk_text_buffer_get_insert(buffer);
10563 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10564 offset = gtk_text_iter_get_line_offset(&ins);
10565 if (gtk_text_iter_forward_line(&ins)) {
10566 gtk_text_iter_set_line_offset(&ins, offset);
10567 gtk_text_buffer_place_cursor(buffer, &ins);
10571 static void textview_move_previous_line (GtkTextView *text)
10573 GtkTextBuffer *buffer;
10578 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10580 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10581 mark = gtk_text_buffer_get_insert(buffer);
10582 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10583 offset = gtk_text_iter_get_line_offset(&ins);
10584 if (gtk_text_iter_backward_line(&ins)) {
10585 gtk_text_iter_set_line_offset(&ins, offset);
10586 gtk_text_buffer_place_cursor(buffer, &ins);
10590 static void textview_delete_forward_character (GtkTextView *text)
10592 GtkTextBuffer *buffer;
10594 GtkTextIter ins, end_iter;
10596 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10598 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10599 mark = gtk_text_buffer_get_insert(buffer);
10600 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10602 if (gtk_text_iter_forward_char(&end_iter)) {
10603 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10607 static void textview_delete_backward_character (GtkTextView *text)
10609 GtkTextBuffer *buffer;
10611 GtkTextIter ins, end_iter;
10613 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10615 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10616 mark = gtk_text_buffer_get_insert(buffer);
10617 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10619 if (gtk_text_iter_backward_char(&end_iter)) {
10620 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10624 static void textview_delete_forward_word (GtkTextView *text)
10626 GtkTextBuffer *buffer;
10628 GtkTextIter ins, end_iter;
10630 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10632 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10633 mark = gtk_text_buffer_get_insert(buffer);
10634 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10636 if (gtk_text_iter_forward_word_end(&end_iter)) {
10637 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10641 static void textview_delete_backward_word (GtkTextView *text)
10643 GtkTextBuffer *buffer;
10645 GtkTextIter ins, end_iter;
10647 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10649 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10650 mark = gtk_text_buffer_get_insert(buffer);
10651 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10653 if (gtk_text_iter_backward_word_start(&end_iter)) {
10654 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10658 static void textview_delete_line (GtkTextView *text)
10660 GtkTextBuffer *buffer;
10662 GtkTextIter ins, start_iter, end_iter;
10664 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10666 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10667 mark = gtk_text_buffer_get_insert(buffer);
10668 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10671 gtk_text_iter_set_line_offset(&start_iter, 0);
10674 if (gtk_text_iter_ends_line(&end_iter)){
10675 if (!gtk_text_iter_forward_char(&end_iter))
10676 gtk_text_iter_backward_char(&start_iter);
10679 gtk_text_iter_forward_to_line_end(&end_iter);
10680 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10683 static void textview_delete_to_line_end (GtkTextView *text)
10685 GtkTextBuffer *buffer;
10687 GtkTextIter ins, end_iter;
10689 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10691 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10692 mark = gtk_text_buffer_get_insert(buffer);
10693 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10695 if (gtk_text_iter_ends_line(&end_iter))
10696 gtk_text_iter_forward_char(&end_iter);
10698 gtk_text_iter_forward_to_line_end(&end_iter);
10699 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10702 #define DO_ACTION(name, act) { \
10703 if(!strcmp(name, a_name)) { \
10707 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10709 const gchar *a_name = gtk_action_get_name(action);
10710 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10711 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10712 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10713 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10714 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10715 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10716 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10717 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10718 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10719 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10720 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10721 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10722 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10723 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10727 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10729 Compose *compose = (Compose *)data;
10730 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10731 ComposeCallAdvancedAction action = -1;
10733 action = compose_call_advanced_action_from_path(gaction);
10736 void (*do_action) (GtkTextView *text);
10737 } action_table[] = {
10738 {textview_move_beginning_of_line},
10739 {textview_move_forward_character},
10740 {textview_move_backward_character},
10741 {textview_move_forward_word},
10742 {textview_move_backward_word},
10743 {textview_move_end_of_line},
10744 {textview_move_next_line},
10745 {textview_move_previous_line},
10746 {textview_delete_forward_character},
10747 {textview_delete_backward_character},
10748 {textview_delete_forward_word},
10749 {textview_delete_backward_word},
10750 {textview_delete_line},
10751 {textview_delete_to_line_end}
10754 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10756 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10757 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10758 if (action_table[action].do_action)
10759 action_table[action].do_action(text);
10761 g_warning("Not implemented yet.");
10765 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10767 GtkAllocation allocation;
10771 if (GTK_IS_EDITABLE(widget)) {
10772 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10773 gtk_editable_set_position(GTK_EDITABLE(widget),
10776 if ((parent = gtk_widget_get_parent(widget))
10777 && (parent = gtk_widget_get_parent(parent))
10778 && (parent = gtk_widget_get_parent(parent))) {
10779 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10780 gtk_widget_get_allocation(widget, &allocation);
10781 gint y = allocation.y;
10782 gint height = allocation.height;
10783 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10784 (GTK_SCROLLED_WINDOW(parent));
10786 gfloat value = gtk_adjustment_get_value(shown);
10787 gfloat upper = gtk_adjustment_get_upper(shown);
10788 gfloat page_size = gtk_adjustment_get_page_size(shown);
10789 if (y < (int)value) {
10790 gtk_adjustment_set_value(shown, y - 1);
10792 if ((y + height) > ((int)value + (int)page_size)) {
10793 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10794 gtk_adjustment_set_value(shown,
10795 y + height - (int)page_size - 1);
10797 gtk_adjustment_set_value(shown,
10798 (int)upper - (int)page_size - 1);
10805 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10806 compose->focused_editable = widget;
10808 #ifdef GENERIC_UMPC
10809 if (GTK_IS_TEXT_VIEW(widget)
10810 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10811 g_object_ref(compose->notebook);
10812 g_object_ref(compose->edit_vbox);
10813 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10814 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10815 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10816 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10817 g_object_unref(compose->notebook);
10818 g_object_unref(compose->edit_vbox);
10819 g_signal_handlers_block_by_func(G_OBJECT(widget),
10820 G_CALLBACK(compose_grab_focus_cb),
10822 gtk_widget_grab_focus(widget);
10823 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10824 G_CALLBACK(compose_grab_focus_cb),
10826 } else if (!GTK_IS_TEXT_VIEW(widget)
10827 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10828 g_object_ref(compose->notebook);
10829 g_object_ref(compose->edit_vbox);
10830 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10831 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10832 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10833 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10834 g_object_unref(compose->notebook);
10835 g_object_unref(compose->edit_vbox);
10836 g_signal_handlers_block_by_func(G_OBJECT(widget),
10837 G_CALLBACK(compose_grab_focus_cb),
10839 gtk_widget_grab_focus(widget);
10840 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10841 G_CALLBACK(compose_grab_focus_cb),
10847 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10849 compose->modified = TRUE;
10850 // compose_beautify_paragraph(compose, NULL, TRUE);
10851 #ifndef GENERIC_UMPC
10852 compose_set_title(compose);
10856 static void compose_wrap_cb(GtkAction *action, gpointer data)
10858 Compose *compose = (Compose *)data;
10859 compose_beautify_paragraph(compose, NULL, TRUE);
10862 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10864 Compose *compose = (Compose *)data;
10865 compose_wrap_all_full(compose, TRUE);
10868 static void compose_find_cb(GtkAction *action, gpointer data)
10870 Compose *compose = (Compose *)data;
10872 message_search_compose(compose);
10875 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10878 Compose *compose = (Compose *)data;
10879 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10880 if (compose->autowrap)
10881 compose_wrap_all_full(compose, TRUE);
10882 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10885 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10888 Compose *compose = (Compose *)data;
10889 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10892 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10894 Compose *compose = (Compose *)data;
10896 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10899 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10901 Compose *compose = (Compose *)data;
10903 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10906 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10908 g_free(compose->privacy_system);
10910 compose->privacy_system = g_strdup(account->default_privacy_system);
10911 compose_update_privacy_system_menu_item(compose, warn);
10914 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10916 Compose *compose = (Compose *)data;
10918 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10919 gtk_widget_show(compose->ruler_hbox);
10920 prefs_common.show_ruler = TRUE;
10922 gtk_widget_hide(compose->ruler_hbox);
10923 gtk_widget_queue_resize(compose->edit_vbox);
10924 prefs_common.show_ruler = FALSE;
10928 static void compose_attach_drag_received_cb (GtkWidget *widget,
10929 GdkDragContext *context,
10932 GtkSelectionData *data,
10935 gpointer user_data)
10937 Compose *compose = (Compose *)user_data;
10941 type = gtk_selection_data_get_data_type(data);
10942 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10944 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10946 ) && gtk_drag_get_source_widget(context) !=
10947 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10948 list = uri_list_extract_filenames(
10949 (const gchar *)gtk_selection_data_get_data(data));
10950 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10951 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10952 compose_attach_append
10953 (compose, (const gchar *)tmp->data,
10954 utf8_filename, NULL, NULL);
10955 g_free(utf8_filename);
10957 if (list) compose_changed_cb(NULL, compose);
10958 list_free_strings(list);
10960 } else if (gtk_drag_get_source_widget(context)
10961 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10962 /* comes from our summaryview */
10963 SummaryView * summaryview = NULL;
10964 GSList * list = NULL, *cur = NULL;
10966 if (mainwindow_get_mainwindow())
10967 summaryview = mainwindow_get_mainwindow()->summaryview;
10970 list = summary_get_selected_msg_list(summaryview);
10972 for (cur = list; cur; cur = cur->next) {
10973 MsgInfo *msginfo = (MsgInfo *)cur->data;
10974 gchar *file = NULL;
10976 file = procmsg_get_message_file_full(msginfo,
10979 compose_attach_append(compose, (const gchar *)file,
10980 (const gchar *)file, "message/rfc822", NULL);
10984 g_slist_free(list);
10988 static gboolean compose_drag_drop(GtkWidget *widget,
10989 GdkDragContext *drag_context,
10991 guint time, gpointer user_data)
10993 /* not handling this signal makes compose_insert_drag_received_cb
10998 static gboolean completion_set_focus_to_subject
10999 (GtkWidget *widget,
11000 GdkEventKey *event,
11003 cm_return_val_if_fail(compose != NULL, FALSE);
11005 /* make backtab move to subject field */
11006 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11007 gtk_widget_grab_focus(compose->subject_entry);
11013 static void compose_insert_drag_received_cb (GtkWidget *widget,
11014 GdkDragContext *drag_context,
11017 GtkSelectionData *data,
11020 gpointer user_data)
11022 Compose *compose = (Compose *)user_data;
11026 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11028 type = gtk_selection_data_get_data_type(data);
11030 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11032 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11034 AlertValue val = G_ALERTDEFAULT;
11035 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11037 list = uri_list_extract_filenames(ddata);
11038 if (list == NULL && strstr(ddata, "://")) {
11039 /* Assume a list of no files, and data has ://, is a remote link */
11040 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11041 gchar *tmpfile = get_tmp_file();
11042 str_write_to_file(tmpdata, tmpfile);
11044 compose_insert_file(compose, tmpfile);
11045 claws_unlink(tmpfile);
11047 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11048 compose_beautify_paragraph(compose, NULL, TRUE);
11051 switch (prefs_common.compose_dnd_mode) {
11052 case COMPOSE_DND_ASK:
11053 val = alertpanel_full(_("Insert or attach?"),
11054 _("Do you want to insert the contents of the file(s) "
11055 "into the message body, or attach it to the email?"),
11056 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11057 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11059 case COMPOSE_DND_INSERT:
11060 val = G_ALERTALTERNATE;
11062 case COMPOSE_DND_ATTACH:
11063 val = G_ALERTOTHER;
11066 /* unexpected case */
11067 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11070 if (val & G_ALERTDISABLE) {
11071 val &= ~G_ALERTDISABLE;
11072 /* remember what action to perform by default, only if we don't click Cancel */
11073 if (val == G_ALERTALTERNATE)
11074 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11075 else if (val == G_ALERTOTHER)
11076 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11079 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11080 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11081 list_free_strings(list);
11084 } else if (val == G_ALERTOTHER) {
11085 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11086 list_free_strings(list);
11091 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11092 compose_insert_file(compose, (const gchar *)tmp->data);
11094 list_free_strings(list);
11096 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11101 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11104 static void compose_header_drag_received_cb (GtkWidget *widget,
11105 GdkDragContext *drag_context,
11108 GtkSelectionData *data,
11111 gpointer user_data)
11113 GtkEditable *entry = (GtkEditable *)user_data;
11114 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11116 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11119 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11120 gchar *decoded=g_new(gchar, strlen(email));
11123 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11124 gtk_editable_delete_text(entry, 0, -1);
11125 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11126 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11130 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11133 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11135 Compose *compose = (Compose *)data;
11137 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11138 compose->return_receipt = TRUE;
11140 compose->return_receipt = FALSE;
11143 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11145 Compose *compose = (Compose *)data;
11147 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11148 compose->remove_references = TRUE;
11150 compose->remove_references = FALSE;
11153 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11154 ComposeHeaderEntry *headerentry)
11156 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11160 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11161 GdkEventKey *event,
11162 ComposeHeaderEntry *headerentry)
11164 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11165 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11166 !(event->state & GDK_MODIFIER_MASK) &&
11167 (event->keyval == GDK_KEY_BackSpace) &&
11168 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11169 gtk_container_remove
11170 (GTK_CONTAINER(headerentry->compose->header_table),
11171 headerentry->combo);
11172 gtk_container_remove
11173 (GTK_CONTAINER(headerentry->compose->header_table),
11174 headerentry->entry);
11175 headerentry->compose->header_list =
11176 g_slist_remove(headerentry->compose->header_list,
11178 g_free(headerentry);
11179 } else if (event->keyval == GDK_KEY_Tab) {
11180 if (headerentry->compose->header_last == headerentry) {
11181 /* Override default next focus, and give it to subject_entry
11182 * instead of notebook tabs
11184 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11185 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11192 static gboolean scroll_postpone(gpointer data)
11194 Compose *compose = (Compose *)data;
11196 if (compose->batch)
11199 GTK_EVENTS_FLUSH();
11200 compose_show_first_last_header(compose, FALSE);
11204 static void compose_headerentry_changed_cb(GtkWidget *entry,
11205 ComposeHeaderEntry *headerentry)
11207 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11208 compose_create_header_entry(headerentry->compose);
11209 g_signal_handlers_disconnect_matched
11210 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11211 0, 0, NULL, NULL, headerentry);
11213 if (!headerentry->compose->batch)
11214 g_timeout_add(0, scroll_postpone, headerentry->compose);
11218 static gboolean compose_defer_auto_save_draft(Compose *compose)
11220 compose->draft_timeout_tag = -1;
11221 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11225 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11227 GtkAdjustment *vadj;
11229 cm_return_if_fail(compose);
11234 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11235 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11236 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11237 gtk_widget_get_parent(compose->header_table)));
11238 gtk_adjustment_set_value(vadj, (show_first ?
11239 gtk_adjustment_get_lower(vadj) :
11240 (gtk_adjustment_get_upper(vadj) -
11241 gtk_adjustment_get_page_size(vadj))));
11242 gtk_adjustment_changed(vadj);
11245 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11246 const gchar *text, gint len, Compose *compose)
11248 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11249 (G_OBJECT(compose->text), "paste_as_quotation"));
11252 cm_return_if_fail(text != NULL);
11254 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11255 G_CALLBACK(text_inserted),
11257 if (paste_as_quotation) {
11259 const gchar *qmark;
11261 GtkTextIter start_iter;
11264 len = strlen(text);
11266 new_text = g_strndup(text, len);
11268 qmark = compose_quote_char_from_context(compose);
11270 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11271 gtk_text_buffer_place_cursor(buffer, iter);
11273 pos = gtk_text_iter_get_offset(iter);
11275 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11276 _("Quote format error at line %d."));
11277 quote_fmt_reset_vartable();
11279 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11280 GINT_TO_POINTER(paste_as_quotation - 1));
11282 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11283 gtk_text_buffer_place_cursor(buffer, iter);
11284 gtk_text_buffer_delete_mark(buffer, mark);
11286 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11287 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11288 compose_beautify_paragraph(compose, &start_iter, FALSE);
11289 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11290 gtk_text_buffer_delete_mark(buffer, mark);
11292 if (strcmp(text, "\n") || compose->automatic_break
11293 || gtk_text_iter_starts_line(iter)) {
11294 GtkTextIter before_ins;
11295 gtk_text_buffer_insert(buffer, iter, text, len);
11296 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11297 before_ins = *iter;
11298 gtk_text_iter_backward_chars(&before_ins, len);
11299 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11302 /* check if the preceding is just whitespace or quote */
11303 GtkTextIter start_line;
11304 gchar *tmp = NULL, *quote = NULL;
11305 gint quote_len = 0, is_normal = 0;
11306 start_line = *iter;
11307 gtk_text_iter_set_line_offset(&start_line, 0);
11308 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11311 if (*tmp == '\0') {
11314 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11322 gtk_text_buffer_insert(buffer, iter, text, len);
11324 gtk_text_buffer_insert_with_tags_by_name(buffer,
11325 iter, text, len, "no_join", NULL);
11330 if (!paste_as_quotation) {
11331 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11332 compose_beautify_paragraph(compose, iter, FALSE);
11333 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11334 gtk_text_buffer_delete_mark(buffer, mark);
11337 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11338 G_CALLBACK(text_inserted),
11340 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11342 if (compose_can_autosave(compose) &&
11343 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11344 compose->draft_timeout_tag != -2 /* disabled while loading */)
11345 compose->draft_timeout_tag = g_timeout_add
11346 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11350 static void compose_check_all(GtkAction *action, gpointer data)
11352 Compose *compose = (Compose *)data;
11353 if (!compose->gtkaspell)
11356 if (gtk_widget_has_focus(compose->subject_entry))
11357 claws_spell_entry_check_all(
11358 CLAWS_SPELL_ENTRY(compose->subject_entry));
11360 gtkaspell_check_all(compose->gtkaspell);
11363 static void compose_highlight_all(GtkAction *action, gpointer data)
11365 Compose *compose = (Compose *)data;
11366 if (compose->gtkaspell) {
11367 claws_spell_entry_recheck_all(
11368 CLAWS_SPELL_ENTRY(compose->subject_entry));
11369 gtkaspell_highlight_all(compose->gtkaspell);
11373 static void compose_check_backwards(GtkAction *action, gpointer data)
11375 Compose *compose = (Compose *)data;
11376 if (!compose->gtkaspell) {
11377 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11381 if (gtk_widget_has_focus(compose->subject_entry))
11382 claws_spell_entry_check_backwards(
11383 CLAWS_SPELL_ENTRY(compose->subject_entry));
11385 gtkaspell_check_backwards(compose->gtkaspell);
11388 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11390 Compose *compose = (Compose *)data;
11391 if (!compose->gtkaspell) {
11392 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11396 if (gtk_widget_has_focus(compose->subject_entry))
11397 claws_spell_entry_check_forwards_go(
11398 CLAWS_SPELL_ENTRY(compose->subject_entry));
11400 gtkaspell_check_forwards_go(compose->gtkaspell);
11405 *\brief Guess originating forward account from MsgInfo and several
11406 * "common preference" settings. Return NULL if no guess.
11408 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11410 PrefsAccount *account = NULL;
11412 cm_return_val_if_fail(msginfo, NULL);
11413 cm_return_val_if_fail(msginfo->folder, NULL);
11414 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11416 if (msginfo->folder->prefs->enable_default_account)
11417 account = account_find_from_id(msginfo->folder->prefs->default_account);
11420 account = msginfo->folder->folder->account;
11422 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11424 Xstrdup_a(to, msginfo->to, return NULL);
11425 extract_address(to);
11426 account = account_find_from_address(to, FALSE);
11429 if (!account && prefs_common.forward_account_autosel) {
11430 gchar cc[BUFFSIZE];
11431 if (!procheader_get_header_from_msginfo
11432 (msginfo, cc,sizeof cc , "Cc:")) {
11433 gchar *buf = cc + strlen("Cc:");
11434 extract_address(buf);
11435 account = account_find_from_address(buf, FALSE);
11439 if (!account && prefs_common.forward_account_autosel) {
11440 gchar deliveredto[BUFFSIZE];
11441 if (!procheader_get_header_from_msginfo
11442 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11443 gchar *buf = deliveredto + strlen("Delivered-To:");
11444 extract_address(buf);
11445 account = account_find_from_address(buf, FALSE);
11452 gboolean compose_close(Compose *compose)
11456 if (!g_mutex_trylock(compose->mutex)) {
11457 /* we have to wait for the (possibly deferred by auto-save)
11458 * drafting to be done, before destroying the compose under
11460 debug_print("waiting for drafting to finish...\n");
11461 compose_allow_user_actions(compose, FALSE);
11462 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11465 cm_return_val_if_fail(compose, FALSE);
11466 gtkut_widget_get_uposition(compose->window, &x, &y);
11467 if (!compose->batch) {
11468 prefs_common.compose_x = x;
11469 prefs_common.compose_y = y;
11471 g_mutex_unlock(compose->mutex);
11472 compose_destroy(compose);
11477 * Add entry field for each address in list.
11478 * \param compose E-Mail composition object.
11479 * \param listAddress List of (formatted) E-Mail addresses.
11481 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11484 node = listAddress;
11486 addr = ( gchar * ) node->data;
11487 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11488 node = g_list_next( node );
11492 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11493 guint action, gboolean opening_multiple)
11495 gchar *body = NULL;
11496 GSList *new_msglist = NULL;
11497 MsgInfo *tmp_msginfo = NULL;
11498 gboolean originally_enc = FALSE;
11499 gboolean originally_sig = FALSE;
11500 Compose *compose = NULL;
11501 gchar *s_system = NULL;
11503 cm_return_if_fail(msgview != NULL);
11505 cm_return_if_fail(msginfo_list != NULL);
11507 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11508 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11509 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11511 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11512 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11513 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11514 orig_msginfo, mimeinfo);
11515 if (tmp_msginfo != NULL) {
11516 new_msglist = g_slist_append(NULL, tmp_msginfo);
11518 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11519 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11520 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11522 tmp_msginfo->folder = orig_msginfo->folder;
11523 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11524 if (orig_msginfo->tags) {
11525 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11526 tmp_msginfo->folder->tags_dirty = TRUE;
11532 if (!opening_multiple)
11533 body = messageview_get_selection(msgview);
11536 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11537 procmsg_msginfo_free(tmp_msginfo);
11538 g_slist_free(new_msglist);
11540 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11542 if (compose && originally_enc) {
11543 compose_force_encryption(compose, compose->account, FALSE, s_system);
11546 if (compose && originally_sig && compose->account->default_sign_reply) {
11547 compose_force_signing(compose, compose->account, s_system);
11551 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11554 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11557 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11558 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11559 GSList *cur = msginfo_list;
11560 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11561 "messages. Opening the windows "
11562 "could take some time. Do you "
11563 "want to continue?"),
11564 g_slist_length(msginfo_list));
11565 if (g_slist_length(msginfo_list) > 9
11566 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11567 != G_ALERTALTERNATE) {
11572 /* We'll open multiple compose windows */
11573 /* let the WM place the next windows */
11574 compose_force_window_origin = FALSE;
11575 for (; cur; cur = cur->next) {
11577 tmplist.data = cur->data;
11578 tmplist.next = NULL;
11579 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11581 compose_force_window_origin = TRUE;
11583 /* forwarding multiple mails as attachments is done via a
11584 * single compose window */
11585 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11589 void compose_check_for_email_account(Compose *compose)
11591 PrefsAccount *ac = NULL, *curr = NULL;
11597 if (compose->account && compose->account->protocol == A_NNTP) {
11598 ac = account_get_cur_account();
11599 if (ac->protocol == A_NNTP) {
11600 list = account_get_list();
11602 for( ; list != NULL ; list = g_list_next(list)) {
11603 curr = (PrefsAccount *) list->data;
11604 if (curr->protocol != A_NNTP) {
11610 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11615 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11616 const gchar *address)
11618 GSList *msginfo_list = NULL;
11619 gchar *body = messageview_get_selection(msgview);
11622 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11624 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11625 compose_check_for_email_account(compose);
11626 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11627 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11628 compose_reply_set_subject(compose, msginfo);
11631 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11634 void compose_set_position(Compose *compose, gint pos)
11636 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11638 gtkut_text_view_set_position(text, pos);
11641 gboolean compose_search_string(Compose *compose,
11642 const gchar *str, gboolean case_sens)
11644 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11646 return gtkut_text_view_search_string(text, str, case_sens);
11649 gboolean compose_search_string_backward(Compose *compose,
11650 const gchar *str, gboolean case_sens)
11652 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11654 return gtkut_text_view_search_string_backward(text, str, case_sens);
11657 /* allocate a msginfo structure and populate its data from a compose data structure */
11658 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11660 MsgInfo *newmsginfo;
11662 gchar buf[BUFFSIZE];
11664 cm_return_val_if_fail( compose != NULL, NULL );
11666 newmsginfo = procmsg_msginfo_new();
11669 get_rfc822_date(buf, sizeof(buf));
11670 newmsginfo->date = g_strdup(buf);
11673 if (compose->from_name) {
11674 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11675 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11679 if (compose->subject_entry)
11680 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11682 /* to, cc, reply-to, newsgroups */
11683 for (list = compose->header_list; list; list = list->next) {
11684 gchar *header = gtk_editable_get_chars(
11686 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11687 gchar *entry = gtk_editable_get_chars(
11688 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11690 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11691 if ( newmsginfo->to == NULL ) {
11692 newmsginfo->to = g_strdup(entry);
11693 } else if (entry && *entry) {
11694 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11695 g_free(newmsginfo->to);
11696 newmsginfo->to = tmp;
11699 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11700 if ( newmsginfo->cc == NULL ) {
11701 newmsginfo->cc = g_strdup(entry);
11702 } else if (entry && *entry) {
11703 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11704 g_free(newmsginfo->cc);
11705 newmsginfo->cc = tmp;
11708 if ( strcasecmp(header,
11709 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11710 if ( newmsginfo->newsgroups == NULL ) {
11711 newmsginfo->newsgroups = g_strdup(entry);
11712 } else if (entry && *entry) {
11713 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11714 g_free(newmsginfo->newsgroups);
11715 newmsginfo->newsgroups = tmp;
11723 /* other data is unset */
11729 /* update compose's dictionaries from folder dict settings */
11730 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11731 FolderItem *folder_item)
11733 cm_return_if_fail(compose != NULL);
11735 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11736 FolderItemPrefs *prefs = folder_item->prefs;
11738 if (prefs->enable_default_dictionary)
11739 gtkaspell_change_dict(compose->gtkaspell,
11740 prefs->default_dictionary, FALSE);
11741 if (folder_item->prefs->enable_default_alt_dictionary)
11742 gtkaspell_change_alt_dict(compose->gtkaspell,
11743 prefs->default_alt_dictionary);
11744 if (prefs->enable_default_dictionary
11745 || prefs->enable_default_alt_dictionary)
11746 compose_spell_menu_changed(compose);
11751 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11753 Compose *compose = (Compose *)data;
11755 cm_return_if_fail(compose != NULL);
11757 gtk_widget_grab_focus(compose->text);