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 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
186 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
188 static GList *compose_list = NULL;
189 static GSList *extra_headers = NULL;
191 static Compose *compose_generic_new (PrefsAccount *account,
195 GList *listAddress );
197 static Compose *compose_create (PrefsAccount *account,
202 static void compose_entry_mark_default_to (Compose *compose,
203 const gchar *address);
204 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
205 ComposeQuoteMode quote_mode,
209 static Compose *compose_forward_multiple (PrefsAccount *account,
210 GSList *msginfo_list);
211 static Compose *compose_reply (MsgInfo *msginfo,
212 ComposeQuoteMode quote_mode,
217 static Compose *compose_reply_mode (ComposeMode mode,
218 GSList *msginfo_list,
220 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
221 static void compose_update_privacy_systems_menu(Compose *compose);
223 static GtkWidget *compose_account_option_menu_create
225 static void compose_set_out_encoding (Compose *compose);
226 static void compose_set_template_menu (Compose *compose);
227 static void compose_destroy (Compose *compose);
229 static MailField compose_entries_set (Compose *compose,
231 ComposeEntryType to_type);
232 static gint compose_parse_header (Compose *compose,
234 static gint compose_parse_manual_headers (Compose *compose,
236 HeaderEntry *entries);
237 static gchar *compose_parse_references (const gchar *ref,
240 static gchar *compose_quote_fmt (Compose *compose,
246 gboolean need_unescape,
247 const gchar *err_msg);
249 static void compose_reply_set_entry (Compose *compose,
255 followup_and_reply_to);
256 static void compose_reedit_set_entry (Compose *compose,
259 static void compose_insert_sig (Compose *compose,
261 static ComposeInsertResult compose_insert_file (Compose *compose,
264 static gboolean compose_attach_append (Compose *compose,
267 const gchar *content_type,
268 const gchar *charset);
269 static void compose_attach_parts (Compose *compose,
272 static gboolean compose_beautify_paragraph (Compose *compose,
273 GtkTextIter *par_iter,
275 static void compose_wrap_all (Compose *compose);
276 static void compose_wrap_all_full (Compose *compose,
279 static void compose_set_title (Compose *compose);
280 static void compose_select_account (Compose *compose,
281 PrefsAccount *account,
284 static PrefsAccount *compose_current_mail_account(void);
285 /* static gint compose_send (Compose *compose); */
286 static gboolean compose_check_for_valid_recipient
288 static gboolean compose_check_entries (Compose *compose,
289 gboolean check_everything);
290 static gint compose_write_to_file (Compose *compose,
293 gboolean attach_parts);
294 static gint compose_write_body_to_file (Compose *compose,
296 static gint compose_remove_reedit_target (Compose *compose,
298 static void compose_remove_draft (Compose *compose);
299 static gint compose_queue_sub (Compose *compose,
303 gboolean check_subject,
304 gboolean remove_reedit_target);
305 static int compose_add_attachments (Compose *compose,
307 static gchar *compose_get_header (Compose *compose);
308 static gchar *compose_get_manual_headers_info (Compose *compose);
310 static void compose_convert_header (Compose *compose,
315 gboolean addr_field);
317 static void compose_attach_info_free (AttachInfo *ainfo);
318 static void compose_attach_remove_selected (GtkAction *action,
321 static void compose_template_apply (Compose *compose,
324 static void compose_attach_property (GtkAction *action,
326 static void compose_attach_property_create (gboolean *cancelled);
327 static void attach_property_ok (GtkWidget *widget,
328 gboolean *cancelled);
329 static void attach_property_cancel (GtkWidget *widget,
330 gboolean *cancelled);
331 static gint attach_property_delete_event (GtkWidget *widget,
333 gboolean *cancelled);
334 static gboolean attach_property_key_pressed (GtkWidget *widget,
336 gboolean *cancelled);
338 static void compose_exec_ext_editor (Compose *compose);
340 static gint compose_exec_ext_editor_real (const gchar *file);
341 static gboolean compose_ext_editor_kill (Compose *compose);
342 static gboolean compose_input_cb (GIOChannel *source,
343 GIOCondition condition,
345 static void compose_set_ext_editor_sensitive (Compose *compose,
347 #endif /* G_OS_UNIX */
349 static void compose_undo_state_changed (UndoMain *undostruct,
354 static void compose_create_header_entry (Compose *compose);
355 static void compose_add_header_entry (Compose *compose, const gchar *header,
356 gchar *text, ComposePrefType pref_type);
357 static void compose_remove_header_entries(Compose *compose);
359 static void compose_update_priority_menu_item(Compose * compose);
361 static void compose_spell_menu_changed (void *data);
362 static void compose_dict_changed (void *data);
364 static void compose_add_field_list ( Compose *compose,
365 GList *listAddress );
367 /* callback functions */
369 static void compose_notebook_size_alloc (GtkNotebook *notebook,
370 GtkAllocation *allocation,
372 static gboolean compose_edit_size_alloc (GtkEditable *widget,
373 GtkAllocation *allocation,
374 GtkSHRuler *shruler);
375 static void account_activated (GtkComboBox *optmenu,
377 static void attach_selected (GtkTreeView *tree_view,
378 GtkTreePath *tree_path,
379 GtkTreeViewColumn *column,
381 static gboolean attach_button_pressed (GtkWidget *widget,
382 GdkEventButton *event,
384 static gboolean attach_key_pressed (GtkWidget *widget,
387 static void compose_send_cb (GtkAction *action, gpointer data);
388 static void compose_send_later_cb (GtkAction *action, gpointer data);
390 static void compose_save_cb (GtkAction *action,
393 static void compose_attach_cb (GtkAction *action,
395 static void compose_insert_file_cb (GtkAction *action,
397 static void compose_insert_sig_cb (GtkAction *action,
399 static void compose_replace_sig_cb (GtkAction *action,
402 static void compose_close_cb (GtkAction *action,
404 static void compose_print_cb (GtkAction *action,
407 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
409 static void compose_address_cb (GtkAction *action,
411 static void about_show_cb (GtkAction *action,
413 static void compose_template_activate_cb(GtkWidget *widget,
416 static void compose_ext_editor_cb (GtkAction *action,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (GtkAction *action,
425 static void compose_redo_cb (GtkAction *action,
427 static void compose_cut_cb (GtkAction *action,
429 static void compose_copy_cb (GtkAction *action,
431 static void compose_paste_cb (GtkAction *action,
433 static void compose_paste_as_quote_cb (GtkAction *action,
435 static void compose_paste_no_wrap_cb (GtkAction *action,
437 static void compose_paste_wrap_cb (GtkAction *action,
439 static void compose_allsel_cb (GtkAction *action,
442 static void compose_advanced_action_cb (GtkAction *action,
445 static void compose_grab_focus_cb (GtkWidget *widget,
448 static void compose_changed_cb (GtkTextBuffer *textbuf,
451 static void compose_wrap_cb (GtkAction *action,
453 static void compose_wrap_all_cb (GtkAction *action,
455 static void compose_find_cb (GtkAction *action,
457 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
459 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
462 static void compose_toggle_ruler_cb (GtkToggleAction *action,
464 static void compose_toggle_sign_cb (GtkToggleAction *action,
466 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
468 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
469 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
470 static void activate_privacy_system (Compose *compose,
471 PrefsAccount *account,
473 static void compose_use_signing(Compose *compose, gboolean use_signing);
474 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
475 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
477 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
479 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
480 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
481 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
483 static void compose_attach_drag_received_cb (GtkWidget *widget,
484 GdkDragContext *drag_context,
487 GtkSelectionData *data,
491 static void compose_insert_drag_received_cb (GtkWidget *widget,
492 GdkDragContext *drag_context,
495 GtkSelectionData *data,
499 static void compose_header_drag_received_cb (GtkWidget *widget,
500 GdkDragContext *drag_context,
503 GtkSelectionData *data,
508 static gboolean compose_drag_drop (GtkWidget *widget,
509 GdkDragContext *drag_context,
511 guint time, gpointer user_data);
512 static gboolean completion_set_focus_to_subject
517 static void text_inserted (GtkTextBuffer *buffer,
522 static Compose *compose_generic_reply(MsgInfo *msginfo,
523 ComposeQuoteMode quote_mode,
527 gboolean followup_and_reply_to,
530 static void compose_headerentry_changed_cb (GtkWidget *entry,
531 ComposeHeaderEntry *headerentry);
532 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
534 ComposeHeaderEntry *headerentry);
535 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
536 ComposeHeaderEntry *headerentry);
538 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
540 static void compose_allow_user_actions (Compose *compose, gboolean allow);
542 static void compose_nothing_cb (GtkAction *action, gpointer data)
548 static void compose_check_all (GtkAction *action, gpointer data);
549 static void compose_highlight_all (GtkAction *action, gpointer data);
550 static void compose_check_backwards (GtkAction *action, gpointer data);
551 static void compose_check_forwards_go (GtkAction *action, gpointer data);
554 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
556 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
559 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
560 FolderItem *folder_item);
562 static void compose_attach_update_label(Compose *compose);
563 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
564 gboolean respect_default_to);
565 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
567 static GtkActionEntry compose_popup_entries[] =
569 {"Compose", NULL, "Compose" },
570 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
571 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
572 {"Compose/---", NULL, "---", NULL, NULL, NULL },
573 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
576 static GtkActionEntry compose_entries[] =
578 {"Menu", NULL, "Menu" },
580 {"Message", NULL, N_("_Message") },
581 {"Edit", NULL, N_("_Edit") },
583 {"Spelling", NULL, N_("_Spelling") },
585 {"Options", NULL, N_("_Options") },
586 {"Tools", NULL, N_("_Tools") },
587 {"Help", NULL, N_("_Help") },
589 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
590 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
591 {"Message/---", NULL, "---" },
593 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
594 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
595 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
596 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
597 /* {"Message/---", NULL, "---" }, */
598 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
599 /* {"Message/---", NULL, "---" }, */
600 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
601 /* {"Message/---", NULL, "---" }, */
602 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
605 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
606 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
607 {"Edit/---", NULL, "---" },
609 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
610 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
611 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
613 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
614 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
615 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
616 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
618 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
620 {"Edit/Advanced", NULL, N_("A_dvanced") },
621 {"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*/
622 {"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*/
623 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
624 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
625 {"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*/
626 {"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*/
627 {"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*/
628 {"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*/
629 {"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*/
630 {"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*/
631 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
632 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
633 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
634 {"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*/
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
641 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
646 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
647 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
648 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
649 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
651 {"Spelling/---", NULL, "---" },
652 {"Spelling/Options", NULL, N_("_Options") },
657 {"Options/ReplyMode", NULL, N_("Reply _mode") },
658 {"Options/---", NULL, "---" },
659 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
660 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662 /* {"Options/---", NULL, "---" }, */
664 {"Options/Priority", NULL, N_("_Priority") },
666 {"Options/Encoding", NULL, N_("Character _encoding") },
667 {"Options/Encoding/---", NULL, "---" },
668 #define ENC_ACTION(cs_char,c_char,string) \
669 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
671 {"Options/Encoding/Western", NULL, N_("Western European") },
672 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
673 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
674 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
675 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
676 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
677 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
678 {"Options/Encoding/Korean", NULL, N_("Korean") },
679 {"Options/Encoding/Thai", NULL, N_("Thai") },
682 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
684 {"Tools/Template", NULL, N_("_Template") },
685 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
686 {"Tools/Actions", NULL, N_("Actio_ns") },
687 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
690 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
693 static GtkToggleActionEntry compose_toggle_entries[] =
695 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
696 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
697 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
698 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
699 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
700 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
701 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
704 static GtkRadioActionEntry compose_radio_rm_entries[] =
706 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
707 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
708 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
709 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
712 static GtkRadioActionEntry compose_radio_prio_entries[] =
714 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
716 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
717 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
718 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
721 static GtkRadioActionEntry compose_radio_enc_entries[] =
723 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
757 static GtkTargetEntry compose_mime_types[] =
759 {"text/uri-list", 0, 0},
760 {"UTF8_STRING", 0, 0},
764 static gboolean compose_put_existing_to_front(MsgInfo *info)
766 const GList *compose_list = compose_get_compose_list();
767 const GList *elem = NULL;
770 for (elem = compose_list; elem != NULL && elem->data != NULL;
772 Compose *c = (Compose*)elem->data;
774 if (!c->targetinfo || !c->targetinfo->msgid ||
778 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
779 gtkut_window_popup(c->window);
787 static GdkColor quote_color1 =
788 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
789 static GdkColor quote_color2 =
790 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_color3 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
794 static GdkColor quote_bgcolor1 =
795 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
796 static GdkColor quote_bgcolor2 =
797 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor quote_bgcolor3 =
799 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
801 static GdkColor signature_color = {
808 static GdkColor uri_color = {
815 static void compose_create_tags(GtkTextView *text, Compose *compose)
817 GtkTextBuffer *buffer;
818 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 #if !GTK_CHECK_VERSION(2, 24, 0)
826 buffer = gtk_text_view_get_buffer(text);
828 if (prefs_common.enable_color) {
829 /* grab the quote colors, converting from an int to a GdkColor */
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
836 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
838 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
840 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
842 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
844 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
847 signature_color = quote_color1 = quote_color2 = quote_color3 =
848 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
851 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
852 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
853 "foreground-gdk", "e_color1,
854 "paragraph-background-gdk", "e_bgcolor1,
856 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
857 "foreground-gdk", "e_color2,
858 "paragraph-background-gdk", "e_bgcolor2,
860 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
861 "foreground-gdk", "e_color3,
862 "paragraph-background-gdk", "e_bgcolor3,
865 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
866 "foreground-gdk", "e_color1,
868 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
869 "foreground-gdk", "e_color2,
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-gdk", "e_color3,
876 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
877 "foreground-gdk", &signature_color,
880 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
881 "foreground-gdk", &uri_color,
883 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
884 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
886 #if !GTK_CHECK_VERSION(2, 24, 0)
887 color[0] = quote_color1;
888 color[1] = quote_color2;
889 color[2] = quote_color3;
890 color[3] = quote_bgcolor1;
891 color[4] = quote_bgcolor2;
892 color[5] = quote_bgcolor3;
893 color[6] = signature_color;
894 color[7] = uri_color;
896 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
897 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
899 for (i = 0; i < 8; i++) {
900 if (success[i] == FALSE) {
901 g_warning("Compose: color allocation failed.\n");
902 quote_color1 = quote_color2 = quote_color3 =
903 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
904 signature_color = uri_color = black;
910 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
913 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
916 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
918 return compose_generic_new(account, mailto, item, NULL, NULL);
921 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
923 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
926 #define SCROLL_TO_CURSOR(compose) { \
927 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
928 gtk_text_view_get_buffer( \
929 GTK_TEXT_VIEW(compose->text))); \
930 gtk_text_view_scroll_mark_onscreen( \
931 GTK_TEXT_VIEW(compose->text), \
935 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
938 if (folderidentifier) {
939 #if !GTK_CHECK_VERSION(2, 24, 0)
940 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
942 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
944 prefs_common.compose_save_to_history = add_history(
945 prefs_common.compose_save_to_history, folderidentifier);
946 #if !GTK_CHECK_VERSION(2, 24, 0)
947 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
948 prefs_common.compose_save_to_history);
950 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
951 prefs_common.compose_save_to_history);
955 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
956 if (folderidentifier)
957 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
959 gtk_entry_set_text(GTK_ENTRY(entry), "");
962 static gchar *compose_get_save_to(Compose *compose)
965 gchar *result = NULL;
966 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
967 result = gtk_editable_get_chars(entry, 0, -1);
970 #if !GTK_CHECK_VERSION(2, 24, 0)
971 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
973 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
975 prefs_common.compose_save_to_history = add_history(
976 prefs_common.compose_save_to_history, result);
977 #if !GTK_CHECK_VERSION(2, 24, 0)
978 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
979 prefs_common.compose_save_to_history);
981 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
982 prefs_common.compose_save_to_history);
988 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
989 GList *attach_files, GList *listAddress )
992 GtkTextView *textview;
993 GtkTextBuffer *textbuf;
995 const gchar *subject_format = NULL;
996 const gchar *body_format = NULL;
997 gchar *mailto_from = NULL;
998 PrefsAccount *mailto_account = NULL;
999 MsgInfo* dummyinfo = NULL;
1000 gint cursor_pos = -1;
1001 MailField mfield = NO_FIELD_PRESENT;
1005 /* check if mailto defines a from */
1006 if (mailto && *mailto != '\0') {
1007 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1008 /* mailto defines a from, check if we can get account prefs from it,
1009 if not, the account prefs will be guessed using other ways, but we'll keep
1012 mailto_account = account_find_from_address(mailto_from, TRUE);
1013 if (mailto_account == NULL) {
1015 Xstrdup_a(tmp_from, mailto_from, return NULL);
1016 extract_address(tmp_from);
1017 mailto_account = account_find_from_address(tmp_from, TRUE);
1021 account = mailto_account;
1024 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1025 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1026 account = account_find_from_id(item->prefs->default_account);
1028 /* if no account prefs set, fallback to the current one */
1029 if (!account) account = cur_account;
1030 cm_return_val_if_fail(account != NULL, NULL);
1032 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1034 /* override from name if mailto asked for it */
1036 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1037 g_free(mailto_from);
1039 /* override from name according to folder properties */
1040 if (item && item->prefs &&
1041 item->prefs->compose_with_format &&
1042 item->prefs->compose_override_from_format &&
1043 *item->prefs->compose_override_from_format != '\0') {
1048 dummyinfo = compose_msginfo_new_from_compose(compose);
1050 /* decode \-escape sequences in the internal representation of the quote format */
1051 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1052 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1055 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1056 compose->gtkaspell);
1058 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1060 quote_fmt_scan_string(tmp);
1063 buf = quote_fmt_get_buffer();
1065 alertpanel_error(_("New message From format error."));
1067 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1068 quote_fmt_reset_vartable();
1073 compose->replyinfo = NULL;
1074 compose->fwdinfo = NULL;
1076 textview = GTK_TEXT_VIEW(compose->text);
1077 textbuf = gtk_text_view_get_buffer(textview);
1078 compose_create_tags(textview, compose);
1080 undo_block(compose->undostruct);
1082 compose_set_dictionaries_from_folder_prefs(compose, item);
1085 if (account->auto_sig)
1086 compose_insert_sig(compose, FALSE);
1087 gtk_text_buffer_get_start_iter(textbuf, &iter);
1088 gtk_text_buffer_place_cursor(textbuf, &iter);
1090 if (account->protocol != A_NNTP) {
1091 if (mailto && *mailto != '\0') {
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1095 compose_set_folder_prefs(compose, item, TRUE);
1097 if (item && item->ret_rcpt) {
1098 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1101 if (mailto && *mailto != '\0') {
1102 if (!strchr(mailto, '@'))
1103 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1105 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1106 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1107 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1108 mfield = TO_FIELD_PRESENT;
1111 * CLAWS: just don't allow return receipt request, even if the user
1112 * may want to send an email. simple but foolproof.
1114 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1116 compose_add_field_list( compose, listAddress );
1118 if (item && item->prefs && item->prefs->compose_with_format) {
1119 subject_format = item->prefs->compose_subject_format;
1120 body_format = item->prefs->compose_body_format;
1121 } else if (account->compose_with_format) {
1122 subject_format = account->compose_subject_format;
1123 body_format = account->compose_body_format;
1124 } else if (prefs_common.compose_with_format) {
1125 subject_format = prefs_common.compose_subject_format;
1126 body_format = prefs_common.compose_body_format;
1129 if (subject_format || body_format) {
1132 && *subject_format != '\0' )
1134 gchar *subject = NULL;
1139 dummyinfo = compose_msginfo_new_from_compose(compose);
1141 /* decode \-escape sequences in the internal representation of the quote format */
1142 tmp = g_malloc(strlen(subject_format)+1);
1143 pref_get_unescaped_pref(tmp, subject_format);
1145 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1148 compose->gtkaspell);
1150 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1152 quote_fmt_scan_string(tmp);
1155 buf = quote_fmt_get_buffer();
1157 alertpanel_error(_("New message subject format error."));
1159 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1160 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1161 quote_fmt_reset_vartable();
1165 mfield = SUBJECT_FIELD_PRESENT;
1169 && *body_format != '\0' )
1172 GtkTextBuffer *buffer;
1173 GtkTextIter start, end;
1177 dummyinfo = compose_msginfo_new_from_compose(compose);
1179 text = GTK_TEXT_VIEW(compose->text);
1180 buffer = gtk_text_view_get_buffer(text);
1181 gtk_text_buffer_get_start_iter(buffer, &start);
1182 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1183 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1185 compose_quote_fmt(compose, dummyinfo,
1187 NULL, tmp, FALSE, TRUE,
1188 _("The body of the \"New message\" template has an error at line %d."));
1189 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1190 quote_fmt_reset_vartable();
1194 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1195 gtkaspell_highlight_all(compose->gtkaspell);
1197 mfield = BODY_FIELD_PRESENT;
1201 procmsg_msginfo_free( dummyinfo );
1207 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1208 ainfo = (AttachInfo *) curr->data;
1209 compose_attach_append(compose, ainfo->file, ainfo->file,
1210 ainfo->content_type, ainfo->charset);
1214 compose_show_first_last_header(compose, TRUE);
1216 /* Set save folder */
1217 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1218 gchar *folderidentifier;
1220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1221 folderidentifier = folder_item_get_identifier(item);
1222 compose_set_save_to(compose, folderidentifier);
1223 g_free(folderidentifier);
1226 /* Place cursor according to provided input (mfield) */
1228 case NO_FIELD_PRESENT:
1229 if (compose->header_last)
1230 gtk_widget_grab_focus(compose->header_last->entry);
1232 case TO_FIELD_PRESENT:
1233 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1235 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1238 gtk_widget_grab_focus(compose->subject_entry);
1240 case SUBJECT_FIELD_PRESENT:
1241 textview = GTK_TEXT_VIEW(compose->text);
1244 textbuf = gtk_text_view_get_buffer(textview);
1247 mark = gtk_text_buffer_get_insert(textbuf);
1248 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1249 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1251 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1252 * only defers where it comes to the variable body
1253 * is not null. If no body is present compose->text
1254 * will be null in which case you cannot place the
1255 * cursor inside the component so. An empty component
1256 * is therefore created before placing the cursor
1258 case BODY_FIELD_PRESENT:
1259 cursor_pos = quote_fmt_get_cursor_pos();
1260 if (cursor_pos == -1)
1261 gtk_widget_grab_focus(compose->header_last->entry);
1263 gtk_widget_grab_focus(compose->text);
1267 undo_unblock(compose->undostruct);
1269 if (prefs_common.auto_exteditor)
1270 compose_exec_ext_editor(compose);
1272 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1274 SCROLL_TO_CURSOR(compose);
1276 compose->modified = FALSE;
1277 compose_set_title(compose);
1279 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1284 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1285 gboolean override_pref, const gchar *system)
1287 const gchar *privacy = NULL;
1289 cm_return_if_fail(compose != NULL);
1290 cm_return_if_fail(account != NULL);
1292 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1295 if (account->default_privacy_system && strlen(account->default_privacy_system))
1296 privacy = account->default_privacy_system;
1300 GSList *privacy_avail = privacy_get_system_ids();
1301 if (privacy_avail && g_slist_length(privacy_avail)) {
1302 privacy = (gchar *)(privacy_avail->data);
1305 if (privacy != NULL) {
1307 g_free(compose->privacy_system);
1308 compose->privacy_system = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 else if (*(compose->privacy_system) == '\0') {
1313 g_free(compose->privacy_system);
1314 compose->privacy_system = g_strdup(privacy);
1316 compose_update_privacy_system_menu_item(compose, FALSE);
1317 compose_use_encryption(compose, TRUE);
1321 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1323 const gchar *privacy = NULL;
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1341 if (compose->privacy_system == NULL)
1342 compose->privacy_system = g_strdup(privacy);
1343 compose_update_privacy_system_menu_item(compose, FALSE);
1344 compose_use_signing(compose, TRUE);
1348 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1352 Compose *compose = NULL;
1354 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1356 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1357 cm_return_val_if_fail(msginfo != NULL, NULL);
1359 list_len = g_slist_length(msginfo_list);
1363 case COMPOSE_REPLY_TO_ADDRESS:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1365 FALSE, prefs_common.default_reply_list, FALSE, body);
1367 case COMPOSE_REPLY_WITH_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1369 FALSE, prefs_common.default_reply_list, FALSE, body);
1371 case COMPOSE_REPLY_WITHOUT_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1373 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1375 case COMPOSE_REPLY_TO_SENDER:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, FALSE, TRUE, body);
1379 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1380 compose = compose_followup_and_reply_to(msginfo,
1381 COMPOSE_QUOTE_CHECK,
1382 FALSE, FALSE, body);
1384 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1386 FALSE, FALSE, TRUE, body);
1388 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1390 FALSE, FALSE, TRUE, NULL);
1392 case COMPOSE_REPLY_TO_ALL:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1394 TRUE, FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 TRUE, FALSE, FALSE, body);
1400 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 TRUE, FALSE, FALSE, NULL);
1404 case COMPOSE_REPLY_TO_LIST:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 FALSE, TRUE, FALSE, body);
1408 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 FALSE, TRUE, FALSE, body);
1412 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 FALSE, TRUE, FALSE, NULL);
1416 case COMPOSE_FORWARD:
1417 if (prefs_common.forward_as_attachment) {
1418 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1421 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1425 case COMPOSE_FORWARD_INLINE:
1426 /* check if we reply to more than one Message */
1427 if (list_len == 1) {
1428 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1431 /* more messages FALL THROUGH */
1432 case COMPOSE_FORWARD_AS_ATTACH:
1433 compose = compose_forward_multiple(NULL, msginfo_list);
1435 case COMPOSE_REDIRECT:
1436 compose = compose_redirect(NULL, msginfo, FALSE);
1439 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1442 if (compose == NULL) {
1443 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1447 compose->rmode = mode;
1448 switch (compose->rmode) {
1450 case COMPOSE_REPLY_WITH_QUOTE:
1451 case COMPOSE_REPLY_WITHOUT_QUOTE:
1452 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1453 debug_print("reply mode Normal\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1455 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1457 case COMPOSE_REPLY_TO_SENDER:
1458 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1459 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1460 debug_print("reply mode Sender\n");
1461 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1463 case COMPOSE_REPLY_TO_ALL:
1464 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1465 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1466 debug_print("reply mode All\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1469 case COMPOSE_REPLY_TO_LIST:
1470 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1472 debug_print("reply mode List\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1475 case COMPOSE_REPLY_TO_ADDRESS:
1476 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1484 static Compose *compose_reply(MsgInfo *msginfo,
1485 ComposeQuoteMode quote_mode,
1491 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1492 to_sender, FALSE, body);
1495 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1496 ComposeQuoteMode quote_mode,
1501 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1502 to_sender, TRUE, body);
1505 static void compose_extract_original_charset(Compose *compose)
1507 MsgInfo *info = NULL;
1508 if (compose->replyinfo) {
1509 info = compose->replyinfo;
1510 } else if (compose->fwdinfo) {
1511 info = compose->fwdinfo;
1512 } else if (compose->targetinfo) {
1513 info = compose->targetinfo;
1516 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1517 MimeInfo *partinfo = mimeinfo;
1518 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1519 partinfo = procmime_mimeinfo_next(partinfo);
1521 compose->orig_charset =
1522 g_strdup(procmime_mimeinfo_get_parameter(
1523 partinfo, "charset"));
1525 procmime_mimeinfo_free_all(mimeinfo);
1529 #define SIGNAL_BLOCK(buffer) { \
1530 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1533 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1538 #define SIGNAL_UNBLOCK(buffer) { \
1539 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1540 G_CALLBACK(compose_changed_cb), \
1542 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(text_inserted), \
1547 static Compose *compose_generic_reply(MsgInfo *msginfo,
1548 ComposeQuoteMode quote_mode,
1549 gboolean to_all, gboolean to_ml,
1551 gboolean followup_and_reply_to,
1555 PrefsAccount *account = NULL;
1556 GtkTextView *textview;
1557 GtkTextBuffer *textbuf;
1558 gboolean quote = FALSE;
1559 const gchar *qmark = NULL;
1560 const gchar *body_fmt = NULL;
1561 gchar *s_system = NULL;
1563 cm_return_val_if_fail(msginfo != NULL, NULL);
1564 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1566 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1568 cm_return_val_if_fail(account != NULL, NULL);
1570 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1572 compose->updating = TRUE;
1574 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1577 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1578 if (!compose->replyinfo)
1579 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1581 compose_extract_original_charset(compose);
1583 if (msginfo->folder && msginfo->folder->ret_rcpt)
1584 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1586 /* Set save folder */
1587 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1588 gchar *folderidentifier;
1590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1591 folderidentifier = folder_item_get_identifier(msginfo->folder);
1592 compose_set_save_to(compose, folderidentifier);
1593 g_free(folderidentifier);
1596 if (compose_parse_header(compose, msginfo) < 0) {
1597 compose->updating = FALSE;
1598 compose_destroy(compose);
1602 /* override from name according to folder properties */
1603 if (msginfo->folder && msginfo->folder->prefs &&
1604 msginfo->folder->prefs->reply_with_format &&
1605 msginfo->folder->prefs->reply_override_from_format &&
1606 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1611 /* decode \-escape sequences in the internal representation of the quote format */
1612 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1613 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1616 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1617 compose->gtkaspell);
1619 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1621 quote_fmt_scan_string(tmp);
1624 buf = quote_fmt_get_buffer();
1626 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1628 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1629 quote_fmt_reset_vartable();
1634 textview = (GTK_TEXT_VIEW(compose->text));
1635 textbuf = gtk_text_view_get_buffer(textview);
1636 compose_create_tags(textview, compose);
1638 undo_block(compose->undostruct);
1640 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1641 gtkaspell_block_check(compose->gtkaspell);
1644 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1645 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1646 /* use the reply format of folder (if enabled), or the account's one
1647 (if enabled) or fallback to the global reply format, which is always
1648 enabled (even if empty), and use the relevant quotemark */
1650 if (msginfo->folder && msginfo->folder->prefs &&
1651 msginfo->folder->prefs->reply_with_format) {
1652 qmark = msginfo->folder->prefs->reply_quotemark;
1653 body_fmt = msginfo->folder->prefs->reply_body_format;
1655 } else if (account->reply_with_format) {
1656 qmark = account->reply_quotemark;
1657 body_fmt = account->reply_body_format;
1660 qmark = prefs_common.quotemark;
1661 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1662 body_fmt = gettext(prefs_common.quotefmt);
1669 /* empty quotemark is not allowed */
1670 if (qmark == NULL || *qmark == '\0')
1672 compose_quote_fmt(compose, compose->replyinfo,
1673 body_fmt, qmark, body, FALSE, TRUE,
1674 _("The body of the \"Reply\" template has an error at line %d."));
1675 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1676 quote_fmt_reset_vartable();
1679 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1680 compose_force_encryption(compose, account, FALSE, s_system);
1683 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1684 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1685 compose_force_signing(compose, account, s_system);
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1748 !(account = compose_guess_forward_account_from_msginfo
1750 account = cur_account;
1752 if (!prefs_common.forward_as_attachment)
1753 mode = COMPOSE_FORWARD_INLINE;
1755 mode = COMPOSE_FORWARD;
1756 compose = compose_create(account, msginfo->folder, mode, batch);
1758 compose->updating = TRUE;
1759 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1760 if (!compose->fwdinfo)
1761 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1763 compose_extract_original_charset(compose);
1765 if (msginfo->subject && *msginfo->subject) {
1766 gchar *buf, *buf2, *p;
1768 buf = p = g_strdup(msginfo->subject);
1769 p += subject_get_prefix_length(p);
1770 memmove(buf, p, strlen(p) + 1);
1772 buf2 = g_strdup_printf("Fw: %s", buf);
1773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1779 /* override from name according to folder properties */
1780 if (msginfo->folder && msginfo->folder->prefs &&
1781 msginfo->folder->prefs->forward_with_format &&
1782 msginfo->folder->prefs->forward_override_from_format &&
1783 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1787 MsgInfo *full_msginfo = NULL;
1790 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1792 full_msginfo = procmsg_msginfo_copy(msginfo);
1794 /* decode \-escape sequences in the internal representation of the quote format */
1795 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1796 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1799 gtkaspell_block_check(compose->gtkaspell);
1800 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1801 compose->gtkaspell);
1803 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1805 quote_fmt_scan_string(tmp);
1808 buf = quote_fmt_get_buffer();
1810 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1812 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1813 quote_fmt_reset_vartable();
1816 procmsg_msginfo_free(full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file not exist\n", msgfile);
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1890 SIGNAL_UNBLOCK(textbuf);
1892 cursor_pos = quote_fmt_get_cursor_pos();
1893 if (cursor_pos == -1)
1894 gtk_widget_grab_focus(compose->header_last->entry);
1896 gtk_widget_grab_focus(compose->text);
1898 if (!no_extedit && prefs_common.auto_exteditor)
1899 compose_exec_ext_editor(compose);
1902 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1903 gchar *folderidentifier;
1905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1906 folderidentifier = folder_item_get_identifier(msginfo->folder);
1907 compose_set_save_to(compose, folderidentifier);
1908 g_free(folderidentifier);
1911 undo_unblock(compose->undostruct);
1913 compose->modified = FALSE;
1914 compose_set_title(compose);
1916 compose->updating = FALSE;
1917 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1918 SCROLL_TO_CURSOR(compose);
1920 if (compose->deferred_destroy) {
1921 compose_destroy(compose);
1925 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1930 #undef INSERT_FW_HEADER
1932 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1935 GtkTextView *textview;
1936 GtkTextBuffer *textbuf;
1940 gboolean single_mail = TRUE;
1942 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1944 if (g_slist_length(msginfo_list) > 1)
1945 single_mail = FALSE;
1947 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1948 if (((MsgInfo *)msginfo->data)->folder == NULL)
1951 /* guess account from first selected message */
1953 !(account = compose_guess_forward_account_from_msginfo
1954 (msginfo_list->data)))
1955 account = cur_account;
1957 cm_return_val_if_fail(account != NULL, NULL);
1959 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1960 if (msginfo->data) {
1961 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1962 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1966 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1967 g_warning("no msginfo_list");
1971 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1973 compose->updating = TRUE;
1975 /* override from name according to folder properties */
1976 if (msginfo_list->data) {
1977 MsgInfo *msginfo = msginfo_list->data;
1979 if (msginfo->folder && msginfo->folder->prefs &&
1980 msginfo->folder->prefs->forward_with_format &&
1981 msginfo->folder->prefs->forward_override_from_format &&
1982 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1987 /* decode \-escape sequences in the internal representation of the quote format */
1988 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1989 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1992 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1993 compose->gtkaspell);
1995 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1997 quote_fmt_scan_string(tmp);
2000 buf = quote_fmt_get_buffer();
2002 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2004 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2005 quote_fmt_reset_vartable();
2011 textview = GTK_TEXT_VIEW(compose->text);
2012 textbuf = gtk_text_view_get_buffer(textview);
2013 compose_create_tags(textview, compose);
2015 undo_block(compose->undostruct);
2016 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2017 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2019 if (!is_file_exist(msgfile))
2020 g_warning("%s: file not exist\n", msgfile);
2022 compose_attach_append(compose, msgfile, msgfile,
2023 "message/rfc822", NULL);
2028 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2029 if (info->subject && *info->subject) {
2030 gchar *buf, *buf2, *p;
2032 buf = p = g_strdup(info->subject);
2033 p += subject_get_prefix_length(p);
2034 memmove(buf, p, strlen(p) + 1);
2036 buf2 = g_strdup_printf("Fw: %s", buf);
2037 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2043 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2044 _("Fw: multiple emails"));
2047 SIGNAL_BLOCK(textbuf);
2049 if (account->auto_sig)
2050 compose_insert_sig(compose, FALSE);
2052 compose_wrap_all(compose);
2054 SIGNAL_UNBLOCK(textbuf);
2056 gtk_text_buffer_get_start_iter(textbuf, &iter);
2057 gtk_text_buffer_place_cursor(textbuf, &iter);
2059 gtk_widget_grab_focus(compose->header_last->entry);
2060 undo_unblock(compose->undostruct);
2061 compose->modified = FALSE;
2062 compose_set_title(compose);
2064 compose->updating = FALSE;
2065 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2066 SCROLL_TO_CURSOR(compose);
2068 if (compose->deferred_destroy) {
2069 compose_destroy(compose);
2073 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2078 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2080 GtkTextIter start = *iter;
2081 GtkTextIter end_iter;
2082 int start_pos = gtk_text_iter_get_offset(&start);
2084 if (!compose->account->sig_sep)
2087 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2088 start_pos+strlen(compose->account->sig_sep));
2090 /* check sig separator */
2091 str = gtk_text_iter_get_text(&start, &end_iter);
2092 if (!strcmp(str, compose->account->sig_sep)) {
2094 /* check end of line (\n) */
2095 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2096 start_pos+strlen(compose->account->sig_sep));
2097 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2098 start_pos+strlen(compose->account->sig_sep)+1);
2099 tmp = gtk_text_iter_get_text(&start, &end_iter);
2100 if (!strcmp(tmp,"\n")) {
2112 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2114 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2115 Compose *compose = (Compose *)data;
2116 FolderItem *old_item = NULL;
2117 FolderItem *new_item = NULL;
2118 gchar *old_id, *new_id;
2120 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2121 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2124 old_item = hookdata->item;
2125 new_item = hookdata->item2;
2127 old_id = folder_item_get_identifier(old_item);
2128 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2130 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2131 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->targetinfo->folder = new_item;
2135 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2136 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2137 compose->replyinfo->folder = new_item;
2140 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2141 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2142 compose->fwdinfo->folder = new_item;
2150 static void compose_colorize_signature(Compose *compose)
2152 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2154 GtkTextIter end_iter;
2155 gtk_text_buffer_get_start_iter(buffer, &iter);
2156 while (gtk_text_iter_forward_line(&iter))
2157 if (compose_is_sig_separator(compose, buffer, &iter)) {
2158 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2159 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2163 #define BLOCK_WRAP() { \
2164 prev_autowrap = compose->autowrap; \
2165 buffer = gtk_text_view_get_buffer( \
2166 GTK_TEXT_VIEW(compose->text)); \
2167 compose->autowrap = FALSE; \
2169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2170 G_CALLBACK(compose_changed_cb), \
2172 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2173 G_CALLBACK(text_inserted), \
2176 #define UNBLOCK_WRAP() { \
2177 compose->autowrap = prev_autowrap; \
2178 if (compose->autowrap) { \
2179 gint old = compose->draft_timeout_tag; \
2180 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2181 compose_wrap_all(compose); \
2182 compose->draft_timeout_tag = old; \
2185 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(compose_changed_cb), \
2188 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2189 G_CALLBACK(text_inserted), \
2193 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2195 Compose *compose = NULL;
2196 PrefsAccount *account = NULL;
2197 GtkTextView *textview;
2198 GtkTextBuffer *textbuf;
2202 gchar buf[BUFFSIZE];
2203 gboolean use_signing = FALSE;
2204 gboolean use_encryption = FALSE;
2205 gchar *privacy_system = NULL;
2206 int priority = PRIORITY_NORMAL;
2207 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2208 gboolean autowrap = prefs_common.autowrap;
2209 gboolean autoindent = prefs_common.auto_indent;
2210 HeaderEntry *manual_headers = NULL;
2212 cm_return_val_if_fail(msginfo != NULL, NULL);
2213 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2215 if (compose_put_existing_to_front(msginfo)) {
2219 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2220 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2221 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2222 gchar queueheader_buf[BUFFSIZE];
2225 /* Select Account from queue headers */
2226 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2227 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2228 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2229 account = account_find_from_id(id);
2231 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2232 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2233 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2234 account = account_find_from_id(id);
2236 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2237 sizeof(queueheader_buf), "NAID:")) {
2238 id = atoi(&queueheader_buf[strlen("NAID:")]);
2239 account = account_find_from_id(id);
2241 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "MAID:")) {
2243 id = atoi(&queueheader_buf[strlen("MAID:")]);
2244 account = account_find_from_id(id);
2246 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2247 sizeof(queueheader_buf), "S:")) {
2248 account = account_find_from_address(queueheader_buf, FALSE);
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2251 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2252 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2253 use_signing = param;
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2258 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2259 use_signing = param;
2262 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2263 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2264 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2265 use_encryption = param;
2267 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2268 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2269 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2270 use_encryption = param;
2272 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2273 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2277 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2278 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2284 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2286 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2287 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2288 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2290 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2291 sizeof(queueheader_buf), "X-Priority: ")) {
2292 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2295 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2296 sizeof(queueheader_buf), "RMID:")) {
2297 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2298 if (tokens[0] && tokens[1] && tokens[2]) {
2299 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2300 if (orig_item != NULL) {
2301 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "FMID:")) {
2308 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2309 if (tokens[0] && tokens[1] && tokens[2]) {
2310 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2311 if (orig_item != NULL) {
2312 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2317 /* Get manual headers */
2318 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2319 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2320 if (*listmh != '\0') {
2321 debug_print("Got manual headers: %s\n", listmh);
2322 manual_headers = procheader_entries_from_str(listmh);
2327 account = msginfo->folder->folder->account;
2330 if (!account && prefs_common.reedit_account_autosel) {
2331 gchar from[BUFFSIZE];
2332 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2333 extract_address(from);
2334 account = account_find_from_address(from, FALSE);
2338 account = cur_account;
2340 cm_return_val_if_fail(account != NULL, NULL);
2342 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2344 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2345 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2346 compose->autowrap = autowrap;
2347 compose->replyinfo = replyinfo;
2348 compose->fwdinfo = fwdinfo;
2350 compose->updating = TRUE;
2351 compose->priority = priority;
2353 if (privacy_system != NULL) {
2354 compose->privacy_system = privacy_system;
2355 compose_use_signing(compose, use_signing);
2356 compose_use_encryption(compose, use_encryption);
2357 compose_update_privacy_system_menu_item(compose, FALSE);
2359 activate_privacy_system(compose, account, FALSE);
2362 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2364 compose_extract_original_charset(compose);
2366 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2367 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2368 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2369 gchar queueheader_buf[BUFFSIZE];
2371 /* Set message save folder */
2372 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2373 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2374 compose_set_save_to(compose, &queueheader_buf[4]);
2376 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2377 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2384 if (compose_parse_header(compose, msginfo) < 0) {
2385 compose->updating = FALSE;
2386 compose_destroy(compose);
2389 compose_reedit_set_entry(compose, msginfo);
2391 textview = GTK_TEXT_VIEW(compose->text);
2392 textbuf = gtk_text_view_get_buffer(textview);
2393 compose_create_tags(textview, compose);
2395 mark = gtk_text_buffer_get_insert(textbuf);
2396 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2398 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2399 G_CALLBACK(compose_changed_cb),
2402 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2403 fp = procmime_get_first_encrypted_text_content(msginfo);
2405 compose_force_encryption(compose, account, TRUE, NULL);
2408 fp = procmime_get_first_text_content(msginfo);
2411 g_warning("Can't get text part\n");
2415 gboolean prev_autowrap;
2416 GtkTextBuffer *buffer;
2418 while (fgets(buf, sizeof(buf), fp) != NULL) {
2420 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2426 compose_attach_parts(compose, msginfo);
2428 compose_colorize_signature(compose);
2430 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2431 G_CALLBACK(compose_changed_cb),
2434 if (manual_headers != NULL) {
2435 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2436 procheader_entries_free(manual_headers);
2437 compose->updating = FALSE;
2438 compose_destroy(compose);
2441 procheader_entries_free(manual_headers);
2444 gtk_widget_grab_focus(compose->text);
2446 if (prefs_common.auto_exteditor) {
2447 compose_exec_ext_editor(compose);
2449 compose->modified = FALSE;
2450 compose_set_title(compose);
2452 compose->updating = FALSE;
2453 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2454 SCROLL_TO_CURSOR(compose);
2456 if (compose->deferred_destroy) {
2457 compose_destroy(compose);
2461 compose->sig_str = account_get_signature_str(compose->account);
2463 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2468 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2475 cm_return_val_if_fail(msginfo != NULL, NULL);
2478 account = account_get_reply_account(msginfo,
2479 prefs_common.reply_account_autosel);
2480 cm_return_val_if_fail(account != NULL, NULL);
2482 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2484 compose->updating = TRUE;
2486 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2487 compose->replyinfo = NULL;
2488 compose->fwdinfo = NULL;
2490 compose_show_first_last_header(compose, TRUE);
2492 gtk_widget_grab_focus(compose->header_last->entry);
2494 filename = procmsg_get_message_file(msginfo);
2496 if (filename == NULL) {
2497 compose->updating = FALSE;
2498 compose_destroy(compose);
2503 compose->redirect_filename = filename;
2505 /* Set save folder */
2506 item = msginfo->folder;
2507 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2508 gchar *folderidentifier;
2510 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2511 folderidentifier = folder_item_get_identifier(item);
2512 compose_set_save_to(compose, folderidentifier);
2513 g_free(folderidentifier);
2516 compose_attach_parts(compose, msginfo);
2518 if (msginfo->subject)
2519 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2521 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2523 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2524 _("The body of the \"Redirect\" template has an error at line %d."));
2525 quote_fmt_reset_vartable();
2526 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2528 compose_colorize_signature(compose);
2531 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2532 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2533 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2543 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2545 if (compose->toolbar->draft_btn)
2546 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2547 if (compose->toolbar->insert_btn)
2548 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2549 if (compose->toolbar->attach_btn)
2550 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2551 if (compose->toolbar->sig_btn)
2552 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2553 if (compose->toolbar->exteditor_btn)
2554 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2555 if (compose->toolbar->linewrap_current_btn)
2556 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2557 if (compose->toolbar->linewrap_all_btn)
2558 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2560 compose->modified = FALSE;
2561 compose_set_title(compose);
2562 compose->updating = FALSE;
2563 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2564 SCROLL_TO_CURSOR(compose);
2566 if (compose->deferred_destroy) {
2567 compose_destroy(compose);
2571 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2576 const GList *compose_get_compose_list(void)
2578 return compose_list;
2581 void compose_entry_append(Compose *compose, const gchar *address,
2582 ComposeEntryType type, ComposePrefType pref_type)
2584 const gchar *header;
2586 gboolean in_quote = FALSE;
2587 if (!address || *address == '\0') return;
2594 header = N_("Bcc:");
2596 case COMPOSE_REPLYTO:
2597 header = N_("Reply-To:");
2599 case COMPOSE_NEWSGROUPS:
2600 header = N_("Newsgroups:");
2602 case COMPOSE_FOLLOWUPTO:
2603 header = N_( "Followup-To:");
2605 case COMPOSE_INREPLYTO:
2606 header = N_( "In-Reply-To:");
2613 header = prefs_common_translated_header_name(header);
2615 cur = begin = (gchar *)address;
2617 /* we separate the line by commas, but not if we're inside a quoted
2619 while (*cur != '\0') {
2621 in_quote = !in_quote;
2622 if (*cur == ',' && !in_quote) {
2623 gchar *tmp = g_strdup(begin);
2625 tmp[cur-begin]='\0';
2628 while (*tmp == ' ' || *tmp == '\t')
2630 compose_add_header_entry(compose, header, tmp, pref_type);
2637 gchar *tmp = g_strdup(begin);
2639 tmp[cur-begin]='\0';
2640 while (*tmp == ' ' || *tmp == '\t')
2642 compose_add_header_entry(compose, header, tmp, pref_type);
2647 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2649 #if !GTK_CHECK_VERSION(3, 0, 0)
2650 static GdkColor yellow;
2651 static GdkColor black;
2652 static gboolean yellow_initialised = FALSE;
2654 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2655 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2660 #if !GTK_CHECK_VERSION(3, 0, 0)
2661 if (!yellow_initialised) {
2662 gdk_color_parse("#f5f6be", &yellow);
2663 gdk_color_parse("#000000", &black);
2664 yellow_initialised = gdk_colormap_alloc_color(
2665 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2666 yellow_initialised &= gdk_colormap_alloc_color(
2667 gdk_colormap_get_system(), &black, FALSE, TRUE);
2671 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2672 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2673 if (gtk_entry_get_text(entry) &&
2674 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (yellow_initialised) {
2678 gtk_widget_modify_base(
2679 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2680 GTK_STATE_NORMAL, &yellow);
2681 gtk_widget_modify_text(
2682 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2683 GTK_STATE_NORMAL, &black);
2684 #if !GTK_CHECK_VERSION(3, 0, 0)
2691 void compose_toolbar_cb(gint action, gpointer data)
2693 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2694 Compose *compose = (Compose*)toolbar_item->parent;
2696 cm_return_if_fail(compose != NULL);
2700 compose_send_cb(NULL, compose);
2703 compose_send_later_cb(NULL, compose);
2706 compose_draft(compose, COMPOSE_QUIT_EDITING);
2709 compose_insert_file_cb(NULL, compose);
2712 compose_attach_cb(NULL, compose);
2715 compose_insert_sig(compose, FALSE);
2718 compose_insert_sig(compose, TRUE);
2721 compose_ext_editor_cb(NULL, compose);
2723 case A_LINEWRAP_CURRENT:
2724 compose_beautify_paragraph(compose, NULL, TRUE);
2726 case A_LINEWRAP_ALL:
2727 compose_wrap_all_full(compose, TRUE);
2730 compose_address_cb(NULL, compose);
2733 case A_CHECK_SPELLING:
2734 compose_check_all(NULL, compose);
2742 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2747 gchar *subject = NULL;
2751 gchar **attach = NULL;
2752 gchar *inreplyto = NULL;
2753 MailField mfield = NO_FIELD_PRESENT;
2755 /* get mailto parts but skip from */
2756 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2759 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2760 mfield = TO_FIELD_PRESENT;
2763 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2765 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2767 if (!g_utf8_validate (subject, -1, NULL)) {
2768 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2774 mfield = SUBJECT_FIELD_PRESENT;
2777 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2778 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2781 gboolean prev_autowrap = compose->autowrap;
2783 compose->autowrap = FALSE;
2785 mark = gtk_text_buffer_get_insert(buffer);
2786 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2788 if (!g_utf8_validate (body, -1, NULL)) {
2789 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2790 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2793 gtk_text_buffer_insert(buffer, &iter, body, -1);
2795 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2797 compose->autowrap = prev_autowrap;
2798 if (compose->autowrap)
2799 compose_wrap_all(compose);
2800 mfield = BODY_FIELD_PRESENT;
2804 gint i = 0, att = 0;
2805 gchar *warn_files = NULL;
2806 while (attach[i] != NULL) {
2807 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2808 if (utf8_filename) {
2809 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2810 gchar *tmp = g_strdup_printf("%s%s\n",
2811 warn_files?warn_files:"",
2817 g_free(utf8_filename);
2819 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2824 alertpanel_notice(ngettext(
2825 "The following file has been attached: \n%s",
2826 "The following files have been attached: \n%s", att), warn_files);
2831 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2844 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2846 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2847 {"Cc:", NULL, TRUE},
2848 {"References:", NULL, FALSE},
2849 {"Bcc:", NULL, TRUE},
2850 {"Newsgroups:", NULL, TRUE},
2851 {"Followup-To:", NULL, TRUE},
2852 {"List-Post:", NULL, FALSE},
2853 {"X-Priority:", NULL, FALSE},
2854 {NULL, NULL, FALSE}};
2870 cm_return_val_if_fail(msginfo != NULL, -1);
2872 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2873 procheader_get_header_fields(fp, hentry);
2876 if (hentry[H_REPLY_TO].body != NULL) {
2877 if (hentry[H_REPLY_TO].body[0] != '\0') {
2879 conv_unmime_header(hentry[H_REPLY_TO].body,
2882 g_free(hentry[H_REPLY_TO].body);
2883 hentry[H_REPLY_TO].body = NULL;
2885 if (hentry[H_CC].body != NULL) {
2886 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2887 g_free(hentry[H_CC].body);
2888 hentry[H_CC].body = NULL;
2890 if (hentry[H_REFERENCES].body != NULL) {
2891 if (compose->mode == COMPOSE_REEDIT)
2892 compose->references = hentry[H_REFERENCES].body;
2894 compose->references = compose_parse_references
2895 (hentry[H_REFERENCES].body, msginfo->msgid);
2896 g_free(hentry[H_REFERENCES].body);
2898 hentry[H_REFERENCES].body = NULL;
2900 if (hentry[H_BCC].body != NULL) {
2901 if (compose->mode == COMPOSE_REEDIT)
2903 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2904 g_free(hentry[H_BCC].body);
2905 hentry[H_BCC].body = NULL;
2907 if (hentry[H_NEWSGROUPS].body != NULL) {
2908 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2909 hentry[H_NEWSGROUPS].body = NULL;
2911 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2912 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2913 compose->followup_to =
2914 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2917 g_free(hentry[H_FOLLOWUP_TO].body);
2918 hentry[H_FOLLOWUP_TO].body = NULL;
2920 if (hentry[H_LIST_POST].body != NULL) {
2921 gchar *to = NULL, *start = NULL;
2923 extract_address(hentry[H_LIST_POST].body);
2924 if (hentry[H_LIST_POST].body[0] != '\0') {
2925 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2927 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2928 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2931 g_free(compose->ml_post);
2932 compose->ml_post = to;
2935 g_free(hentry[H_LIST_POST].body);
2936 hentry[H_LIST_POST].body = NULL;
2939 /* CLAWS - X-Priority */
2940 if (compose->mode == COMPOSE_REEDIT)
2941 if (hentry[H_X_PRIORITY].body != NULL) {
2944 priority = atoi(hentry[H_X_PRIORITY].body);
2945 g_free(hentry[H_X_PRIORITY].body);
2947 hentry[H_X_PRIORITY].body = NULL;
2949 if (priority < PRIORITY_HIGHEST ||
2950 priority > PRIORITY_LOWEST)
2951 priority = PRIORITY_NORMAL;
2953 compose->priority = priority;
2956 if (compose->mode == COMPOSE_REEDIT) {
2957 if (msginfo->inreplyto && *msginfo->inreplyto)
2958 compose->inreplyto = g_strdup(msginfo->inreplyto);
2962 if (msginfo->msgid && *msginfo->msgid)
2963 compose->inreplyto = g_strdup(msginfo->msgid);
2965 if (!compose->references) {
2966 if (msginfo->msgid && *msginfo->msgid) {
2967 if (msginfo->inreplyto && *msginfo->inreplyto)
2968 compose->references =
2969 g_strdup_printf("<%s>\n\t<%s>",
2973 compose->references =
2974 g_strconcat("<", msginfo->msgid, ">",
2976 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2977 compose->references =
2978 g_strconcat("<", msginfo->inreplyto, ">",
2986 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2991 cm_return_val_if_fail(msginfo != NULL, -1);
2993 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2994 procheader_get_header_fields(fp, entries);
2998 while (he != NULL && he->name != NULL) {
3000 GtkListStore *model = NULL;
3002 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3003 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3004 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3005 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3006 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3013 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3015 GSList *ref_id_list, *cur;
3019 ref_id_list = references_list_append(NULL, ref);
3020 if (!ref_id_list) return NULL;
3021 if (msgid && *msgid)
3022 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3027 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3028 /* "<" + Message-ID + ">" + CR+LF+TAB */
3029 len += strlen((gchar *)cur->data) + 5;
3031 if (len > MAX_REFERENCES_LEN) {
3032 /* remove second message-ID */
3033 if (ref_id_list && ref_id_list->next &&
3034 ref_id_list->next->next) {
3035 g_free(ref_id_list->next->data);
3036 ref_id_list = g_slist_remove
3037 (ref_id_list, ref_id_list->next->data);
3039 slist_free_strings_full(ref_id_list);
3046 new_ref = g_string_new("");
3047 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3048 if (new_ref->len > 0)
3049 g_string_append(new_ref, "\n\t");
3050 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3053 slist_free_strings_full(ref_id_list);
3055 new_ref_str = new_ref->str;
3056 g_string_free(new_ref, FALSE);
3061 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3062 const gchar *fmt, const gchar *qmark,
3063 const gchar *body, gboolean rewrap,
3064 gboolean need_unescape,
3065 const gchar *err_msg)
3067 MsgInfo* dummyinfo = NULL;
3068 gchar *quote_str = NULL;
3070 gboolean prev_autowrap;
3071 const gchar *trimmed_body = body;
3072 gint cursor_pos = -1;
3073 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3074 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3079 SIGNAL_BLOCK(buffer);
3082 dummyinfo = compose_msginfo_new_from_compose(compose);
3083 msginfo = dummyinfo;
3086 if (qmark != NULL) {
3088 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3089 compose->gtkaspell);
3091 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3093 quote_fmt_scan_string(qmark);
3096 buf = quote_fmt_get_buffer();
3098 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3100 Xstrdup_a(quote_str, buf, goto error)
3103 if (fmt && *fmt != '\0') {
3106 while (*trimmed_body == '\n')
3110 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3111 compose->gtkaspell);
3113 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3115 if (need_unescape) {
3118 /* decode \-escape sequences in the internal representation of the quote format */
3119 tmp = g_malloc(strlen(fmt)+1);
3120 pref_get_unescaped_pref(tmp, fmt);
3121 quote_fmt_scan_string(tmp);
3125 quote_fmt_scan_string(fmt);
3129 buf = quote_fmt_get_buffer();
3131 gint line = quote_fmt_get_line();
3132 alertpanel_error(err_msg, line);
3138 prev_autowrap = compose->autowrap;
3139 compose->autowrap = FALSE;
3141 mark = gtk_text_buffer_get_insert(buffer);
3142 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3143 if (g_utf8_validate(buf, -1, NULL)) {
3144 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3146 gchar *tmpout = NULL;
3147 tmpout = conv_codeset_strdup
3148 (buf, conv_get_locale_charset_str_no_utf8(),
3150 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3152 tmpout = g_malloc(strlen(buf)*2+1);
3153 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3155 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3159 cursor_pos = quote_fmt_get_cursor_pos();
3160 if (cursor_pos == -1)
3161 cursor_pos = gtk_text_iter_get_offset(&iter);
3162 compose->set_cursor_pos = cursor_pos;
3164 gtk_text_buffer_get_start_iter(buffer, &iter);
3165 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3166 gtk_text_buffer_place_cursor(buffer, &iter);
3168 compose->autowrap = prev_autowrap;
3169 if (compose->autowrap && rewrap)
3170 compose_wrap_all(compose);
3177 SIGNAL_UNBLOCK(buffer);
3179 procmsg_msginfo_free( dummyinfo );
3184 /* if ml_post is of type addr@host and from is of type
3185 * addr-anything@host, return TRUE
3187 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3189 gchar *left_ml = NULL;
3190 gchar *right_ml = NULL;
3191 gchar *left_from = NULL;
3192 gchar *right_from = NULL;
3193 gboolean result = FALSE;
3195 if (!ml_post || !from)
3198 left_ml = g_strdup(ml_post);
3199 if (strstr(left_ml, "@")) {
3200 right_ml = strstr(left_ml, "@")+1;
3201 *(strstr(left_ml, "@")) = '\0';
3204 left_from = g_strdup(from);
3205 if (strstr(left_from, "@")) {
3206 right_from = strstr(left_from, "@")+1;
3207 *(strstr(left_from, "@")) = '\0';
3210 if (left_ml && left_from && right_ml && right_from
3211 && !strncmp(left_from, left_ml, strlen(left_ml))
3212 && !strcmp(right_from, right_ml)) {
3221 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3222 gboolean respect_default_to)
3226 if (!folder || !folder->prefs)
3229 if (respect_default_to && folder->prefs->enable_default_to) {
3230 compose_entry_append(compose, folder->prefs->default_to,
3231 COMPOSE_TO, PREF_FOLDER);
3232 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3234 if (folder->prefs->enable_default_cc)
3235 compose_entry_append(compose, folder->prefs->default_cc,
3236 COMPOSE_CC, PREF_FOLDER);
3237 if (folder->prefs->enable_default_bcc)
3238 compose_entry_append(compose, folder->prefs->default_bcc,
3239 COMPOSE_BCC, PREF_FOLDER);
3240 if (folder->prefs->enable_default_replyto)
3241 compose_entry_append(compose, folder->prefs->default_replyto,
3242 COMPOSE_REPLYTO, PREF_FOLDER);
3245 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3250 if (!compose || !msginfo)
3253 if (msginfo->subject && *msginfo->subject) {
3254 buf = p = g_strdup(msginfo->subject);
3255 p += subject_get_prefix_length(p);
3256 memmove(buf, p, strlen(p) + 1);
3258 buf2 = g_strdup_printf("Re: %s", buf);
3259 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3264 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3267 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3268 gboolean to_all, gboolean to_ml,
3270 gboolean followup_and_reply_to)
3272 GSList *cc_list = NULL;
3275 gchar *replyto = NULL;
3276 gchar *ac_email = NULL;
3278 gboolean reply_to_ml = FALSE;
3279 gboolean default_reply_to = FALSE;
3281 cm_return_if_fail(compose->account != NULL);
3282 cm_return_if_fail(msginfo != NULL);
3284 reply_to_ml = to_ml && compose->ml_post;
3286 default_reply_to = msginfo->folder &&
3287 msginfo->folder->prefs->enable_default_reply_to;
3289 if (compose->account->protocol != A_NNTP) {
3290 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3292 if (reply_to_ml && !default_reply_to) {
3294 gboolean is_subscr = is_subscription(compose->ml_post,
3297 /* normal answer to ml post with a reply-to */
3298 compose_entry_append(compose,
3300 COMPOSE_TO, PREF_ML);
3301 if (compose->replyto)
3302 compose_entry_append(compose,
3304 COMPOSE_CC, PREF_ML);
3306 /* answer to subscription confirmation */
3307 if (compose->replyto)
3308 compose_entry_append(compose,
3310 COMPOSE_TO, PREF_ML);
3311 else if (msginfo->from)
3312 compose_entry_append(compose,
3314 COMPOSE_TO, PREF_ML);
3317 else if (!(to_all || to_sender) && default_reply_to) {
3318 compose_entry_append(compose,
3319 msginfo->folder->prefs->default_reply_to,
3320 COMPOSE_TO, PREF_FOLDER);
3321 compose_entry_mark_default_to(compose,
3322 msginfo->folder->prefs->default_reply_to);
3328 compose_entry_append(compose, msginfo->from,
3329 COMPOSE_TO, PREF_NONE);
3331 Xstrdup_a(tmp1, msginfo->from, return);
3332 extract_address(tmp1);
3333 compose_entry_append(compose,
3334 (!account_find_from_address(tmp1, FALSE))
3337 COMPOSE_TO, PREF_NONE);
3339 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3340 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3341 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3342 if (compose->replyto) {
3343 compose_entry_append(compose,
3345 COMPOSE_TO, PREF_NONE);
3347 compose_entry_append(compose,
3348 msginfo->from ? msginfo->from : "",
3349 COMPOSE_TO, PREF_NONE);
3352 /* replying to own mail, use original recp */
3353 compose_entry_append(compose,
3354 msginfo->to ? msginfo->to : "",
3355 COMPOSE_TO, PREF_NONE);
3356 compose_entry_append(compose,
3357 msginfo->cc ? msginfo->cc : "",
3358 COMPOSE_CC, PREF_NONE);
3363 if (to_sender || (compose->followup_to &&
3364 !strncmp(compose->followup_to, "poster", 6)))
3365 compose_entry_append
3367 (compose->replyto ? compose->replyto :
3368 msginfo->from ? msginfo->from : ""),
3369 COMPOSE_TO, PREF_NONE);
3371 else if (followup_and_reply_to || to_all) {
3372 compose_entry_append
3374 (compose->replyto ? compose->replyto :
3375 msginfo->from ? msginfo->from : ""),
3376 COMPOSE_TO, PREF_NONE);
3378 compose_entry_append
3380 compose->followup_to ? compose->followup_to :
3381 compose->newsgroups ? compose->newsgroups : "",
3382 COMPOSE_NEWSGROUPS, PREF_NONE);
3385 compose_entry_append
3387 compose->followup_to ? compose->followup_to :
3388 compose->newsgroups ? compose->newsgroups : "",
3389 COMPOSE_NEWSGROUPS, PREF_NONE);
3391 compose_reply_set_subject(compose, msginfo);
3393 if (to_ml && compose->ml_post) return;
3394 if (!to_all || compose->account->protocol == A_NNTP) return;
3396 if (compose->replyto) {
3397 Xstrdup_a(replyto, compose->replyto, return);
3398 extract_address(replyto);
3400 if (msginfo->from) {
3401 Xstrdup_a(from, msginfo->from, return);
3402 extract_address(from);
3405 if (replyto && from)
3406 cc_list = address_list_append_with_comments(cc_list, from);
3407 if (to_all && msginfo->folder &&
3408 msginfo->folder->prefs->enable_default_reply_to)
3409 cc_list = address_list_append_with_comments(cc_list,
3410 msginfo->folder->prefs->default_reply_to);
3411 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3412 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3414 ac_email = g_utf8_strdown(compose->account->address, -1);
3417 for (cur = cc_list; cur != NULL; cur = cur->next) {
3418 gchar *addr = g_utf8_strdown(cur->data, -1);
3419 extract_address(addr);
3421 if (strcmp(ac_email, addr))
3422 compose_entry_append(compose, (gchar *)cur->data,
3423 COMPOSE_CC, PREF_NONE);
3425 debug_print("Cc address same as compose account's, ignoring\n");
3430 slist_free_strings_full(cc_list);
3436 #define SET_ENTRY(entry, str) \
3439 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3442 #define SET_ADDRESS(type, str) \
3445 compose_entry_append(compose, str, type, PREF_NONE); \
3448 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3450 cm_return_if_fail(msginfo != NULL);
3452 SET_ENTRY(subject_entry, msginfo->subject);
3453 SET_ENTRY(from_name, msginfo->from);
3454 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3455 SET_ADDRESS(COMPOSE_CC, compose->cc);
3456 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3457 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3458 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3459 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3461 compose_update_priority_menu_item(compose);
3462 compose_update_privacy_system_menu_item(compose, FALSE);
3463 compose_show_first_last_header(compose, TRUE);
3469 static void compose_insert_sig(Compose *compose, gboolean replace)
3471 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3472 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3474 GtkTextIter iter, iter_end;
3475 gint cur_pos, ins_pos;
3476 gboolean prev_autowrap;
3477 gboolean found = FALSE;
3478 gboolean exists = FALSE;
3480 cm_return_if_fail(compose->account != NULL);
3484 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3485 G_CALLBACK(compose_changed_cb),
3488 mark = gtk_text_buffer_get_insert(buffer);
3489 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3490 cur_pos = gtk_text_iter_get_offset (&iter);
3493 gtk_text_buffer_get_end_iter(buffer, &iter);
3495 exists = (compose->sig_str != NULL);
3498 GtkTextIter first_iter, start_iter, end_iter;
3500 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3502 if (!exists || compose->sig_str[0] == '\0')
3505 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3506 compose->signature_tag);
3509 /* include previous \n\n */
3510 gtk_text_iter_backward_chars(&first_iter, 1);
3511 start_iter = first_iter;
3512 end_iter = first_iter;
3514 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3515 compose->signature_tag);
3516 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3517 compose->signature_tag);
3519 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3525 g_free(compose->sig_str);
3526 compose->sig_str = account_get_signature_str(compose->account);
3528 cur_pos = gtk_text_iter_get_offset(&iter);
3530 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3531 g_free(compose->sig_str);
3532 compose->sig_str = NULL;
3534 if (compose->sig_inserted == FALSE)
3535 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3536 compose->sig_inserted = TRUE;
3538 cur_pos = gtk_text_iter_get_offset(&iter);
3539 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3541 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3542 gtk_text_iter_forward_chars(&iter, 1);
3543 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3544 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3546 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3547 cur_pos = gtk_text_buffer_get_char_count (buffer);
3550 /* put the cursor where it should be
3551 * either where the quote_fmt says, either where it was */
3552 if (compose->set_cursor_pos < 0)
3553 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3555 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3556 compose->set_cursor_pos);
3558 compose->set_cursor_pos = -1;
3559 gtk_text_buffer_place_cursor(buffer, &iter);
3560 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3561 G_CALLBACK(compose_changed_cb),
3567 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3570 GtkTextBuffer *buffer;
3573 const gchar *cur_encoding;
3574 gchar buf[BUFFSIZE];
3577 gboolean prev_autowrap;
3578 gboolean badtxt = FALSE;
3579 struct stat file_stat;
3581 GString *file_contents = NULL;
3583 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3585 /* get the size of the file we are about to insert */
3586 ret = g_stat(file, &file_stat);
3588 gchar *shortfile = g_path_get_basename(file);
3589 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3591 return COMPOSE_INSERT_NO_FILE;
3592 } else if (prefs_common.warn_large_insert == TRUE) {
3594 /* ask user for confirmation if the file is large */
3595 if (prefs_common.warn_large_insert_size < 0 ||
3596 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3600 msg = g_strdup_printf(_("You are about to insert a file of %s "
3601 "in the message body. Are you sure you want to do that?"),
3602 to_human_readable(file_stat.st_size));
3603 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3604 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3607 /* do we ask for confirmation next time? */
3608 if (aval & G_ALERTDISABLE) {
3609 /* no confirmation next time, disable feature in preferences */
3610 aval &= ~G_ALERTDISABLE;
3611 prefs_common.warn_large_insert = FALSE;
3614 /* abort file insertion if user canceled action */
3615 if (aval != G_ALERTALTERNATE) {
3616 return COMPOSE_INSERT_NO_FILE;
3622 if ((fp = g_fopen(file, "rb")) == NULL) {
3623 FILE_OP_ERROR(file, "fopen");
3624 return COMPOSE_INSERT_READ_ERROR;
3627 prev_autowrap = compose->autowrap;
3628 compose->autowrap = FALSE;
3630 text = GTK_TEXT_VIEW(compose->text);
3631 buffer = gtk_text_view_get_buffer(text);
3632 mark = gtk_text_buffer_get_insert(buffer);
3633 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3635 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3636 G_CALLBACK(text_inserted),
3639 cur_encoding = conv_get_locale_charset_str_no_utf8();
3641 file_contents = g_string_new("");
3642 while (fgets(buf, sizeof(buf), fp) != NULL) {
3645 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3646 str = g_strdup(buf);
3648 str = conv_codeset_strdup
3649 (buf, cur_encoding, CS_INTERNAL);
3652 /* strip <CR> if DOS/Windows file,
3653 replace <CR> with <LF> if Macintosh file. */
3656 if (len > 0 && str[len - 1] != '\n') {
3658 if (str[len] == '\r') str[len] = '\n';
3661 file_contents = g_string_append(file_contents, str);
3665 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3666 g_string_free(file_contents, TRUE);
3668 compose_changed_cb(NULL, compose);
3669 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3670 G_CALLBACK(text_inserted),
3672 compose->autowrap = prev_autowrap;
3673 if (compose->autowrap)
3674 compose_wrap_all(compose);
3679 return COMPOSE_INSERT_INVALID_CHARACTER;
3681 return COMPOSE_INSERT_SUCCESS;
3684 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3685 const gchar *filename,
3686 const gchar *content_type,
3687 const gchar *charset)
3695 GtkListStore *store;
3697 gboolean has_binary = FALSE;
3699 if (!is_file_exist(file)) {
3700 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3701 gboolean result = FALSE;
3702 if (file_from_uri && is_file_exist(file_from_uri)) {
3703 result = compose_attach_append(
3704 compose, file_from_uri,
3705 filename, content_type,
3708 g_free(file_from_uri);
3711 alertpanel_error("File %s doesn't exist\n", filename);
3714 if ((size = get_file_size(file)) < 0) {
3715 alertpanel_error("Can't get file size of %s\n", filename);
3719 /* In batch mode, we allow 0-length files to be attached no questions asked */
3720 if (size == 0 && !compose->batch) {
3721 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3722 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3723 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3724 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3727 if (aval != G_ALERTALTERNATE) {
3731 if ((fp = g_fopen(file, "rb")) == NULL) {
3732 alertpanel_error(_("Can't read %s."), filename);
3737 ainfo = g_new0(AttachInfo, 1);
3738 auto_ainfo = g_auto_pointer_new_with_free
3739 (ainfo, (GFreeFunc) compose_attach_info_free);
3740 ainfo->file = g_strdup(file);
3743 ainfo->content_type = g_strdup(content_type);
3744 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3746 MsgFlags flags = {0, 0};
3748 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3749 ainfo->encoding = ENC_7BIT;
3751 ainfo->encoding = ENC_8BIT;
3753 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3754 if (msginfo && msginfo->subject)
3755 name = g_strdup(msginfo->subject);
3757 name = g_path_get_basename(filename ? filename : file);
3759 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3761 procmsg_msginfo_free(msginfo);
3763 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3764 ainfo->charset = g_strdup(charset);
3765 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3767 ainfo->encoding = ENC_BASE64;
3769 name = g_path_get_basename(filename ? filename : file);
3770 ainfo->name = g_strdup(name);
3774 ainfo->content_type = procmime_get_mime_type(file);
3775 if (!ainfo->content_type) {
3776 ainfo->content_type =
3777 g_strdup("application/octet-stream");
3778 ainfo->encoding = ENC_BASE64;
3779 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3781 procmime_get_encoding_for_text_file(file, &has_binary);
3783 ainfo->encoding = ENC_BASE64;
3784 name = g_path_get_basename(filename ? filename : file);
3785 ainfo->name = g_strdup(name);
3789 if (ainfo->name != NULL
3790 && !strcmp(ainfo->name, ".")) {
3791 g_free(ainfo->name);
3795 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3796 g_free(ainfo->content_type);
3797 ainfo->content_type = g_strdup("application/octet-stream");
3798 g_free(ainfo->charset);
3799 ainfo->charset = NULL;
3802 ainfo->size = (goffset)size;
3803 size_text = to_human_readable((goffset)size);
3805 store = GTK_LIST_STORE(gtk_tree_view_get_model
3806 (GTK_TREE_VIEW(compose->attach_clist)));
3808 gtk_list_store_append(store, &iter);
3809 gtk_list_store_set(store, &iter,
3810 COL_MIMETYPE, ainfo->content_type,
3811 COL_SIZE, size_text,
3812 COL_NAME, ainfo->name,
3813 COL_CHARSET, ainfo->charset,
3815 COL_AUTODATA, auto_ainfo,
3818 g_auto_pointer_free(auto_ainfo);
3819 compose_attach_update_label(compose);
3823 static void compose_use_signing(Compose *compose, gboolean use_signing)
3825 compose->use_signing = use_signing;
3826 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3829 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3831 compose->use_encryption = use_encryption;
3832 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3835 #define NEXT_PART_NOT_CHILD(info) \
3837 node = info->node; \
3838 while (node->children) \
3839 node = g_node_last_child(node); \
3840 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3843 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3847 MimeInfo *firsttext = NULL;
3848 MimeInfo *encrypted = NULL;
3851 const gchar *partname = NULL;
3853 mimeinfo = procmime_scan_message(msginfo);
3854 if (!mimeinfo) return;
3856 if (mimeinfo->node->children == NULL) {
3857 procmime_mimeinfo_free_all(mimeinfo);
3861 /* find first content part */
3862 child = (MimeInfo *) mimeinfo->node->children->data;
3863 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3864 child = (MimeInfo *)child->node->children->data;
3867 if (child->type == MIMETYPE_TEXT) {
3869 debug_print("First text part found\n");
3870 } else if (compose->mode == COMPOSE_REEDIT &&
3871 child->type == MIMETYPE_APPLICATION &&
3872 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3873 encrypted = (MimeInfo *)child->node->parent->data;
3876 child = (MimeInfo *) mimeinfo->node->children->data;
3877 while (child != NULL) {
3880 if (child == encrypted) {
3881 /* skip this part of tree */
3882 NEXT_PART_NOT_CHILD(child);
3886 if (child->type == MIMETYPE_MULTIPART) {
3887 /* get the actual content */
3888 child = procmime_mimeinfo_next(child);
3892 if (child == firsttext) {
3893 child = procmime_mimeinfo_next(child);
3897 outfile = procmime_get_tmp_file_name(child);
3898 if ((err = procmime_get_part(outfile, child)) < 0)
3899 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3901 gchar *content_type;
3903 content_type = procmime_get_content_type_str(child->type, child->subtype);
3905 /* if we meet a pgp signature, we don't attach it, but
3906 * we force signing. */
3907 if ((strcmp(content_type, "application/pgp-signature") &&
3908 strcmp(content_type, "application/pkcs7-signature") &&
3909 strcmp(content_type, "application/x-pkcs7-signature"))
3910 || compose->mode == COMPOSE_REDIRECT) {
3911 partname = procmime_mimeinfo_get_parameter(child, "filename");
3912 if (partname == NULL)
3913 partname = procmime_mimeinfo_get_parameter(child, "name");
3914 if (partname == NULL)
3916 compose_attach_append(compose, outfile,
3917 partname, content_type,
3918 procmime_mimeinfo_get_parameter(child, "charset"));
3920 compose_force_signing(compose, compose->account, NULL);
3922 g_free(content_type);
3925 NEXT_PART_NOT_CHILD(child);
3927 procmime_mimeinfo_free_all(mimeinfo);
3930 #undef NEXT_PART_NOT_CHILD
3935 WAIT_FOR_INDENT_CHAR,
3936 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3939 /* return indent length, we allow:
3940 indent characters followed by indent characters or spaces/tabs,
3941 alphabets and numbers immediately followed by indent characters,
3942 and the repeating sequences of the above
3943 If quote ends with multiple spaces, only the first one is included. */
3944 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3945 const GtkTextIter *start, gint *len)
3947 GtkTextIter iter = *start;
3951 IndentState state = WAIT_FOR_INDENT_CHAR;
3954 gint alnum_count = 0;
3955 gint space_count = 0;
3958 if (prefs_common.quote_chars == NULL) {
3962 while (!gtk_text_iter_ends_line(&iter)) {
3963 wc = gtk_text_iter_get_char(&iter);
3964 if (g_unichar_iswide(wc))
3966 clen = g_unichar_to_utf8(wc, ch);
3970 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3971 is_space = g_unichar_isspace(wc);
3973 if (state == WAIT_FOR_INDENT_CHAR) {
3974 if (!is_indent && !g_unichar_isalnum(wc))
3977 quote_len += alnum_count + space_count + 1;
3978 alnum_count = space_count = 0;
3979 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3982 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3983 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3987 else if (is_indent) {
3988 quote_len += alnum_count + space_count + 1;
3989 alnum_count = space_count = 0;
3992 state = WAIT_FOR_INDENT_CHAR;
3996 gtk_text_iter_forward_char(&iter);
3999 if (quote_len > 0 && space_count > 0)
4005 if (quote_len > 0) {
4007 gtk_text_iter_forward_chars(&iter, quote_len);
4008 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4014 /* return >0 if the line is itemized */
4015 static int compose_itemized_length(GtkTextBuffer *buffer,
4016 const GtkTextIter *start)
4018 GtkTextIter iter = *start;
4023 if (gtk_text_iter_ends_line(&iter))
4028 wc = gtk_text_iter_get_char(&iter);
4029 if (!g_unichar_isspace(wc))
4031 gtk_text_iter_forward_char(&iter);
4032 if (gtk_text_iter_ends_line(&iter))
4036 clen = g_unichar_to_utf8(wc, ch);
4040 if (!strchr("*-+", ch[0]))
4043 gtk_text_iter_forward_char(&iter);
4044 if (gtk_text_iter_ends_line(&iter))
4046 wc = gtk_text_iter_get_char(&iter);
4047 if (g_unichar_isspace(wc)) {
4053 /* return the string at the start of the itemization */
4054 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4055 const GtkTextIter *start)
4057 GtkTextIter iter = *start;
4060 GString *item_chars = g_string_new("");
4063 if (gtk_text_iter_ends_line(&iter))
4068 wc = gtk_text_iter_get_char(&iter);
4069 if (!g_unichar_isspace(wc))
4071 gtk_text_iter_forward_char(&iter);
4072 if (gtk_text_iter_ends_line(&iter))
4074 g_string_append_unichar(item_chars, wc);
4077 str = item_chars->str;
4078 g_string_free(item_chars, FALSE);
4082 /* return the number of spaces at a line's start */
4083 static int compose_left_offset_length(GtkTextBuffer *buffer,
4084 const GtkTextIter *start)
4086 GtkTextIter iter = *start;
4089 if (gtk_text_iter_ends_line(&iter))
4093 wc = gtk_text_iter_get_char(&iter);
4094 if (!g_unichar_isspace(wc))
4097 gtk_text_iter_forward_char(&iter);
4098 if (gtk_text_iter_ends_line(&iter))
4102 gtk_text_iter_forward_char(&iter);
4103 if (gtk_text_iter_ends_line(&iter))
4108 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4109 const GtkTextIter *start,
4110 GtkTextIter *break_pos,
4114 GtkTextIter iter = *start, line_end = *start;
4115 PangoLogAttr *attrs;
4122 gboolean can_break = FALSE;
4123 gboolean do_break = FALSE;
4124 gboolean was_white = FALSE;
4125 gboolean prev_dont_break = FALSE;
4127 gtk_text_iter_forward_to_line_end(&line_end);
4128 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4129 len = g_utf8_strlen(str, -1);
4133 g_warning("compose_get_line_break_pos: len = 0!\n");
4137 /* g_print("breaking line: %d: %s (len = %d)\n",
4138 gtk_text_iter_get_line(&iter), str, len); */
4140 attrs = g_new(PangoLogAttr, len + 1);
4142 pango_default_break(str, -1, NULL, attrs, len + 1);
4146 /* skip quote and leading spaces */
4147 for (i = 0; *p != '\0' && i < len; i++) {
4150 wc = g_utf8_get_char(p);
4151 if (i >= quote_len && !g_unichar_isspace(wc))
4153 if (g_unichar_iswide(wc))
4155 else if (*p == '\t')
4159 p = g_utf8_next_char(p);
4162 for (; *p != '\0' && i < len; i++) {
4163 PangoLogAttr *attr = attrs + i;
4167 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4170 was_white = attr->is_white;
4172 /* don't wrap URI */
4173 if ((uri_len = get_uri_len(p)) > 0) {
4175 if (pos > 0 && col > max_col) {
4185 wc = g_utf8_get_char(p);
4186 if (g_unichar_iswide(wc)) {
4188 if (prev_dont_break && can_break && attr->is_line_break)
4190 } else if (*p == '\t')
4194 if (pos > 0 && col > max_col) {
4199 if (*p == '-' || *p == '/')
4200 prev_dont_break = TRUE;
4202 prev_dont_break = FALSE;
4204 p = g_utf8_next_char(p);
4208 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4213 *break_pos = *start;
4214 gtk_text_iter_set_line_offset(break_pos, pos);
4219 static gboolean compose_join_next_line(Compose *compose,
4220 GtkTextBuffer *buffer,
4222 const gchar *quote_str)
4224 GtkTextIter iter_ = *iter, cur, prev, next, end;
4225 PangoLogAttr attrs[3];
4227 gchar *next_quote_str;
4230 gboolean keep_cursor = FALSE;
4232 if (!gtk_text_iter_forward_line(&iter_) ||
4233 gtk_text_iter_ends_line(&iter_)) {
4236 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4238 if ((quote_str || next_quote_str) &&
4239 strcmp2(quote_str, next_quote_str) != 0) {
4240 g_free(next_quote_str);
4243 g_free(next_quote_str);
4246 if (quote_len > 0) {
4247 gtk_text_iter_forward_chars(&end, quote_len);
4248 if (gtk_text_iter_ends_line(&end)) {
4253 /* don't join itemized lines */
4254 if (compose_itemized_length(buffer, &end) > 0) {
4258 /* don't join signature separator */
4259 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4262 /* delete quote str */
4264 gtk_text_buffer_delete(buffer, &iter_, &end);
4266 /* don't join line breaks put by the user */
4268 gtk_text_iter_backward_char(&cur);
4269 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4270 gtk_text_iter_forward_char(&cur);
4274 gtk_text_iter_forward_char(&cur);
4275 /* delete linebreak and extra spaces */
4276 while (gtk_text_iter_backward_char(&cur)) {
4277 wc1 = gtk_text_iter_get_char(&cur);
4278 if (!g_unichar_isspace(wc1))
4283 while (!gtk_text_iter_ends_line(&cur)) {
4284 wc1 = gtk_text_iter_get_char(&cur);
4285 if (!g_unichar_isspace(wc1))
4287 gtk_text_iter_forward_char(&cur);
4290 if (!gtk_text_iter_equal(&prev, &next)) {
4293 mark = gtk_text_buffer_get_insert(buffer);
4294 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4295 if (gtk_text_iter_equal(&prev, &cur))
4297 gtk_text_buffer_delete(buffer, &prev, &next);
4301 /* insert space if required */
4302 gtk_text_iter_backward_char(&prev);
4303 wc1 = gtk_text_iter_get_char(&prev);
4304 wc2 = gtk_text_iter_get_char(&next);
4305 gtk_text_iter_forward_char(&next);
4306 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4307 pango_default_break(str, -1, NULL, attrs, 3);
4308 if (!attrs[1].is_line_break ||
4309 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4310 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4312 gtk_text_iter_backward_char(&iter_);
4313 gtk_text_buffer_place_cursor(buffer, &iter_);
4322 #define ADD_TXT_POS(bp_, ep_, pti_) \
4323 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4324 last = last->next; \
4325 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4326 last->next = NULL; \
4328 g_warning("alloc error scanning URIs\n"); \
4331 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4333 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4334 GtkTextBuffer *buffer;
4335 GtkTextIter iter, break_pos, end_of_line;
4336 gchar *quote_str = NULL;
4338 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4339 gboolean prev_autowrap = compose->autowrap;
4340 gint startq_offset = -1, noq_offset = -1;
4341 gint uri_start = -1, uri_stop = -1;
4342 gint nouri_start = -1, nouri_stop = -1;
4343 gint num_blocks = 0;
4344 gint quotelevel = -1;
4345 gboolean modified = force;
4346 gboolean removed = FALSE;
4347 gboolean modified_before_remove = FALSE;
4349 gboolean start = TRUE;
4350 gint itemized_len = 0, rem_item_len = 0;
4351 gchar *itemized_chars = NULL;
4352 gboolean item_continuation = FALSE;
4357 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4361 compose->autowrap = FALSE;
4363 buffer = gtk_text_view_get_buffer(text);
4364 undo_wrapping(compose->undostruct, TRUE);
4369 mark = gtk_text_buffer_get_insert(buffer);
4370 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4374 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4375 if (gtk_text_iter_ends_line(&iter)) {
4376 while (gtk_text_iter_ends_line(&iter) &&
4377 gtk_text_iter_forward_line(&iter))
4380 while (gtk_text_iter_backward_line(&iter)) {
4381 if (gtk_text_iter_ends_line(&iter)) {
4382 gtk_text_iter_forward_line(&iter);
4388 /* move to line start */
4389 gtk_text_iter_set_line_offset(&iter, 0);
4392 itemized_len = compose_itemized_length(buffer, &iter);
4394 if (!itemized_len) {
4395 itemized_len = compose_left_offset_length(buffer, &iter);
4396 item_continuation = TRUE;
4400 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4402 /* go until paragraph end (empty line) */
4403 while (start || !gtk_text_iter_ends_line(&iter)) {
4404 gchar *scanpos = NULL;
4405 /* parse table - in order of priority */
4407 const gchar *needle; /* token */
4409 /* token search function */
4410 gchar *(*search) (const gchar *haystack,
4411 const gchar *needle);
4412 /* part parsing function */
4413 gboolean (*parse) (const gchar *start,
4414 const gchar *scanpos,
4418 /* part to URI function */
4419 gchar *(*build_uri) (const gchar *bp,
4423 static struct table parser[] = {
4424 {"http://", strcasestr, get_uri_part, make_uri_string},
4425 {"https://", strcasestr, get_uri_part, make_uri_string},
4426 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4427 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4428 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4429 {"www.", strcasestr, get_uri_part, make_http_string},
4430 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4431 {"@", strcasestr, get_email_part, make_email_string}
4433 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4434 gint last_index = PARSE_ELEMS;
4436 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4440 if (!prev_autowrap && num_blocks == 0) {
4442 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4443 G_CALLBACK(text_inserted),
4446 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4449 uri_start = uri_stop = -1;
4451 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4454 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4455 if (startq_offset == -1)
4456 startq_offset = gtk_text_iter_get_offset(&iter);
4457 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4458 if (quotelevel > 2) {
4459 /* recycle colors */
4460 if (prefs_common.recycle_quote_colors)
4469 if (startq_offset == -1)
4470 noq_offset = gtk_text_iter_get_offset(&iter);
4474 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4477 if (gtk_text_iter_ends_line(&iter)) {
4479 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4480 prefs_common.linewrap_len,
4482 GtkTextIter prev, next, cur;
4483 if (prev_autowrap != FALSE || force) {
4484 compose->automatic_break = TRUE;
4486 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4487 compose->automatic_break = FALSE;
4488 if (itemized_len && compose->autoindent) {
4489 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4490 if (!item_continuation)
4491 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4493 } else if (quote_str && wrap_quote) {
4494 compose->automatic_break = TRUE;
4496 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4497 compose->automatic_break = FALSE;
4498 if (itemized_len && compose->autoindent) {
4499 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4500 if (!item_continuation)
4501 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4505 /* remove trailing spaces */
4507 rem_item_len = itemized_len;
4508 while (compose->autoindent && rem_item_len-- > 0)
4509 gtk_text_iter_backward_char(&cur);
4510 gtk_text_iter_backward_char(&cur);
4513 while (!gtk_text_iter_starts_line(&cur)) {
4516 gtk_text_iter_backward_char(&cur);
4517 wc = gtk_text_iter_get_char(&cur);
4518 if (!g_unichar_isspace(wc))
4522 if (!gtk_text_iter_equal(&prev, &next)) {
4523 gtk_text_buffer_delete(buffer, &prev, &next);
4525 gtk_text_iter_forward_char(&break_pos);
4529 gtk_text_buffer_insert(buffer, &break_pos,
4533 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4535 /* move iter to current line start */
4536 gtk_text_iter_set_line_offset(&iter, 0);
4543 /* move iter to next line start */
4549 if (!prev_autowrap && num_blocks > 0) {
4551 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4552 G_CALLBACK(text_inserted),
4556 while (!gtk_text_iter_ends_line(&end_of_line)) {
4557 gtk_text_iter_forward_char(&end_of_line);
4559 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4561 nouri_start = gtk_text_iter_get_offset(&iter);
4562 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4564 walk_pos = gtk_text_iter_get_offset(&iter);
4565 /* FIXME: this looks phony. scanning for anything in the parse table */
4566 for (n = 0; n < PARSE_ELEMS; n++) {
4569 tmp = parser[n].search(walk, parser[n].needle);
4571 if (scanpos == NULL || tmp < scanpos) {
4580 /* check if URI can be parsed */
4581 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4582 (const gchar **)&ep, FALSE)
4583 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4587 strlen(parser[last_index].needle);
4590 uri_start = walk_pos + (bp - o_walk);
4591 uri_stop = walk_pos + (ep - o_walk);
4595 gtk_text_iter_forward_line(&iter);
4598 if (startq_offset != -1) {
4599 GtkTextIter startquote, endquote;
4600 gtk_text_buffer_get_iter_at_offset(
4601 buffer, &startquote, startq_offset);
4604 switch (quotelevel) {
4606 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4607 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4608 gtk_text_buffer_apply_tag_by_name(
4609 buffer, "quote0", &startquote, &endquote);
4610 gtk_text_buffer_remove_tag_by_name(
4611 buffer, "quote1", &startquote, &endquote);
4612 gtk_text_buffer_remove_tag_by_name(
4613 buffer, "quote2", &startquote, &endquote);
4618 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4619 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4620 gtk_text_buffer_apply_tag_by_name(
4621 buffer, "quote1", &startquote, &endquote);
4622 gtk_text_buffer_remove_tag_by_name(
4623 buffer, "quote0", &startquote, &endquote);
4624 gtk_text_buffer_remove_tag_by_name(
4625 buffer, "quote2", &startquote, &endquote);
4630 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4631 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4632 gtk_text_buffer_apply_tag_by_name(
4633 buffer, "quote2", &startquote, &endquote);
4634 gtk_text_buffer_remove_tag_by_name(
4635 buffer, "quote0", &startquote, &endquote);
4636 gtk_text_buffer_remove_tag_by_name(
4637 buffer, "quote1", &startquote, &endquote);
4643 } else if (noq_offset != -1) {
4644 GtkTextIter startnoquote, endnoquote;
4645 gtk_text_buffer_get_iter_at_offset(
4646 buffer, &startnoquote, noq_offset);
4649 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4650 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4651 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4652 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4653 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4654 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote0", &startnoquote, &endnoquote);
4657 gtk_text_buffer_remove_tag_by_name(
4658 buffer, "quote1", &startnoquote, &endnoquote);
4659 gtk_text_buffer_remove_tag_by_name(
4660 buffer, "quote2", &startnoquote, &endnoquote);
4666 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4667 GtkTextIter nouri_start_iter, nouri_end_iter;
4668 gtk_text_buffer_get_iter_at_offset(
4669 buffer, &nouri_start_iter, nouri_start);
4670 gtk_text_buffer_get_iter_at_offset(
4671 buffer, &nouri_end_iter, nouri_stop);
4672 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4673 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4674 gtk_text_buffer_remove_tag_by_name(
4675 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4676 modified_before_remove = modified;
4681 if (uri_start >= 0 && uri_stop > 0) {
4682 GtkTextIter uri_start_iter, uri_end_iter, back;
4683 gtk_text_buffer_get_iter_at_offset(
4684 buffer, &uri_start_iter, uri_start);
4685 gtk_text_buffer_get_iter_at_offset(
4686 buffer, &uri_end_iter, uri_stop);
4687 back = uri_end_iter;
4688 gtk_text_iter_backward_char(&back);
4689 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4690 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4691 gtk_text_buffer_apply_tag_by_name(
4692 buffer, "link", &uri_start_iter, &uri_end_iter);
4694 if (removed && !modified_before_remove) {
4700 // debug_print("not modified, out after %d lines\n", lines);
4704 // debug_print("modified, out after %d lines\n", lines);
4706 g_free(itemized_chars);
4709 undo_wrapping(compose->undostruct, FALSE);
4710 compose->autowrap = prev_autowrap;
4715 void compose_action_cb(void *data)
4717 Compose *compose = (Compose *)data;
4718 compose_wrap_all(compose);
4721 static void compose_wrap_all(Compose *compose)
4723 compose_wrap_all_full(compose, FALSE);
4726 static void compose_wrap_all_full(Compose *compose, gboolean force)
4728 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4729 GtkTextBuffer *buffer;
4731 gboolean modified = TRUE;
4733 buffer = gtk_text_view_get_buffer(text);
4735 gtk_text_buffer_get_start_iter(buffer, &iter);
4737 undo_wrapping(compose->undostruct, TRUE);
4739 while (!gtk_text_iter_is_end(&iter) && modified)
4740 modified = compose_beautify_paragraph(compose, &iter, force);
4742 undo_wrapping(compose->undostruct, FALSE);
4746 static void compose_set_title(Compose *compose)
4752 edited = compose->modified ? _(" [Edited]") : "";
4754 subject = gtk_editable_get_chars(
4755 GTK_EDITABLE(compose->subject_entry), 0, -1);
4757 #ifndef GENERIC_UMPC
4758 if (subject && strlen(subject))
4759 str = g_strdup_printf(_("%s - Compose message%s"),
4762 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4764 str = g_strdup(_("Compose message"));
4767 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4773 * compose_current_mail_account:
4775 * Find a current mail account (the currently selected account, or the
4776 * default account, if a news account is currently selected). If a
4777 * mail account cannot be found, display an error message.
4779 * Return value: Mail account, or NULL if not found.
4781 static PrefsAccount *
4782 compose_current_mail_account(void)
4786 if (cur_account && cur_account->protocol != A_NNTP)
4789 ac = account_get_default();
4790 if (!ac || ac->protocol == A_NNTP) {
4791 alertpanel_error(_("Account for sending mail is not specified.\n"
4792 "Please select a mail account before sending."));
4799 #define QUOTE_IF_REQUIRED(out, str) \
4801 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4805 len = strlen(str) + 3; \
4806 if ((__tmp = alloca(len)) == NULL) { \
4807 g_warning("can't allocate memory\n"); \
4808 g_string_free(header, TRUE); \
4811 g_snprintf(__tmp, len, "\"%s\"", str); \
4816 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4817 g_warning("can't allocate memory\n"); \
4818 g_string_free(header, TRUE); \
4821 strcpy(__tmp, str); \
4827 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4829 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4833 len = strlen(str) + 3; \
4834 if ((__tmp = alloca(len)) == NULL) { \
4835 g_warning("can't allocate memory\n"); \
4838 g_snprintf(__tmp, len, "\"%s\"", str); \
4843 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4844 g_warning("can't allocate memory\n"); \
4847 strcpy(__tmp, str); \
4853 static void compose_select_account(Compose *compose, PrefsAccount *account,
4856 gchar *from = NULL, *header = NULL;
4857 ComposeHeaderEntry *header_entry;
4858 #if GTK_CHECK_VERSION(2, 24, 0)
4862 cm_return_if_fail(account != NULL);
4864 compose->account = account;
4865 if (account->name && *account->name) {
4867 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4868 qbuf = escape_internal_quotes(buf, '"');
4869 from = g_strdup_printf("%s <%s>",
4870 qbuf, account->address);
4873 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4875 from = g_strdup_printf("<%s>",
4877 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4882 compose_set_title(compose);
4884 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4885 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4887 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4888 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4889 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4891 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4893 activate_privacy_system(compose, account, FALSE);
4895 if (!init && compose->mode != COMPOSE_REDIRECT) {
4896 undo_block(compose->undostruct);
4897 compose_insert_sig(compose, TRUE);
4898 undo_unblock(compose->undostruct);
4901 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4902 #if !GTK_CHECK_VERSION(2, 24, 0)
4903 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4905 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4906 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4907 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4910 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4911 if (account->protocol == A_NNTP) {
4912 if (!strcmp(header, _("To:")))
4913 combobox_select_by_text(
4914 GTK_COMBO_BOX(header_entry->combo),
4917 if (!strcmp(header, _("Newsgroups:")))
4918 combobox_select_by_text(
4919 GTK_COMBO_BOX(header_entry->combo),
4927 /* use account's dict info if set */
4928 if (compose->gtkaspell) {
4929 if (account->enable_default_dictionary)
4930 gtkaspell_change_dict(compose->gtkaspell,
4931 account->default_dictionary, FALSE);
4932 if (account->enable_default_alt_dictionary)
4933 gtkaspell_change_alt_dict(compose->gtkaspell,
4934 account->default_alt_dictionary);
4935 if (account->enable_default_dictionary
4936 || account->enable_default_alt_dictionary)
4937 compose_spell_menu_changed(compose);
4942 gboolean compose_check_for_valid_recipient(Compose *compose) {
4943 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4944 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4945 gboolean recipient_found = FALSE;
4949 /* free to and newsgroup list */
4950 slist_free_strings_full(compose->to_list);
4951 compose->to_list = NULL;
4953 slist_free_strings_full(compose->newsgroup_list);
4954 compose->newsgroup_list = NULL;
4956 /* search header entries for to and newsgroup entries */
4957 for (list = compose->header_list; list; list = list->next) {
4960 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4961 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4964 if (entry[0] != '\0') {
4965 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4966 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4967 compose->to_list = address_list_append(compose->to_list, entry);
4968 recipient_found = TRUE;
4971 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4972 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4973 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4974 recipient_found = TRUE;
4981 return recipient_found;
4984 static gboolean compose_check_for_set_recipients(Compose *compose)
4986 if (compose->account->set_autocc && compose->account->auto_cc) {
4987 gboolean found_other = FALSE;
4989 /* search header entries for to and newsgroup entries */
4990 for (list = compose->header_list; list; list = list->next) {
4993 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4994 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4997 if (strcmp(entry, compose->account->auto_cc)
4998 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5008 if (compose->batch) {
5009 gtk_widget_show_all(compose->window);
5011 aval = alertpanel(_("Send"),
5012 _("The only recipient is the default CC address. Send anyway?"),
5013 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5014 if (aval != G_ALERTALTERNATE)
5018 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5019 gboolean found_other = FALSE;
5021 /* search header entries for to and newsgroup entries */
5022 for (list = compose->header_list; list; list = list->next) {
5025 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5026 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5029 if (strcmp(entry, compose->account->auto_bcc)
5030 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5040 if (compose->batch) {
5041 gtk_widget_show_all(compose->window);
5043 aval = alertpanel(_("Send"),
5044 _("The only recipient is the default BCC address. Send anyway?"),
5045 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5046 if (aval != G_ALERTALTERNATE)
5053 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5057 if (compose_check_for_valid_recipient(compose) == FALSE) {
5058 if (compose->batch) {
5059 gtk_widget_show_all(compose->window);
5061 alertpanel_error(_("Recipient is not specified."));
5065 if (compose_check_for_set_recipients(compose) == FALSE) {
5069 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5070 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5071 if (*str == '\0' && check_everything == TRUE &&
5072 compose->mode != COMPOSE_REDIRECT) {
5074 gchar *button_label;
5077 if (compose->sending)
5078 button_label = _("+_Send");
5080 button_label = _("+_Queue");
5081 message = g_strdup_printf(_("Subject is empty. %s"),
5082 compose->sending?_("Send it anyway?"):
5083 _("Queue it anyway?"));
5085 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5086 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5087 ALERT_QUESTION, G_ALERTDEFAULT);
5089 if (aval & G_ALERTDISABLE) {
5090 aval &= ~G_ALERTDISABLE;
5091 prefs_common.warn_empty_subj = FALSE;
5093 if (aval != G_ALERTALTERNATE)
5098 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5104 gint compose_send(Compose *compose)
5107 FolderItem *folder = NULL;
5109 gchar *msgpath = NULL;
5110 gboolean discard_window = FALSE;
5111 gchar *errstr = NULL;
5112 gchar *tmsgid = NULL;
5113 MainWindow *mainwin = mainwindow_get_mainwindow();
5114 gboolean queued_removed = FALSE;
5116 if (prefs_common.send_dialog_invisible
5117 || compose->batch == TRUE)
5118 discard_window = TRUE;
5120 compose_allow_user_actions (compose, FALSE);
5121 compose->sending = TRUE;
5123 if (compose_check_entries(compose, TRUE) == FALSE) {
5124 if (compose->batch) {
5125 gtk_widget_show_all(compose->window);
5131 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5134 if (compose->batch) {
5135 gtk_widget_show_all(compose->window);
5138 alertpanel_error(_("Could not queue message for sending:\n\n"
5139 "Charset conversion failed."));
5140 } else if (val == -5) {
5141 alertpanel_error(_("Could not queue message for sending:\n\n"
5142 "Couldn't get recipient encryption key."));
5143 } else if (val == -6) {
5145 } else if (val == -3) {
5146 if (privacy_peek_error())
5147 alertpanel_error(_("Could not queue message for sending:\n\n"
5148 "Signature failed: %s"), privacy_get_error());
5149 } else if (val == -2 && errno != 0) {
5150 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5152 alertpanel_error(_("Could not queue message for sending."));
5157 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5158 if (discard_window) {
5159 compose->sending = FALSE;
5160 compose_close(compose);
5161 /* No more compose access in the normal codepath
5162 * after this point! */
5167 alertpanel_error(_("The message was queued but could not be "
5168 "sent.\nUse \"Send queued messages\" from "
5169 "the main window to retry."));
5170 if (!discard_window) {
5177 if (msgpath == NULL) {
5178 msgpath = folder_item_fetch_msg(folder, msgnum);
5179 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5182 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5183 claws_unlink(msgpath);
5186 if (!discard_window) {
5188 if (!queued_removed)
5189 folder_item_remove_msg(folder, msgnum);
5190 folder_item_scan(folder);
5192 /* make sure we delete that */
5193 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5195 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5196 folder_item_remove_msg(folder, tmp->msgnum);
5197 procmsg_msginfo_free(tmp);
5204 if (!queued_removed)
5205 folder_item_remove_msg(folder, msgnum);
5206 folder_item_scan(folder);
5208 /* make sure we delete that */
5209 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5211 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5212 folder_item_remove_msg(folder, tmp->msgnum);
5213 procmsg_msginfo_free(tmp);
5216 if (!discard_window) {
5217 compose->sending = FALSE;
5218 compose_allow_user_actions (compose, TRUE);
5219 compose_close(compose);
5223 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5224 "the main window to retry."), errstr);
5227 alertpanel_error_log(_("The message was queued but could not be "
5228 "sent.\nUse \"Send queued messages\" from "
5229 "the main window to retry."));
5231 if (!discard_window) {
5240 toolbar_main_set_sensitive(mainwin);
5241 main_window_set_menu_sensitive(mainwin);
5247 compose_allow_user_actions (compose, TRUE);
5248 compose->sending = FALSE;
5249 compose->modified = TRUE;
5250 toolbar_main_set_sensitive(mainwin);
5251 main_window_set_menu_sensitive(mainwin);
5256 static gboolean compose_use_attach(Compose *compose)
5258 GtkTreeModel *model = gtk_tree_view_get_model
5259 (GTK_TREE_VIEW(compose->attach_clist));
5260 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5263 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5266 gchar buf[BUFFSIZE];
5268 gboolean first_to_address;
5269 gboolean first_cc_address;
5271 ComposeHeaderEntry *headerentry;
5272 const gchar *headerentryname;
5273 const gchar *cc_hdr;
5274 const gchar *to_hdr;
5275 gboolean err = FALSE;
5277 debug_print("Writing redirect header\n");
5279 cc_hdr = prefs_common_translated_header_name("Cc:");
5280 to_hdr = prefs_common_translated_header_name("To:");
5282 first_to_address = TRUE;
5283 for (list = compose->header_list; list; list = list->next) {
5284 headerentry = ((ComposeHeaderEntry *)list->data);
5285 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5287 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5288 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5289 Xstrdup_a(str, entstr, return -1);
5291 if (str[0] != '\0') {
5292 compose_convert_header
5293 (compose, buf, sizeof(buf), str,
5294 strlen("Resent-To") + 2, TRUE);
5296 if (first_to_address) {
5297 err |= (fprintf(fp, "Resent-To: ") < 0);
5298 first_to_address = FALSE;
5300 err |= (fprintf(fp, ",") < 0);
5302 err |= (fprintf(fp, "%s", buf) < 0);
5306 if (!first_to_address) {
5307 err |= (fprintf(fp, "\n") < 0);
5310 first_cc_address = TRUE;
5311 for (list = compose->header_list; list; list = list->next) {
5312 headerentry = ((ComposeHeaderEntry *)list->data);
5313 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5315 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5316 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5317 Xstrdup_a(str, strg, return -1);
5319 if (str[0] != '\0') {
5320 compose_convert_header
5321 (compose, buf, sizeof(buf), str,
5322 strlen("Resent-Cc") + 2, TRUE);
5324 if (first_cc_address) {
5325 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5326 first_cc_address = FALSE;
5328 err |= (fprintf(fp, ",") < 0);
5330 err |= (fprintf(fp, "%s", buf) < 0);
5334 if (!first_cc_address) {
5335 err |= (fprintf(fp, "\n") < 0);
5338 return (err ? -1:0);
5341 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5343 gchar buf[BUFFSIZE];
5345 const gchar *entstr;
5346 /* struct utsname utsbuf; */
5347 gboolean err = FALSE;
5349 cm_return_val_if_fail(fp != NULL, -1);
5350 cm_return_val_if_fail(compose->account != NULL, -1);
5351 cm_return_val_if_fail(compose->account->address != NULL, -1);
5354 get_rfc822_date(buf, sizeof(buf));
5355 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5358 if (compose->account->name && *compose->account->name) {
5359 compose_convert_header
5360 (compose, buf, sizeof(buf), compose->account->name,
5361 strlen("From: "), TRUE);
5362 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5363 buf, compose->account->address) < 0);
5365 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5368 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5369 if (*entstr != '\0') {
5370 Xstrdup_a(str, entstr, return -1);
5373 compose_convert_header(compose, buf, sizeof(buf), str,
5374 strlen("Subject: "), FALSE);
5375 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5379 /* Resent-Message-ID */
5380 if (compose->account->set_domain && compose->account->domain) {
5381 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5382 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5383 g_snprintf(buf, sizeof(buf), "%s",
5384 strchr(compose->account->address, '@') ?
5385 strchr(compose->account->address, '@')+1 :
5386 compose->account->address);
5388 g_snprintf(buf, sizeof(buf), "%s", "");
5391 if (compose->account->gen_msgid) {
5393 if (compose->account->msgid_with_addr) {
5394 addr = compose->account->address;
5396 generate_msgid(buf, sizeof(buf), addr);
5397 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5399 g_free(compose->msgid);
5400 compose->msgid = g_strdup(buf);
5402 compose->msgid = NULL;
5405 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5408 /* separator between header and body */
5409 err |= (fputs("\n", fp) == EOF);
5411 return (err ? -1:0);
5414 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5418 gchar buf[BUFFSIZE];
5420 gboolean skip = FALSE;
5421 gboolean err = FALSE;
5422 gchar *not_included[]={
5423 "Return-Path:", "Delivered-To:", "Received:",
5424 "Subject:", "X-UIDL:", "AF:",
5425 "NF:", "PS:", "SRH:",
5426 "SFN:", "DSR:", "MID:",
5427 "CFG:", "PT:", "S:",
5428 "RQ:", "SSV:", "NSV:",
5429 "SSH:", "R:", "MAID:",
5430 "NAID:", "RMID:", "FMID:",
5431 "SCF:", "RRCPT:", "NG:",
5432 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5433 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5434 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5435 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5436 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5439 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5440 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5444 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5446 for (i = 0; not_included[i] != NULL; i++) {
5447 if (g_ascii_strncasecmp(buf, not_included[i],
5448 strlen(not_included[i])) == 0) {
5455 if (fputs(buf, fdest) == -1)
5458 if (!prefs_common.redirect_keep_from) {
5459 if (g_ascii_strncasecmp(buf, "From:",
5460 strlen("From:")) == 0) {
5461 err |= (fputs(" (by way of ", fdest) == EOF);
5462 if (compose->account->name
5463 && *compose->account->name) {
5464 compose_convert_header
5465 (compose, buf, sizeof(buf),
5466 compose->account->name,
5469 err |= (fprintf(fdest, "%s <%s>",
5471 compose->account->address) < 0);
5473 err |= (fprintf(fdest, "%s",
5474 compose->account->address) < 0);
5475 err |= (fputs(")", fdest) == EOF);
5479 if (fputs("\n", fdest) == -1)
5486 if (compose_redirect_write_headers(compose, fdest))
5489 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5490 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5503 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5505 GtkTextBuffer *buffer;
5506 GtkTextIter start, end;
5509 const gchar *out_codeset;
5510 EncodingType encoding = ENC_UNKNOWN;
5511 MimeInfo *mimemsg, *mimetext;
5513 const gchar *src_codeset = CS_INTERNAL;
5514 gchar *from_addr = NULL;
5515 gchar *from_name = NULL;
5517 if (action == COMPOSE_WRITE_FOR_SEND)
5518 attach_parts = TRUE;
5520 /* create message MimeInfo */
5521 mimemsg = procmime_mimeinfo_new();
5522 mimemsg->type = MIMETYPE_MESSAGE;
5523 mimemsg->subtype = g_strdup("rfc822");
5524 mimemsg->content = MIMECONTENT_MEM;
5525 mimemsg->tmp = TRUE; /* must free content later */
5526 mimemsg->data.mem = compose_get_header(compose);
5528 /* Create text part MimeInfo */
5529 /* get all composed text */
5530 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5531 gtk_text_buffer_get_start_iter(buffer, &start);
5532 gtk_text_buffer_get_end_iter(buffer, &end);
5533 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5535 out_codeset = conv_get_charset_str(compose->out_encoding);
5537 if (!out_codeset && is_ascii_str(chars)) {
5538 out_codeset = CS_US_ASCII;
5539 } else if (prefs_common.outgoing_fallback_to_ascii &&
5540 is_ascii_str(chars)) {
5541 out_codeset = CS_US_ASCII;
5542 encoding = ENC_7BIT;
5546 gchar *test_conv_global_out = NULL;
5547 gchar *test_conv_reply = NULL;
5549 /* automatic mode. be automatic. */
5550 codeconv_set_strict(TRUE);
5552 out_codeset = conv_get_outgoing_charset_str();
5554 debug_print("trying to convert to %s\n", out_codeset);
5555 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5558 if (!test_conv_global_out && compose->orig_charset
5559 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5560 out_codeset = compose->orig_charset;
5561 debug_print("failure; trying to convert to %s\n", out_codeset);
5562 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5565 if (!test_conv_global_out && !test_conv_reply) {
5567 out_codeset = CS_INTERNAL;
5568 debug_print("failure; finally using %s\n", out_codeset);
5570 g_free(test_conv_global_out);
5571 g_free(test_conv_reply);
5572 codeconv_set_strict(FALSE);
5575 if (encoding == ENC_UNKNOWN) {
5576 if (prefs_common.encoding_method == CTE_BASE64)
5577 encoding = ENC_BASE64;
5578 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5579 encoding = ENC_QUOTED_PRINTABLE;
5580 else if (prefs_common.encoding_method == CTE_8BIT)
5581 encoding = ENC_8BIT;
5583 encoding = procmime_get_encoding_for_charset(out_codeset);
5586 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5587 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5589 if (action == COMPOSE_WRITE_FOR_SEND) {
5590 codeconv_set_strict(TRUE);
5591 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5592 codeconv_set_strict(FALSE);
5598 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5599 "to the specified %s charset.\n"
5600 "Send it as %s?"), out_codeset, src_codeset);
5601 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5602 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5605 if (aval != G_ALERTALTERNATE) {
5610 out_codeset = src_codeset;
5616 out_codeset = src_codeset;
5621 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5622 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5623 strstr(buf, "\nFrom ") != NULL) {
5624 encoding = ENC_QUOTED_PRINTABLE;
5628 mimetext = procmime_mimeinfo_new();
5629 mimetext->content = MIMECONTENT_MEM;
5630 mimetext->tmp = TRUE; /* must free content later */
5631 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5632 * and free the data, which we need later. */
5633 mimetext->data.mem = g_strdup(buf);
5634 mimetext->type = MIMETYPE_TEXT;
5635 mimetext->subtype = g_strdup("plain");
5636 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5637 g_strdup(out_codeset));
5639 /* protect trailing spaces when signing message */
5640 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5641 privacy_system_can_sign(compose->privacy_system)) {
5642 encoding = ENC_QUOTED_PRINTABLE;
5645 debug_print("main text: %zd bytes encoded as %s in %d\n",
5646 strlen(buf), out_codeset, encoding);
5648 /* check for line length limit */
5649 if (action == COMPOSE_WRITE_FOR_SEND &&
5650 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5651 check_line_length(buf, 1000, &line) < 0) {
5655 msg = g_strdup_printf
5656 (_("Line %d exceeds the line length limit (998 bytes).\n"
5657 "The contents of the message might be broken on the way to the delivery.\n"
5659 "Send it anyway?"), line + 1);
5660 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5662 if (aval != G_ALERTALTERNATE) {
5668 if (encoding != ENC_UNKNOWN)
5669 procmime_encode_content(mimetext, encoding);
5671 /* append attachment parts */
5672 if (compose_use_attach(compose) && attach_parts) {
5673 MimeInfo *mimempart;
5674 gchar *boundary = NULL;
5675 mimempart = procmime_mimeinfo_new();
5676 mimempart->content = MIMECONTENT_EMPTY;
5677 mimempart->type = MIMETYPE_MULTIPART;
5678 mimempart->subtype = g_strdup("mixed");
5682 boundary = generate_mime_boundary(NULL);
5683 } while (strstr(buf, boundary) != NULL);
5685 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5688 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5690 g_node_append(mimempart->node, mimetext->node);
5691 g_node_append(mimemsg->node, mimempart->node);
5693 if (compose_add_attachments(compose, mimempart) < 0)
5696 g_node_append(mimemsg->node, mimetext->node);
5700 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5701 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5702 /* extract name and address */
5703 if (strstr(spec, " <") && strstr(spec, ">")) {
5704 from_addr = g_strdup(strrchr(spec, '<')+1);
5705 *(strrchr(from_addr, '>')) = '\0';
5706 from_name = g_strdup(spec);
5707 *(strrchr(from_name, '<')) = '\0';
5714 /* sign message if sending */
5715 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5716 privacy_system_can_sign(compose->privacy_system))
5717 if (!privacy_sign(compose->privacy_system, mimemsg,
5718 compose->account, from_addr)) {
5725 procmime_write_mimeinfo(mimemsg, fp);
5727 procmime_mimeinfo_free_all(mimemsg);
5732 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5734 GtkTextBuffer *buffer;
5735 GtkTextIter start, end;
5740 if ((fp = g_fopen(file, "wb")) == NULL) {
5741 FILE_OP_ERROR(file, "fopen");
5745 /* chmod for security */
5746 if (change_file_mode_rw(fp, file) < 0) {
5747 FILE_OP_ERROR(file, "chmod");
5748 g_warning("can't change file mode\n");
5751 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5752 gtk_text_buffer_get_start_iter(buffer, &start);
5753 gtk_text_buffer_get_end_iter(buffer, &end);
5754 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5756 chars = conv_codeset_strdup
5757 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5760 if (!chars) return -1;
5763 len = strlen(chars);
5764 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5765 FILE_OP_ERROR(file, "fwrite");
5774 if (fclose(fp) == EOF) {
5775 FILE_OP_ERROR(file, "fclose");
5782 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5785 MsgInfo *msginfo = compose->targetinfo;
5787 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5788 if (!msginfo) return -1;
5790 if (!force && MSG_IS_LOCKED(msginfo->flags))
5793 item = msginfo->folder;
5794 cm_return_val_if_fail(item != NULL, -1);
5796 if (procmsg_msg_exist(msginfo) &&
5797 (folder_has_parent_of_type(item, F_QUEUE) ||
5798 folder_has_parent_of_type(item, F_DRAFT)
5799 || msginfo == compose->autosaved_draft)) {
5800 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5801 g_warning("can't remove the old message\n");
5804 debug_print("removed reedit target %d\n", msginfo->msgnum);
5811 static void compose_remove_draft(Compose *compose)
5814 MsgInfo *msginfo = compose->targetinfo;
5815 drafts = account_get_special_folder(compose->account, F_DRAFT);
5817 if (procmsg_msg_exist(msginfo)) {
5818 folder_item_remove_msg(drafts, msginfo->msgnum);
5823 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5824 gboolean remove_reedit_target)
5826 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5829 static gboolean compose_warn_encryption(Compose *compose)
5831 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5832 AlertValue val = G_ALERTALTERNATE;
5834 if (warning == NULL)
5837 val = alertpanel_full(_("Encryption warning"), warning,
5838 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5839 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5840 if (val & G_ALERTDISABLE) {
5841 val &= ~G_ALERTDISABLE;
5842 if (val == G_ALERTALTERNATE)
5843 privacy_inhibit_encrypt_warning(compose->privacy_system,
5847 if (val == G_ALERTALTERNATE) {
5854 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5855 gchar **msgpath, gboolean check_subject,
5856 gboolean remove_reedit_target)
5863 PrefsAccount *mailac = NULL, *newsac = NULL;
5864 gboolean err = FALSE;
5866 debug_print("queueing message...\n");
5867 cm_return_val_if_fail(compose->account != NULL, -1);
5869 if (compose_check_entries(compose, check_subject) == FALSE) {
5870 if (compose->batch) {
5871 gtk_widget_show_all(compose->window);
5876 if (!compose->to_list && !compose->newsgroup_list) {
5877 g_warning("can't get recipient list.");
5881 if (compose->to_list) {
5882 if (compose->account->protocol != A_NNTP)
5883 mailac = compose->account;
5884 else if (cur_account && cur_account->protocol != A_NNTP)
5885 mailac = cur_account;
5886 else if (!(mailac = compose_current_mail_account())) {
5887 alertpanel_error(_("No account for sending mails available!"));
5892 if (compose->newsgroup_list) {
5893 if (compose->account->protocol == A_NNTP)
5894 newsac = compose->account;
5896 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5901 /* write queue header */
5902 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5903 G_DIR_SEPARATOR, compose, (guint) rand());
5904 debug_print("queuing to %s\n", tmp);
5905 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5906 FILE_OP_ERROR(tmp, "fopen");
5911 if (change_file_mode_rw(fp, tmp) < 0) {
5912 FILE_OP_ERROR(tmp, "chmod");
5913 g_warning("can't change file mode\n");
5916 /* queueing variables */
5917 err |= (fprintf(fp, "AF:\n") < 0);
5918 err |= (fprintf(fp, "NF:0\n") < 0);
5919 err |= (fprintf(fp, "PS:10\n") < 0);
5920 err |= (fprintf(fp, "SRH:1\n") < 0);
5921 err |= (fprintf(fp, "SFN:\n") < 0);
5922 err |= (fprintf(fp, "DSR:\n") < 0);
5924 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5926 err |= (fprintf(fp, "MID:\n") < 0);
5927 err |= (fprintf(fp, "CFG:\n") < 0);
5928 err |= (fprintf(fp, "PT:0\n") < 0);
5929 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5930 err |= (fprintf(fp, "RQ:\n") < 0);
5932 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5934 err |= (fprintf(fp, "SSV:\n") < 0);
5936 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5938 err |= (fprintf(fp, "NSV:\n") < 0);
5939 err |= (fprintf(fp, "SSH:\n") < 0);
5940 /* write recepient list */
5941 if (compose->to_list) {
5942 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5943 for (cur = compose->to_list->next; cur != NULL;
5945 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5946 err |= (fprintf(fp, "\n") < 0);
5948 /* write newsgroup list */
5949 if (compose->newsgroup_list) {
5950 err |= (fprintf(fp, "NG:") < 0);
5951 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5952 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5953 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5954 err |= (fprintf(fp, "\n") < 0);
5956 /* Sylpheed account IDs */
5958 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5960 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5963 if (compose->privacy_system != NULL) {
5964 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5965 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5966 if (compose->use_encryption) {
5968 if (!compose_warn_encryption(compose)) {
5974 if (mailac && mailac->encrypt_to_self) {
5975 GSList *tmp_list = g_slist_copy(compose->to_list);
5976 tmp_list = g_slist_append(tmp_list, compose->account->address);
5977 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5978 g_slist_free(tmp_list);
5980 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5982 if (encdata != NULL) {
5983 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5984 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5985 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5987 } /* else we finally dont want to encrypt */
5989 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5990 /* and if encdata was null, it means there's been a problem in
5993 g_warning("failed to write queue message");
6003 /* Save copy folder */
6004 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6005 gchar *savefolderid;
6007 savefolderid = compose_get_save_to(compose);
6008 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6009 g_free(savefolderid);
6011 /* Save copy folder */
6012 if (compose->return_receipt) {
6013 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6015 /* Message-ID of message replying to */
6016 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6017 gchar *folderid = NULL;
6019 if (compose->replyinfo->folder)
6020 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6021 if (folderid == NULL)
6022 folderid = g_strdup("NULL");
6024 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6027 /* Message-ID of message forwarding to */
6028 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6029 gchar *folderid = NULL;
6031 if (compose->fwdinfo->folder)
6032 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6033 if (folderid == NULL)
6034 folderid = g_strdup("NULL");
6036 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6040 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6041 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6043 /* end of headers */
6044 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6046 if (compose->redirect_filename != NULL) {
6047 if (compose_redirect_write_to_file(compose, fp) < 0) {
6055 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6059 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6063 g_warning("failed to write queue message\n");
6069 if (fclose(fp) == EOF) {
6070 FILE_OP_ERROR(tmp, "fclose");
6076 if (item && *item) {
6079 queue = account_get_special_folder(compose->account, F_QUEUE);
6082 g_warning("can't find queue folder\n");
6087 folder_item_scan(queue);
6088 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6089 g_warning("can't queue the message\n");
6095 if (msgpath == NULL) {
6101 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6102 compose_remove_reedit_target(compose, FALSE);
6105 if ((msgnum != NULL) && (item != NULL)) {
6113 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6116 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6118 struct stat statbuf;
6119 gchar *type, *subtype;
6120 GtkTreeModel *model;
6123 model = gtk_tree_view_get_model(tree_view);
6125 if (!gtk_tree_model_get_iter_first(model, &iter))
6128 gtk_tree_model_get(model, &iter,
6132 if (!is_file_exist(ainfo->file)) {
6133 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6134 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6135 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6137 if (val == G_ALERTDEFAULT) {
6142 mimepart = procmime_mimeinfo_new();
6143 mimepart->content = MIMECONTENT_FILE;
6144 mimepart->data.filename = g_strdup(ainfo->file);
6145 mimepart->tmp = FALSE; /* or we destroy our attachment */
6146 mimepart->offset = 0;
6148 g_stat(ainfo->file, &statbuf);
6149 mimepart->length = statbuf.st_size;
6151 type = g_strdup(ainfo->content_type);
6153 if (!strchr(type, '/')) {
6155 type = g_strdup("application/octet-stream");
6158 subtype = strchr(type, '/') + 1;
6159 *(subtype - 1) = '\0';
6160 mimepart->type = procmime_get_media_type(type);
6161 mimepart->subtype = g_strdup(subtype);
6164 if (mimepart->type == MIMETYPE_MESSAGE &&
6165 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6166 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6167 } else if (mimepart->type == MIMETYPE_TEXT) {
6168 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6169 /* Text parts with no name come from multipart/alternative
6170 * forwards. Make sure the recipient won't look at the
6171 * original HTML part by mistake. */
6172 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6173 ainfo->name = g_strdup_printf(_("Original %s part"),
6177 g_hash_table_insert(mimepart->typeparameters,
6178 g_strdup("charset"), g_strdup(ainfo->charset));
6180 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6181 if (mimepart->type == MIMETYPE_APPLICATION &&
6182 !strcmp2(mimepart->subtype, "octet-stream"))
6183 g_hash_table_insert(mimepart->typeparameters,
6184 g_strdup("name"), g_strdup(ainfo->name));
6185 g_hash_table_insert(mimepart->dispositionparameters,
6186 g_strdup("filename"), g_strdup(ainfo->name));
6187 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6190 if (mimepart->type == MIMETYPE_MESSAGE
6191 || mimepart->type == MIMETYPE_MULTIPART)
6192 ainfo->encoding = ENC_BINARY;
6193 else if (compose->use_signing) {
6194 if (ainfo->encoding == ENC_7BIT)
6195 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6196 else if (ainfo->encoding == ENC_8BIT)
6197 ainfo->encoding = ENC_BASE64;
6202 procmime_encode_content(mimepart, ainfo->encoding);
6204 g_node_append(parent->node, mimepart->node);
6205 } while (gtk_tree_model_iter_next(model, &iter));
6210 static gchar *compose_quote_list_of_addresses(gchar *str)
6212 GSList *list = NULL, *item = NULL;
6213 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6215 list = address_list_append_with_comments(list, str);
6216 for (item = list; item != NULL; item = item->next) {
6217 gchar *spec = item->data;
6218 gchar *endofname = strstr(spec, " <");
6219 if (endofname != NULL) {
6222 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6223 qqname = escape_internal_quotes(qname, '"');
6225 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6226 gchar *addr = g_strdup(endofname);
6227 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6228 faddr = g_strconcat(name, addr, NULL);
6231 debug_print("new auto-quoted address: '%s'", faddr);
6235 result = g_strdup((faddr != NULL)? faddr: spec);
6237 result = g_strconcat(result,
6239 (faddr != NULL)? faddr: spec,
6242 if (faddr != NULL) {
6247 slist_free_strings_full(list);
6252 #define IS_IN_CUSTOM_HEADER(header) \
6253 (compose->account->add_customhdr && \
6254 custom_header_find(compose->account->customhdr_list, header) != NULL)
6256 static void compose_add_headerfield_from_headerlist(Compose *compose,
6258 const gchar *fieldname,
6259 const gchar *seperator)
6261 gchar *str, *fieldname_w_colon;
6262 gboolean add_field = FALSE;
6264 ComposeHeaderEntry *headerentry;
6265 const gchar *headerentryname;
6266 const gchar *trans_fieldname;
6269 if (IS_IN_CUSTOM_HEADER(fieldname))
6272 debug_print("Adding %s-fields\n", fieldname);
6274 fieldstr = g_string_sized_new(64);
6276 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6277 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6279 for (list = compose->header_list; list; list = list->next) {
6280 headerentry = ((ComposeHeaderEntry *)list->data);
6281 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6283 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6284 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6286 str = compose_quote_list_of_addresses(ustr);
6288 if (str != NULL && str[0] != '\0') {
6290 g_string_append(fieldstr, seperator);
6291 g_string_append(fieldstr, str);
6300 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6301 compose_convert_header
6302 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6303 strlen(fieldname) + 2, TRUE);
6304 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6308 g_free(fieldname_w_colon);
6309 g_string_free(fieldstr, TRUE);
6314 static gchar *compose_get_manual_headers_info(Compose *compose)
6316 GString *sh_header = g_string_new(" ");
6318 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6320 for (list = compose->header_list; list; list = list->next) {
6321 ComposeHeaderEntry *headerentry;
6324 gchar *headername_wcolon;
6325 const gchar *headername_trans;
6327 gboolean standard_header = FALSE;
6329 headerentry = ((ComposeHeaderEntry *)list->data);
6331 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6333 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6338 if (!strstr(tmp, ":")) {
6339 headername_wcolon = g_strconcat(tmp, ":", NULL);
6340 headername = g_strdup(tmp);
6342 headername_wcolon = g_strdup(tmp);
6343 headername = g_strdup(strtok(tmp, ":"));
6347 string = std_headers;
6348 while (*string != NULL) {
6349 headername_trans = prefs_common_translated_header_name(*string);
6350 if (!strcmp(headername_trans, headername_wcolon))
6351 standard_header = TRUE;
6354 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6355 g_string_append_printf(sh_header, "%s ", headername);
6357 g_free(headername_wcolon);
6359 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6360 return g_string_free(sh_header, FALSE);
6363 static gchar *compose_get_header(Compose *compose)
6365 gchar buf[BUFFSIZE];
6366 const gchar *entry_str;
6370 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6372 gchar *from_name = NULL, *from_address = NULL;
6375 cm_return_val_if_fail(compose->account != NULL, NULL);
6376 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6378 header = g_string_sized_new(64);
6381 get_rfc822_date(buf, sizeof(buf));
6382 g_string_append_printf(header, "Date: %s\n", buf);
6386 if (compose->account->name && *compose->account->name) {
6388 QUOTE_IF_REQUIRED(buf, compose->account->name);
6389 tmp = g_strdup_printf("%s <%s>",
6390 buf, compose->account->address);
6392 tmp = g_strdup_printf("%s",
6393 compose->account->address);
6395 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6396 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6398 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6399 from_address = g_strdup(compose->account->address);
6401 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6402 /* extract name and address */
6403 if (strstr(spec, " <") && strstr(spec, ">")) {
6404 from_address = g_strdup(strrchr(spec, '<')+1);
6405 *(strrchr(from_address, '>')) = '\0';
6406 from_name = g_strdup(spec);
6407 *(strrchr(from_name, '<')) = '\0';
6410 from_address = g_strdup(spec);
6417 if (from_name && *from_name) {
6419 compose_convert_header
6420 (compose, buf, sizeof(buf), from_name,
6421 strlen("From: "), TRUE);
6422 QUOTE_IF_REQUIRED(name, buf);
6423 qname = escape_internal_quotes(name, '"');
6425 g_string_append_printf(header, "From: %s <%s>\n",
6426 qname, from_address);
6430 g_string_append_printf(header, "From: %s\n", from_address);
6433 g_free(from_address);
6436 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6439 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6442 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6446 * If this account is a NNTP account remove Bcc header from
6447 * message body since it otherwise will be publicly shown
6449 if (compose->account->protocol != A_NNTP)
6450 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6453 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6455 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6458 compose_convert_header(compose, buf, sizeof(buf), str,
6459 strlen("Subject: "), FALSE);
6460 g_string_append_printf(header, "Subject: %s\n", buf);
6466 if (compose->account->set_domain && compose->account->domain) {
6467 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6468 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6469 g_snprintf(buf, sizeof(buf), "%s",
6470 strchr(compose->account->address, '@') ?
6471 strchr(compose->account->address, '@')+1 :
6472 compose->account->address);
6474 g_snprintf(buf, sizeof(buf), "%s", "");
6477 if (compose->account->gen_msgid) {
6479 if (compose->account->msgid_with_addr) {
6480 addr = compose->account->address;
6482 generate_msgid(buf, sizeof(buf), addr);
6483 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6485 g_free(compose->msgid);
6486 compose->msgid = g_strdup(buf);
6488 compose->msgid = NULL;
6491 if (compose->remove_references == FALSE) {
6493 if (compose->inreplyto && compose->to_list)
6494 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6497 if (compose->references)
6498 g_string_append_printf(header, "References: %s\n", compose->references);
6502 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6505 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6508 if (compose->account->organization &&
6509 strlen(compose->account->organization) &&
6510 !IS_IN_CUSTOM_HEADER("Organization")) {
6511 compose_convert_header(compose, buf, sizeof(buf),
6512 compose->account->organization,
6513 strlen("Organization: "), FALSE);
6514 g_string_append_printf(header, "Organization: %s\n", buf);
6517 /* Program version and system info */
6518 if (compose->account->gen_xmailer &&
6519 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6520 !compose->newsgroup_list) {
6521 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6523 gtk_major_version, gtk_minor_version, gtk_micro_version,
6526 if (compose->account->gen_xmailer &&
6527 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6528 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6530 gtk_major_version, gtk_minor_version, gtk_micro_version,
6534 /* custom headers */
6535 if (compose->account->add_customhdr) {
6538 for (cur = compose->account->customhdr_list; cur != NULL;
6540 CustomHeader *chdr = (CustomHeader *)cur->data;
6542 if (custom_header_is_allowed(chdr->name)
6543 && chdr->value != NULL
6544 && *(chdr->value) != '\0') {
6545 compose_convert_header
6546 (compose, buf, sizeof(buf),
6548 strlen(chdr->name) + 2, FALSE);
6549 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6554 /* Automatic Faces and X-Faces */
6555 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6556 g_string_append_printf(header, "X-Face: %s\n", buf);
6558 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6559 g_string_append_printf(header, "X-Face: %s\n", buf);
6561 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6562 g_string_append_printf(header, "Face: %s\n", buf);
6564 else if (get_default_face (buf, sizeof(buf)) == 0) {
6565 g_string_append_printf(header, "Face: %s\n", buf);
6569 switch (compose->priority) {
6570 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6571 "X-Priority: 1 (Highest)\n");
6573 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6574 "X-Priority: 2 (High)\n");
6576 case PRIORITY_NORMAL: break;
6577 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6578 "X-Priority: 4 (Low)\n");
6580 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6581 "X-Priority: 5 (Lowest)\n");
6583 default: debug_print("compose: priority unknown : %d\n",
6587 /* Request Return Receipt */
6588 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6589 if (compose->return_receipt) {
6590 if (compose->account->name
6591 && *compose->account->name) {
6592 compose_convert_header(compose, buf, sizeof(buf),
6593 compose->account->name,
6594 strlen("Disposition-Notification-To: "),
6596 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6598 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6602 /* get special headers */
6603 for (list = compose->header_list; list; list = list->next) {
6604 ComposeHeaderEntry *headerentry;
6607 gchar *headername_wcolon;
6608 const gchar *headername_trans;
6611 gboolean standard_header = FALSE;
6613 headerentry = ((ComposeHeaderEntry *)list->data);
6615 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6617 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6622 if (!strstr(tmp, ":")) {
6623 headername_wcolon = g_strconcat(tmp, ":", NULL);
6624 headername = g_strdup(tmp);
6626 headername_wcolon = g_strdup(tmp);
6627 headername = g_strdup(strtok(tmp, ":"));
6631 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6632 Xstrdup_a(headervalue, entry_str, return NULL);
6633 subst_char(headervalue, '\r', ' ');
6634 subst_char(headervalue, '\n', ' ');
6635 string = std_headers;
6636 while (*string != NULL) {
6637 headername_trans = prefs_common_translated_header_name(*string);
6638 if (!strcmp(headername_trans, headername_wcolon))
6639 standard_header = TRUE;
6642 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6643 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6646 g_free(headername_wcolon);
6650 g_string_free(header, FALSE);
6655 #undef IS_IN_CUSTOM_HEADER
6657 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6658 gint header_len, gboolean addr_field)
6660 gchar *tmpstr = NULL;
6661 const gchar *out_codeset = NULL;
6663 cm_return_if_fail(src != NULL);
6664 cm_return_if_fail(dest != NULL);
6666 if (len < 1) return;
6668 tmpstr = g_strdup(src);
6670 subst_char(tmpstr, '\n', ' ');
6671 subst_char(tmpstr, '\r', ' ');
6674 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6675 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6676 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6681 codeconv_set_strict(TRUE);
6682 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6683 conv_get_charset_str(compose->out_encoding));
6684 codeconv_set_strict(FALSE);
6686 if (!dest || *dest == '\0') {
6687 gchar *test_conv_global_out = NULL;
6688 gchar *test_conv_reply = NULL;
6690 /* automatic mode. be automatic. */
6691 codeconv_set_strict(TRUE);
6693 out_codeset = conv_get_outgoing_charset_str();
6695 debug_print("trying to convert to %s\n", out_codeset);
6696 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6699 if (!test_conv_global_out && compose->orig_charset
6700 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6701 out_codeset = compose->orig_charset;
6702 debug_print("failure; trying to convert to %s\n", out_codeset);
6703 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6706 if (!test_conv_global_out && !test_conv_reply) {
6708 out_codeset = CS_INTERNAL;
6709 debug_print("finally using %s\n", out_codeset);
6711 g_free(test_conv_global_out);
6712 g_free(test_conv_reply);
6713 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6715 codeconv_set_strict(FALSE);
6720 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6724 cm_return_if_fail(user_data != NULL);
6726 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6727 g_strstrip(address);
6728 if (*address != '\0') {
6729 gchar *name = procheader_get_fromname(address);
6730 extract_address(address);
6731 #ifndef USE_NEW_ADDRBOOK
6732 addressbook_add_contact(name, address, NULL, NULL);
6734 debug_print("%s: %s\n", name, address);
6735 if (addressadd_selection(name, address, NULL, NULL)) {
6736 debug_print( "addressbook_add_contact - added\n" );
6743 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6745 GtkWidget *menuitem;
6748 cm_return_if_fail(menu != NULL);
6749 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6751 menuitem = gtk_separator_menu_item_new();
6752 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6753 gtk_widget_show(menuitem);
6755 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6756 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6758 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6759 g_strstrip(address);
6760 if (*address == '\0') {
6761 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6764 g_signal_connect(G_OBJECT(menuitem), "activate",
6765 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6766 gtk_widget_show(menuitem);
6769 void compose_add_extra_header(gchar *header, GtkListStore *model)
6772 if (strcmp(header, "")) {
6773 COMBOBOX_ADD(model, header, COMPOSE_TO);
6777 void compose_add_extra_header_entries(GtkListStore *model)
6781 gchar buf[BUFFSIZE];
6784 if (extra_headers == NULL) {
6785 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6786 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6787 debug_print("extra headers file not found\n");
6788 goto extra_headers_done;
6790 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6791 lastc = strlen(buf) - 1; /* remove trailing control chars */
6792 while (lastc >= 0 && buf[lastc] != ':')
6793 buf[lastc--] = '\0';
6794 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6795 buf[lastc] = '\0'; /* remove trailing : for comparison */
6796 if (custom_header_is_allowed(buf)) {
6798 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6801 g_message("disallowed extra header line: %s\n", buf);
6805 g_message("invalid extra header line: %s\n", buf);
6811 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6812 extra_headers = g_slist_reverse(extra_headers);
6814 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6817 static void compose_create_header_entry(Compose *compose)
6819 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6826 const gchar *header = NULL;
6827 ComposeHeaderEntry *headerentry;
6828 gboolean standard_header = FALSE;
6829 GtkListStore *model;
6831 #if !(GTK_CHECK_VERSION(2,12,0))
6832 GtkTooltips *tips = compose->tooltips;
6835 headerentry = g_new0(ComposeHeaderEntry, 1);
6837 /* Combo box model */
6838 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6839 #if !GTK_CHECK_VERSION(2, 24, 0)
6840 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6842 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6844 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6846 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6848 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6849 COMPOSE_NEWSGROUPS);
6850 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6852 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6853 COMPOSE_FOLLOWUPTO);
6854 compose_add_extra_header_entries(model);
6857 #if GTK_CHECK_VERSION(2, 24, 0)
6858 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6859 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6860 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6861 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6862 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6864 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6865 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6866 G_CALLBACK(compose_grab_focus_cb), compose);
6867 gtk_widget_show(combo);
6870 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6871 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6874 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6875 compose->header_nextrow, compose->header_nextrow+1,
6876 GTK_SHRINK, GTK_FILL, 0, 0);
6877 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6878 const gchar *last_header_entry = gtk_entry_get_text(
6879 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6881 while (*string != NULL) {
6882 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6883 standard_header = TRUE;
6886 if (standard_header)
6887 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6889 if (!compose->header_last || !standard_header) {
6890 switch(compose->account->protocol) {
6892 header = prefs_common_translated_header_name("Newsgroups:");
6895 header = prefs_common_translated_header_name("To:");
6900 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6902 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6903 G_CALLBACK(compose_grab_focus_cb), compose);
6905 /* Entry field with cleanup button */
6906 button = gtk_button_new();
6907 gtk_button_set_image(GTK_BUTTON(button),
6908 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6909 gtk_widget_show(button);
6910 CLAWS_SET_TIP(button,
6911 _("Delete entry contents"));
6912 entry = gtk_entry_new();
6913 gtk_widget_show(entry);
6914 CLAWS_SET_TIP(entry,
6915 _("Use <tab> to autocomplete from addressbook"));
6916 hbox = gtk_hbox_new (FALSE, 0);
6917 gtk_widget_show(hbox);
6918 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6919 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6920 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6921 compose->header_nextrow, compose->header_nextrow+1,
6922 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6924 g_signal_connect(G_OBJECT(entry), "key-press-event",
6925 G_CALLBACK(compose_headerentry_key_press_event_cb),
6927 g_signal_connect(G_OBJECT(entry), "changed",
6928 G_CALLBACK(compose_headerentry_changed_cb),
6930 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6931 G_CALLBACK(compose_grab_focus_cb), compose);
6933 g_signal_connect(G_OBJECT(button), "clicked",
6934 G_CALLBACK(compose_headerentry_button_clicked_cb),
6938 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6939 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6940 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6941 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6942 G_CALLBACK(compose_header_drag_received_cb),
6944 g_signal_connect(G_OBJECT(entry), "drag-drop",
6945 G_CALLBACK(compose_drag_drop),
6947 g_signal_connect(G_OBJECT(entry), "populate-popup",
6948 G_CALLBACK(compose_entry_popup_extend),
6951 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6953 headerentry->compose = compose;
6954 headerentry->combo = combo;
6955 headerentry->entry = entry;
6956 headerentry->button = button;
6957 headerentry->hbox = hbox;
6958 headerentry->headernum = compose->header_nextrow;
6959 headerentry->type = PREF_NONE;
6961 compose->header_nextrow++;
6962 compose->header_last = headerentry;
6963 compose->header_list =
6964 g_slist_append(compose->header_list,
6968 static void compose_add_header_entry(Compose *compose, const gchar *header,
6969 gchar *text, ComposePrefType pref_type)
6971 ComposeHeaderEntry *last_header = compose->header_last;
6972 gchar *tmp = g_strdup(text), *email;
6973 gboolean replyto_hdr;
6975 replyto_hdr = (!strcasecmp(header,
6976 prefs_common_translated_header_name("Reply-To:")) ||
6978 prefs_common_translated_header_name("Followup-To:")) ||
6980 prefs_common_translated_header_name("In-Reply-To:")));
6982 extract_address(tmp);
6983 email = g_utf8_strdown(tmp, -1);
6985 if (replyto_hdr == FALSE &&
6986 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6988 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6989 header, text, (gint) pref_type);
6995 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6996 gtk_entry_set_text(GTK_ENTRY(
6997 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6999 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7000 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7001 last_header->type = pref_type;
7003 if (replyto_hdr == FALSE)
7004 g_hash_table_insert(compose->email_hashtable, email,
7005 GUINT_TO_POINTER(1));
7012 static void compose_destroy_headerentry(Compose *compose,
7013 ComposeHeaderEntry *headerentry)
7015 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7018 extract_address(text);
7019 email = g_utf8_strdown(text, -1);
7020 g_hash_table_remove(compose->email_hashtable, email);
7024 gtk_widget_destroy(headerentry->combo);
7025 gtk_widget_destroy(headerentry->entry);
7026 gtk_widget_destroy(headerentry->button);
7027 gtk_widget_destroy(headerentry->hbox);
7028 g_free(headerentry);
7031 static void compose_remove_header_entries(Compose *compose)
7034 for (list = compose->header_list; list; list = list->next)
7035 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7037 compose->header_last = NULL;
7038 g_slist_free(compose->header_list);
7039 compose->header_list = NULL;
7040 compose->header_nextrow = 1;
7041 compose_create_header_entry(compose);
7044 static GtkWidget *compose_create_header(Compose *compose)
7046 GtkWidget *from_optmenu_hbox;
7047 GtkWidget *header_scrolledwin_main;
7048 GtkWidget *header_table_main;
7049 GtkWidget *header_scrolledwin;
7050 GtkWidget *header_table;
7052 /* parent with account selection and from header */
7053 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7054 gtk_widget_show(header_scrolledwin_main);
7055 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7057 header_table_main = gtk_table_new(2, 2, FALSE);
7058 gtk_widget_show(header_table_main);
7059 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7060 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7061 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7063 from_optmenu_hbox = compose_account_option_menu_create(compose);
7064 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7065 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7067 /* child with header labels and entries */
7068 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7069 gtk_widget_show(header_scrolledwin);
7070 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7072 header_table = gtk_table_new(2, 2, FALSE);
7073 gtk_widget_show(header_table);
7074 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7075 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7076 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7078 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7079 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7081 compose->header_table = header_table;
7082 compose->header_list = NULL;
7083 compose->header_nextrow = 0;
7085 compose_create_header_entry(compose);
7087 compose->table = NULL;
7089 return header_scrolledwin_main;
7092 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7094 Compose *compose = (Compose *)data;
7095 GdkEventButton event;
7098 event.time = gtk_get_current_event_time();
7100 return attach_button_pressed(compose->attach_clist, &event, compose);
7103 static GtkWidget *compose_create_attach(Compose *compose)
7105 GtkWidget *attach_scrwin;
7106 GtkWidget *attach_clist;
7108 GtkListStore *store;
7109 GtkCellRenderer *renderer;
7110 GtkTreeViewColumn *column;
7111 GtkTreeSelection *selection;
7113 /* attachment list */
7114 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7115 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7116 GTK_POLICY_AUTOMATIC,
7117 GTK_POLICY_AUTOMATIC);
7118 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7120 store = gtk_list_store_new(N_ATTACH_COLS,
7126 G_TYPE_AUTO_POINTER,
7128 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7129 (GTK_TREE_MODEL(store)));
7130 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7131 g_object_unref(store);
7133 renderer = gtk_cell_renderer_text_new();
7134 column = gtk_tree_view_column_new_with_attributes
7135 (_("Mime type"), renderer, "text",
7136 COL_MIMETYPE, NULL);
7137 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7139 renderer = gtk_cell_renderer_text_new();
7140 column = gtk_tree_view_column_new_with_attributes
7141 (_("Size"), renderer, "text",
7143 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7145 renderer = gtk_cell_renderer_text_new();
7146 column = gtk_tree_view_column_new_with_attributes
7147 (_("Name"), renderer, "text",
7149 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7151 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7152 prefs_common.use_stripes_everywhere);
7153 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7154 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7156 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7157 G_CALLBACK(attach_selected), compose);
7158 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7159 G_CALLBACK(attach_button_pressed), compose);
7160 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7161 G_CALLBACK(popup_attach_button_pressed), compose);
7162 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7163 G_CALLBACK(attach_key_pressed), compose);
7166 gtk_drag_dest_set(attach_clist,
7167 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7168 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7169 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7170 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7171 G_CALLBACK(compose_attach_drag_received_cb),
7173 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7174 G_CALLBACK(compose_drag_drop),
7177 compose->attach_scrwin = attach_scrwin;
7178 compose->attach_clist = attach_clist;
7180 return attach_scrwin;
7183 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7184 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7186 static GtkWidget *compose_create_others(Compose *compose)
7189 GtkWidget *savemsg_checkbtn;
7190 GtkWidget *savemsg_combo;
7191 GtkWidget *savemsg_select;
7194 gchar *folderidentifier;
7196 /* Table for settings */
7197 table = gtk_table_new(3, 1, FALSE);
7198 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7199 gtk_widget_show(table);
7200 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7203 /* Save Message to folder */
7204 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7205 gtk_widget_show(savemsg_checkbtn);
7206 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7207 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7208 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7210 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7211 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7213 #if !GTK_CHECK_VERSION(2, 24, 0)
7214 savemsg_combo = gtk_combo_box_entry_new_text();
7216 savemsg_combo = gtk_combo_box_text_new_with_entry();
7218 compose->savemsg_checkbtn = savemsg_checkbtn;
7219 compose->savemsg_combo = savemsg_combo;
7220 gtk_widget_show(savemsg_combo);
7222 if (prefs_common.compose_save_to_history)
7223 #if !GTK_CHECK_VERSION(2, 24, 0)
7224 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7225 prefs_common.compose_save_to_history);
7227 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7228 prefs_common.compose_save_to_history);
7230 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7231 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7232 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7233 G_CALLBACK(compose_grab_focus_cb), compose);
7234 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7235 folderidentifier = folder_item_get_identifier(account_get_special_folder
7236 (compose->account, F_OUTBOX));
7237 compose_set_save_to(compose, folderidentifier);
7238 g_free(folderidentifier);
7241 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7242 gtk_widget_show(savemsg_select);
7243 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7244 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7245 G_CALLBACK(compose_savemsg_select_cb),
7251 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7253 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7254 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7257 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7262 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7265 path = folder_item_get_identifier(dest);
7267 compose_set_save_to(compose, path);
7271 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7272 GdkAtom clip, GtkTextIter *insert_place);
7275 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7279 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7281 if (event->button == 3) {
7283 GtkTextIter sel_start, sel_end;
7284 gboolean stuff_selected;
7286 /* move the cursor to allow GtkAspell to check the word
7287 * under the mouse */
7288 if (event->x && event->y) {
7289 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7290 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7292 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7295 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7296 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7299 stuff_selected = gtk_text_buffer_get_selection_bounds(
7301 &sel_start, &sel_end);
7303 gtk_text_buffer_place_cursor (buffer, &iter);
7304 /* reselect stuff */
7306 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7307 gtk_text_buffer_select_range(buffer,
7308 &sel_start, &sel_end);
7310 return FALSE; /* pass the event so that the right-click goes through */
7313 if (event->button == 2) {
7318 /* get the middle-click position to paste at the correct place */
7319 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7320 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7322 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7325 entry_paste_clipboard(compose, text,
7326 prefs_common.linewrap_pastes,
7327 GDK_SELECTION_PRIMARY, &iter);
7335 static void compose_spell_menu_changed(void *data)
7337 Compose *compose = (Compose *)data;
7339 GtkWidget *menuitem;
7340 GtkWidget *parent_item;
7341 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7344 if (compose->gtkaspell == NULL)
7347 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7348 "/Menu/Spelling/Options");
7350 /* setting the submenu removes /Spelling/Options from the factory
7351 * so we need to save it */
7353 if (parent_item == NULL) {
7354 parent_item = compose->aspell_options_menu;
7355 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7357 compose->aspell_options_menu = parent_item;
7359 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7361 spell_menu = g_slist_reverse(spell_menu);
7362 for (items = spell_menu;
7363 items; items = items->next) {
7364 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7365 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7366 gtk_widget_show(GTK_WIDGET(menuitem));
7368 g_slist_free(spell_menu);
7370 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7371 gtk_widget_show(parent_item);
7374 static void compose_dict_changed(void *data)
7376 Compose *compose = (Compose *) data;
7378 if(compose->gtkaspell &&
7379 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7382 gtkaspell_highlight_all(compose->gtkaspell);
7383 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7387 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7389 Compose *compose = (Compose *)data;
7390 GdkEventButton event;
7393 event.time = gtk_get_current_event_time();
7397 return text_clicked(compose->text, &event, compose);
7400 static gboolean compose_force_window_origin = TRUE;
7401 static Compose *compose_create(PrefsAccount *account,
7410 GtkWidget *handlebox;
7412 GtkWidget *notebook;
7414 GtkWidget *attach_hbox;
7415 GtkWidget *attach_lab1;
7416 GtkWidget *attach_lab2;
7421 GtkWidget *subject_hbox;
7422 GtkWidget *subject_frame;
7423 GtkWidget *subject_entry;
7427 GtkWidget *edit_vbox;
7428 GtkWidget *ruler_hbox;
7430 GtkWidget *scrolledwin;
7432 GtkTextBuffer *buffer;
7433 GtkClipboard *clipboard;
7435 UndoMain *undostruct;
7437 GtkWidget *popupmenu;
7438 GtkWidget *tmpl_menu;
7439 GtkActionGroup *action_group = NULL;
7442 GtkAspell * gtkaspell = NULL;
7445 static GdkGeometry geometry;
7447 cm_return_val_if_fail(account != NULL, NULL);
7449 debug_print("Creating compose window...\n");
7450 compose = g_new0(Compose, 1);
7452 compose->batch = batch;
7453 compose->account = account;
7454 compose->folder = folder;
7456 compose->mutex = cm_mutex_new();
7457 compose->set_cursor_pos = -1;
7459 #if !(GTK_CHECK_VERSION(2,12,0))
7460 compose->tooltips = tips;
7463 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7465 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7466 gtk_widget_set_size_request(window, prefs_common.compose_width,
7467 prefs_common.compose_height);
7469 if (!geometry.max_width) {
7470 geometry.max_width = gdk_screen_width();
7471 geometry.max_height = gdk_screen_height();
7474 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7475 &geometry, GDK_HINT_MAX_SIZE);
7476 if (!geometry.min_width) {
7477 geometry.min_width = 600;
7478 geometry.min_height = 440;
7480 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7481 &geometry, GDK_HINT_MIN_SIZE);
7483 #ifndef GENERIC_UMPC
7484 if (compose_force_window_origin)
7485 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7486 prefs_common.compose_y);
7488 g_signal_connect(G_OBJECT(window), "delete_event",
7489 G_CALLBACK(compose_delete_cb), compose);
7490 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7491 gtk_widget_realize(window);
7493 gtkut_widget_set_composer_icon(window);
7495 vbox = gtk_vbox_new(FALSE, 0);
7496 gtk_container_add(GTK_CONTAINER(window), vbox);
7498 compose->ui_manager = gtk_ui_manager_new();
7499 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7500 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7501 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7502 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7503 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7504 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7505 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7506 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7507 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7508 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7587 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7609 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7626 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7627 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)
7628 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)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7633 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7634 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)
7635 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)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7640 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)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7644 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)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7650 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)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7652 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7655 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7656 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)
7657 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)
7658 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7665 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7668 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7669 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7670 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)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7673 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7674 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7682 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7683 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7686 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7688 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7689 gtk_widget_show_all(menubar);
7691 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7692 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7694 if (prefs_common.toolbar_detachable) {
7695 handlebox = gtk_handle_box_new();
7697 handlebox = gtk_hbox_new(FALSE, 0);
7699 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7701 gtk_widget_realize(handlebox);
7702 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7705 vbox2 = gtk_vbox_new(FALSE, 2);
7706 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7707 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7710 notebook = gtk_notebook_new();
7711 gtk_widget_show(notebook);
7713 /* header labels and entries */
7714 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7715 compose_create_header(compose),
7716 gtk_label_new_with_mnemonic(_("Hea_der")));
7717 /* attachment list */
7718 attach_hbox = gtk_hbox_new(FALSE, 0);
7719 gtk_widget_show(attach_hbox);
7721 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7722 gtk_widget_show(attach_lab1);
7723 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7725 attach_lab2 = gtk_label_new("");
7726 gtk_widget_show(attach_lab2);
7727 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7729 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7730 compose_create_attach(compose),
7733 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7734 compose_create_others(compose),
7735 gtk_label_new_with_mnemonic(_("Othe_rs")));
7738 subject_hbox = gtk_hbox_new(FALSE, 0);
7739 gtk_widget_show(subject_hbox);
7741 subject_frame = gtk_frame_new(NULL);
7742 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7743 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7744 gtk_widget_show(subject_frame);
7746 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7747 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7748 gtk_widget_show(subject);
7750 label = gtk_label_new_with_mnemonic(_("Subject:"));
7751 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7752 gtk_widget_show(label);
7755 subject_entry = claws_spell_entry_new();
7757 subject_entry = gtk_entry_new();
7759 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7760 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7761 G_CALLBACK(compose_grab_focus_cb), compose);
7762 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7763 gtk_widget_show(subject_entry);
7764 compose->subject_entry = subject_entry;
7765 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7767 edit_vbox = gtk_vbox_new(FALSE, 0);
7769 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7772 ruler_hbox = gtk_hbox_new(FALSE, 0);
7773 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7775 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7776 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7777 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7781 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7782 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7783 GTK_POLICY_AUTOMATIC,
7784 GTK_POLICY_AUTOMATIC);
7785 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7787 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7789 text = gtk_text_view_new();
7790 if (prefs_common.show_compose_margin) {
7791 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7792 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7794 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7795 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7796 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7797 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7798 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7800 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7801 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7802 G_CALLBACK(compose_edit_size_alloc),
7804 g_signal_connect(G_OBJECT(buffer), "changed",
7805 G_CALLBACK(compose_changed_cb), compose);
7806 g_signal_connect(G_OBJECT(text), "grab_focus",
7807 G_CALLBACK(compose_grab_focus_cb), compose);
7808 g_signal_connect(G_OBJECT(buffer), "insert_text",
7809 G_CALLBACK(text_inserted), compose);
7810 g_signal_connect(G_OBJECT(text), "button_press_event",
7811 G_CALLBACK(text_clicked), compose);
7812 g_signal_connect(G_OBJECT(text), "popup-menu",
7813 G_CALLBACK(compose_popup_menu), compose);
7814 g_signal_connect(G_OBJECT(subject_entry), "changed",
7815 G_CALLBACK(compose_changed_cb), compose);
7816 g_signal_connect(G_OBJECT(subject_entry), "activate",
7817 G_CALLBACK(compose_subject_entry_activated), compose);
7820 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7821 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7822 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7823 g_signal_connect(G_OBJECT(text), "drag_data_received",
7824 G_CALLBACK(compose_insert_drag_received_cb),
7826 g_signal_connect(G_OBJECT(text), "drag-drop",
7827 G_CALLBACK(compose_drag_drop),
7829 g_signal_connect(G_OBJECT(text), "key-press-event",
7830 G_CALLBACK(completion_set_focus_to_subject),
7832 gtk_widget_show_all(vbox);
7834 /* pane between attach clist and text */
7835 paned = gtk_vpaned_new();
7836 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7837 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7838 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7839 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7840 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7841 G_CALLBACK(compose_notebook_size_alloc), paned);
7843 gtk_widget_show_all(paned);
7846 if (prefs_common.textfont) {
7847 PangoFontDescription *font_desc;
7849 font_desc = pango_font_description_from_string
7850 (prefs_common.textfont);
7852 gtk_widget_modify_font(text, font_desc);
7853 pango_font_description_free(font_desc);
7857 gtk_action_group_add_actions(action_group, compose_popup_entries,
7858 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7863 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7864 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7866 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7868 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7869 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7870 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7872 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7874 undostruct = undo_init(text);
7875 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7878 address_completion_start(window);
7880 compose->window = window;
7881 compose->vbox = vbox;
7882 compose->menubar = menubar;
7883 compose->handlebox = handlebox;
7885 compose->vbox2 = vbox2;
7887 compose->paned = paned;
7889 compose->attach_label = attach_lab2;
7891 compose->notebook = notebook;
7892 compose->edit_vbox = edit_vbox;
7893 compose->ruler_hbox = ruler_hbox;
7894 compose->ruler = ruler;
7895 compose->scrolledwin = scrolledwin;
7896 compose->text = text;
7898 compose->focused_editable = NULL;
7900 compose->popupmenu = popupmenu;
7902 compose->tmpl_menu = tmpl_menu;
7904 compose->mode = mode;
7905 compose->rmode = mode;
7907 compose->targetinfo = NULL;
7908 compose->replyinfo = NULL;
7909 compose->fwdinfo = NULL;
7911 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7912 g_str_equal, (GDestroyNotify) g_free, NULL);
7914 compose->replyto = NULL;
7916 compose->bcc = NULL;
7917 compose->followup_to = NULL;
7919 compose->ml_post = NULL;
7921 compose->inreplyto = NULL;
7922 compose->references = NULL;
7923 compose->msgid = NULL;
7924 compose->boundary = NULL;
7926 compose->autowrap = prefs_common.autowrap;
7927 compose->autoindent = prefs_common.auto_indent;
7928 compose->use_signing = FALSE;
7929 compose->use_encryption = FALSE;
7930 compose->privacy_system = NULL;
7932 compose->modified = FALSE;
7934 compose->return_receipt = FALSE;
7936 compose->to_list = NULL;
7937 compose->newsgroup_list = NULL;
7939 compose->undostruct = undostruct;
7941 compose->sig_str = NULL;
7943 compose->exteditor_file = NULL;
7944 compose->exteditor_pid = -1;
7945 compose->exteditor_tag = -1;
7946 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7948 compose->folder_update_callback_id =
7949 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
7950 compose_update_folder_hook,
7951 (gpointer) compose);
7954 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7955 if (mode != COMPOSE_REDIRECT) {
7956 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7957 strcmp(prefs_common.dictionary, "")) {
7958 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7959 prefs_common.alt_dictionary,
7960 conv_get_locale_charset_str(),
7961 prefs_common.misspelled_col,
7962 prefs_common.check_while_typing,
7963 prefs_common.recheck_when_changing_dict,
7964 prefs_common.use_alternate,
7965 prefs_common.use_both_dicts,
7966 GTK_TEXT_VIEW(text),
7967 GTK_WINDOW(compose->window),
7968 compose_dict_changed,
7969 compose_spell_menu_changed,
7972 alertpanel_error(_("Spell checker could not "
7974 gtkaspell_checkers_strerror());
7975 gtkaspell_checkers_reset_error();
7977 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7981 compose->gtkaspell = gtkaspell;
7982 compose_spell_menu_changed(compose);
7983 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7986 compose_select_account(compose, account, TRUE);
7988 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7989 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7991 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7992 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7994 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7995 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7997 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7998 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8000 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
8001 if (account->protocol != A_NNTP)
8002 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8003 prefs_common_translated_header_name("To:"));
8005 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8006 prefs_common_translated_header_name("Newsgroups:"));
8008 #ifndef USE_NEW_ADDRBOOK
8009 addressbook_set_target_compose(compose);
8011 if (mode != COMPOSE_REDIRECT)
8012 compose_set_template_menu(compose);
8014 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8017 compose_list = g_list_append(compose_list, compose);
8019 if (!prefs_common.show_ruler)
8020 gtk_widget_hide(ruler_hbox);
8022 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8025 compose->priority = PRIORITY_NORMAL;
8026 compose_update_priority_menu_item(compose);
8028 compose_set_out_encoding(compose);
8031 compose_update_actions_menu(compose);
8033 /* Privacy Systems menu */
8034 compose_update_privacy_systems_menu(compose);
8036 activate_privacy_system(compose, account, TRUE);
8037 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8039 gtk_widget_realize(window);
8041 gtk_widget_show(window);
8047 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8052 GtkWidget *optmenubox;
8055 GtkWidget *from_name = NULL;
8056 #if !(GTK_CHECK_VERSION(2,12,0))
8057 GtkTooltips *tips = compose->tooltips;
8060 gint num = 0, def_menu = 0;
8062 accounts = account_get_list();
8063 cm_return_val_if_fail(accounts != NULL, NULL);
8065 optmenubox = gtk_event_box_new();
8066 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8067 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8069 hbox = gtk_hbox_new(FALSE, 6);
8070 from_name = gtk_entry_new();
8072 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8073 G_CALLBACK(compose_grab_focus_cb), compose);
8075 for (; accounts != NULL; accounts = accounts->next, num++) {
8076 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8077 gchar *name, *from = NULL;
8079 if (ac == compose->account) def_menu = num;
8081 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8084 if (ac == compose->account) {
8085 if (ac->name && *ac->name) {
8087 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8088 from = g_strdup_printf("%s <%s>",
8090 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8092 from = g_strdup_printf("%s",
8094 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8097 COMBOBOX_ADD(menu, name, ac->account_id);
8102 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8104 g_signal_connect(G_OBJECT(optmenu), "changed",
8105 G_CALLBACK(account_activated),
8107 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8108 G_CALLBACK(compose_entry_popup_extend),
8111 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8112 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8114 CLAWS_SET_TIP(optmenubox,
8115 _("Account to use for this email"));
8116 CLAWS_SET_TIP(from_name,
8117 _("Sender address to be used"));
8119 compose->account_combo = optmenu;
8120 compose->from_name = from_name;
8125 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8127 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8128 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8129 Compose *compose = (Compose *) data;
8131 compose->priority = value;
8135 static void compose_reply_change_mode(Compose *compose,
8138 gboolean was_modified = compose->modified;
8140 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8142 cm_return_if_fail(compose->replyinfo != NULL);
8144 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8146 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8148 if (action == COMPOSE_REPLY_TO_ALL)
8150 if (action == COMPOSE_REPLY_TO_SENDER)
8152 if (action == COMPOSE_REPLY_TO_LIST)
8155 compose_remove_header_entries(compose);
8156 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8157 if (compose->account->set_autocc && compose->account->auto_cc)
8158 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8160 if (compose->account->set_autobcc && compose->account->auto_bcc)
8161 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8163 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8164 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8165 compose_show_first_last_header(compose, TRUE);
8166 compose->modified = was_modified;
8167 compose_set_title(compose);
8170 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8172 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8173 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8174 Compose *compose = (Compose *) data;
8177 compose_reply_change_mode(compose, value);
8180 static void compose_update_priority_menu_item(Compose * compose)
8182 GtkWidget *menuitem = NULL;
8183 switch (compose->priority) {
8184 case PRIORITY_HIGHEST:
8185 menuitem = gtk_ui_manager_get_widget
8186 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8189 menuitem = gtk_ui_manager_get_widget
8190 (compose->ui_manager, "/Menu/Options/Priority/High");
8192 case PRIORITY_NORMAL:
8193 menuitem = gtk_ui_manager_get_widget
8194 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8197 menuitem = gtk_ui_manager_get_widget
8198 (compose->ui_manager, "/Menu/Options/Priority/Low");
8200 case PRIORITY_LOWEST:
8201 menuitem = gtk_ui_manager_get_widget
8202 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8205 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8208 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8210 Compose *compose = (Compose *) data;
8212 gboolean can_sign = FALSE, can_encrypt = FALSE;
8214 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8216 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8219 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8220 g_free(compose->privacy_system);
8221 compose->privacy_system = NULL;
8222 if (systemid != NULL) {
8223 compose->privacy_system = g_strdup(systemid);
8225 can_sign = privacy_system_can_sign(systemid);
8226 can_encrypt = privacy_system_can_encrypt(systemid);
8229 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8231 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8232 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8235 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8237 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8238 GtkWidget *menuitem = NULL;
8239 GList *children, *amenu;
8240 gboolean can_sign = FALSE, can_encrypt = FALSE;
8241 gboolean found = FALSE;
8243 if (compose->privacy_system != NULL) {
8245 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8246 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8247 cm_return_if_fail(menuitem != NULL);
8249 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8252 while (amenu != NULL) {
8253 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8254 if (systemid != NULL) {
8255 if (strcmp(systemid, compose->privacy_system) == 0 &&
8256 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8257 menuitem = GTK_WIDGET(amenu->data);
8259 can_sign = privacy_system_can_sign(systemid);
8260 can_encrypt = privacy_system_can_encrypt(systemid);
8264 } else if (strlen(compose->privacy_system) == 0 &&
8265 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8266 menuitem = GTK_WIDGET(amenu->data);
8269 can_encrypt = FALSE;
8274 amenu = amenu->next;
8276 g_list_free(children);
8277 if (menuitem != NULL)
8278 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8280 if (warn && !found && strlen(compose->privacy_system)) {
8281 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8282 "will not be able to sign or encrypt this message."),
8283 compose->privacy_system);
8287 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8288 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8291 static void compose_set_out_encoding(Compose *compose)
8293 CharSet out_encoding;
8294 const gchar *branch = NULL;
8295 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8297 switch(out_encoding) {
8298 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8299 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8300 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8301 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8302 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8303 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8304 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8305 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8306 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8307 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8308 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8309 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8310 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8311 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8312 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8313 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8314 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8315 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8316 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8317 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8318 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8319 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8320 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8321 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8322 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8323 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8324 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8325 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8326 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8327 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8328 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8329 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8330 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8332 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8335 static void compose_set_template_menu(Compose *compose)
8337 GSList *tmpl_list, *cur;
8341 tmpl_list = template_get_config();
8343 menu = gtk_menu_new();
8345 gtk_menu_set_accel_group (GTK_MENU (menu),
8346 gtk_ui_manager_get_accel_group(compose->ui_manager));
8347 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8348 Template *tmpl = (Template *)cur->data;
8349 gchar *accel_path = NULL;
8350 item = gtk_menu_item_new_with_label(tmpl->name);
8351 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8352 g_signal_connect(G_OBJECT(item), "activate",
8353 G_CALLBACK(compose_template_activate_cb),
8355 g_object_set_data(G_OBJECT(item), "template", tmpl);
8356 gtk_widget_show(item);
8357 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8358 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8362 gtk_widget_show(menu);
8363 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8366 void compose_update_actions_menu(Compose *compose)
8368 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8371 static void compose_update_privacy_systems_menu(Compose *compose)
8373 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8374 GSList *systems, *cur;
8376 GtkWidget *system_none;
8378 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8379 GtkWidget *privacy_menu = gtk_menu_new();
8381 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8382 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8384 g_signal_connect(G_OBJECT(system_none), "activate",
8385 G_CALLBACK(compose_set_privacy_system_cb), compose);
8387 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8388 gtk_widget_show(system_none);
8390 systems = privacy_get_system_ids();
8391 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8392 gchar *systemid = cur->data;
8394 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8395 widget = gtk_radio_menu_item_new_with_label(group,
8396 privacy_system_get_name(systemid));
8397 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8398 g_strdup(systemid), g_free);
8399 g_signal_connect(G_OBJECT(widget), "activate",
8400 G_CALLBACK(compose_set_privacy_system_cb), compose);
8402 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8403 gtk_widget_show(widget);
8406 g_slist_free(systems);
8407 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8408 gtk_widget_show_all(privacy_menu);
8409 gtk_widget_show_all(privacy_menuitem);
8412 void compose_reflect_prefs_all(void)
8417 for (cur = compose_list; cur != NULL; cur = cur->next) {
8418 compose = (Compose *)cur->data;
8419 compose_set_template_menu(compose);
8423 void compose_reflect_prefs_pixmap_theme(void)
8428 for (cur = compose_list; cur != NULL; cur = cur->next) {
8429 compose = (Compose *)cur->data;
8430 toolbar_update(TOOLBAR_COMPOSE, compose);
8434 static const gchar *compose_quote_char_from_context(Compose *compose)
8436 const gchar *qmark = NULL;
8438 cm_return_val_if_fail(compose != NULL, NULL);
8440 switch (compose->mode) {
8441 /* use forward-specific quote char */
8442 case COMPOSE_FORWARD:
8443 case COMPOSE_FORWARD_AS_ATTACH:
8444 case COMPOSE_FORWARD_INLINE:
8445 if (compose->folder && compose->folder->prefs &&
8446 compose->folder->prefs->forward_with_format)
8447 qmark = compose->folder->prefs->forward_quotemark;
8448 else if (compose->account->forward_with_format)
8449 qmark = compose->account->forward_quotemark;
8451 qmark = prefs_common.fw_quotemark;
8454 /* use reply-specific quote char in all other modes */
8456 if (compose->folder && compose->folder->prefs &&
8457 compose->folder->prefs->reply_with_format)
8458 qmark = compose->folder->prefs->reply_quotemark;
8459 else if (compose->account->reply_with_format)
8460 qmark = compose->account->reply_quotemark;
8462 qmark = prefs_common.quotemark;
8466 if (qmark == NULL || *qmark == '\0')
8472 static void compose_template_apply(Compose *compose, Template *tmpl,
8476 GtkTextBuffer *buffer;
8480 gchar *parsed_str = NULL;
8481 gint cursor_pos = 0;
8482 const gchar *err_msg = _("The body of the template has an error at line %d.");
8485 /* process the body */
8487 text = GTK_TEXT_VIEW(compose->text);
8488 buffer = gtk_text_view_get_buffer(text);
8491 qmark = compose_quote_char_from_context(compose);
8493 if (compose->replyinfo != NULL) {
8496 gtk_text_buffer_set_text(buffer, "", -1);
8497 mark = gtk_text_buffer_get_insert(buffer);
8498 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8500 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8501 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8503 } else if (compose->fwdinfo != NULL) {
8506 gtk_text_buffer_set_text(buffer, "", -1);
8507 mark = gtk_text_buffer_get_insert(buffer);
8508 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8510 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8511 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8514 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8516 GtkTextIter start, end;
8519 gtk_text_buffer_get_start_iter(buffer, &start);
8520 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8521 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8523 /* clear the buffer now */
8525 gtk_text_buffer_set_text(buffer, "", -1);
8527 parsed_str = compose_quote_fmt(compose, dummyinfo,
8528 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8529 procmsg_msginfo_free( dummyinfo );
8535 gtk_text_buffer_set_text(buffer, "", -1);
8536 mark = gtk_text_buffer_get_insert(buffer);
8537 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8540 if (replace && parsed_str && compose->account->auto_sig)
8541 compose_insert_sig(compose, FALSE);
8543 if (replace && parsed_str) {
8544 gtk_text_buffer_get_start_iter(buffer, &iter);
8545 gtk_text_buffer_place_cursor(buffer, &iter);
8549 cursor_pos = quote_fmt_get_cursor_pos();
8550 compose->set_cursor_pos = cursor_pos;
8551 if (cursor_pos == -1)
8553 gtk_text_buffer_get_start_iter(buffer, &iter);
8554 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8555 gtk_text_buffer_place_cursor(buffer, &iter);
8558 /* process the other fields */
8560 compose_template_apply_fields(compose, tmpl);
8561 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8562 quote_fmt_reset_vartable();
8563 compose_changed_cb(NULL, compose);
8566 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8567 gtkaspell_highlight_all(compose->gtkaspell);
8571 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8573 MsgInfo* dummyinfo = NULL;
8574 MsgInfo *msginfo = NULL;
8577 if (compose->replyinfo != NULL)
8578 msginfo = compose->replyinfo;
8579 else if (compose->fwdinfo != NULL)
8580 msginfo = compose->fwdinfo;
8582 dummyinfo = compose_msginfo_new_from_compose(compose);
8583 msginfo = dummyinfo;
8586 if (tmpl->from && *tmpl->from != '\0') {
8588 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8589 compose->gtkaspell);
8591 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8593 quote_fmt_scan_string(tmpl->from);
8596 buf = quote_fmt_get_buffer();
8598 alertpanel_error(_("Template From format error."));
8600 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8604 if (tmpl->to && *tmpl->to != '\0') {
8606 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8607 compose->gtkaspell);
8609 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8611 quote_fmt_scan_string(tmpl->to);
8614 buf = quote_fmt_get_buffer();
8616 alertpanel_error(_("Template To format error."));
8618 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8622 if (tmpl->cc && *tmpl->cc != '\0') {
8624 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8625 compose->gtkaspell);
8627 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8629 quote_fmt_scan_string(tmpl->cc);
8632 buf = quote_fmt_get_buffer();
8634 alertpanel_error(_("Template Cc format error."));
8636 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8640 if (tmpl->bcc && *tmpl->bcc != '\0') {
8642 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8643 compose->gtkaspell);
8645 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8647 quote_fmt_scan_string(tmpl->bcc);
8650 buf = quote_fmt_get_buffer();
8652 alertpanel_error(_("Template Bcc format error."));
8654 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8658 /* process the subject */
8659 if (tmpl->subject && *tmpl->subject != '\0') {
8661 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8662 compose->gtkaspell);
8664 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8666 quote_fmt_scan_string(tmpl->subject);
8669 buf = quote_fmt_get_buffer();
8671 alertpanel_error(_("Template subject format error."));
8673 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8677 procmsg_msginfo_free( dummyinfo );
8680 static void compose_destroy(Compose *compose)
8682 GtkAllocation allocation;
8683 GtkTextBuffer *buffer;
8684 GtkClipboard *clipboard;
8686 compose_list = g_list_remove(compose_list, compose);
8688 if (compose->updating) {
8689 debug_print("danger, not destroying anything now\n");
8690 compose->deferred_destroy = TRUE;
8694 /* NOTE: address_completion_end() does nothing with the window
8695 * however this may change. */
8696 address_completion_end(compose->window);
8698 slist_free_strings_full(compose->to_list);
8699 slist_free_strings_full(compose->newsgroup_list);
8700 slist_free_strings_full(compose->header_list);
8702 slist_free_strings_full(extra_headers);
8703 extra_headers = NULL;
8705 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8707 g_hash_table_destroy(compose->email_hashtable);
8709 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8710 compose->folder_update_callback_id);
8712 procmsg_msginfo_free(compose->targetinfo);
8713 procmsg_msginfo_free(compose->replyinfo);
8714 procmsg_msginfo_free(compose->fwdinfo);
8716 g_free(compose->replyto);
8717 g_free(compose->cc);
8718 g_free(compose->bcc);
8719 g_free(compose->newsgroups);
8720 g_free(compose->followup_to);
8722 g_free(compose->ml_post);
8724 g_free(compose->inreplyto);
8725 g_free(compose->references);
8726 g_free(compose->msgid);
8727 g_free(compose->boundary);
8729 g_free(compose->redirect_filename);
8730 if (compose->undostruct)
8731 undo_destroy(compose->undostruct);
8733 g_free(compose->sig_str);
8735 g_free(compose->exteditor_file);
8737 g_free(compose->orig_charset);
8739 g_free(compose->privacy_system);
8741 #ifndef USE_NEW_ADDRBOOK
8742 if (addressbook_get_target_compose() == compose)
8743 addressbook_set_target_compose(NULL);
8746 if (compose->gtkaspell) {
8747 gtkaspell_delete(compose->gtkaspell);
8748 compose->gtkaspell = NULL;
8752 if (!compose->batch) {
8753 gtk_widget_get_allocation(compose->window, &allocation);
8754 prefs_common.compose_width = allocation.width;
8755 prefs_common.compose_height = allocation.height;
8758 if (!gtk_widget_get_parent(compose->paned))
8759 gtk_widget_destroy(compose->paned);
8760 gtk_widget_destroy(compose->popupmenu);
8762 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8763 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8764 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8766 gtk_widget_destroy(compose->window);
8767 toolbar_destroy(compose->toolbar);
8768 g_free(compose->toolbar);
8769 cm_mutex_free(compose->mutex);
8773 static void compose_attach_info_free(AttachInfo *ainfo)
8775 g_free(ainfo->file);
8776 g_free(ainfo->content_type);
8777 g_free(ainfo->name);
8778 g_free(ainfo->charset);
8782 static void compose_attach_update_label(Compose *compose)
8787 GtkTreeModel *model;
8792 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8793 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8794 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8798 while(gtk_tree_model_iter_next(model, &iter))
8801 text = g_strdup_printf("(%d)", i);
8802 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8806 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8808 Compose *compose = (Compose *)data;
8809 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8810 GtkTreeSelection *selection;
8812 GtkTreeModel *model;
8814 selection = gtk_tree_view_get_selection(tree_view);
8815 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8820 for (cur = sel; cur != NULL; cur = cur->next) {
8821 GtkTreePath *path = cur->data;
8822 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8825 gtk_tree_path_free(path);
8828 for (cur = sel; cur != NULL; cur = cur->next) {
8829 GtkTreeRowReference *ref = cur->data;
8830 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8833 if (gtk_tree_model_get_iter(model, &iter, path))
8834 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8836 gtk_tree_path_free(path);
8837 gtk_tree_row_reference_free(ref);
8841 compose_attach_update_label(compose);
8844 static struct _AttachProperty
8847 GtkWidget *mimetype_entry;
8848 GtkWidget *encoding_optmenu;
8849 GtkWidget *path_entry;
8850 GtkWidget *filename_entry;
8852 GtkWidget *cancel_btn;
8855 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8857 gtk_tree_path_free((GtkTreePath *)ptr);
8860 static void compose_attach_property(GtkAction *action, gpointer data)
8862 Compose *compose = (Compose *)data;
8863 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8865 GtkComboBox *optmenu;
8866 GtkTreeSelection *selection;
8868 GtkTreeModel *model;
8871 static gboolean cancelled;
8873 /* only if one selected */
8874 selection = gtk_tree_view_get_selection(tree_view);
8875 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8878 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8882 path = (GtkTreePath *) sel->data;
8883 gtk_tree_model_get_iter(model, &iter, path);
8884 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8887 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8893 if (!attach_prop.window)
8894 compose_attach_property_create(&cancelled);
8895 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8896 gtk_widget_grab_focus(attach_prop.ok_btn);
8897 gtk_widget_show(attach_prop.window);
8898 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8899 GTK_WINDOW(compose->window));
8901 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8902 if (ainfo->encoding == ENC_UNKNOWN)
8903 combobox_select_by_data(optmenu, ENC_BASE64);
8905 combobox_select_by_data(optmenu, ainfo->encoding);
8907 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8908 ainfo->content_type ? ainfo->content_type : "");
8909 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8910 ainfo->file ? ainfo->file : "");
8911 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8912 ainfo->name ? ainfo->name : "");
8915 const gchar *entry_text;
8917 gchar *cnttype = NULL;
8924 gtk_widget_hide(attach_prop.window);
8925 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8930 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8931 if (*entry_text != '\0') {
8934 text = g_strstrip(g_strdup(entry_text));
8935 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8936 cnttype = g_strdup(text);
8939 alertpanel_error(_("Invalid MIME type."));
8945 ainfo->encoding = combobox_get_active_data(optmenu);
8947 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8948 if (*entry_text != '\0') {
8949 if (is_file_exist(entry_text) &&
8950 (size = get_file_size(entry_text)) > 0)
8951 file = g_strdup(entry_text);
8954 (_("File doesn't exist or is empty."));
8960 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8961 if (*entry_text != '\0') {
8962 g_free(ainfo->name);
8963 ainfo->name = g_strdup(entry_text);
8967 g_free(ainfo->content_type);
8968 ainfo->content_type = cnttype;
8971 g_free(ainfo->file);
8975 ainfo->size = (goffset)size;
8977 /* update tree store */
8978 text = to_human_readable(ainfo->size);
8979 gtk_tree_model_get_iter(model, &iter, path);
8980 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8981 COL_MIMETYPE, ainfo->content_type,
8983 COL_NAME, ainfo->name,
8984 COL_CHARSET, ainfo->charset,
8990 gtk_tree_path_free(path);
8993 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8995 label = gtk_label_new(str); \
8996 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8997 GTK_FILL, 0, 0, 0); \
8998 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9000 entry = gtk_entry_new(); \
9001 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9002 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9005 static void compose_attach_property_create(gboolean *cancelled)
9011 GtkWidget *mimetype_entry;
9014 GtkListStore *optmenu_menu;
9015 GtkWidget *path_entry;
9016 GtkWidget *filename_entry;
9019 GtkWidget *cancel_btn;
9020 GList *mime_type_list, *strlist;
9023 debug_print("Creating attach_property window...\n");
9025 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9026 gtk_widget_set_size_request(window, 480, -1);
9027 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9028 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9029 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9030 g_signal_connect(G_OBJECT(window), "delete_event",
9031 G_CALLBACK(attach_property_delete_event),
9033 g_signal_connect(G_OBJECT(window), "key_press_event",
9034 G_CALLBACK(attach_property_key_pressed),
9037 vbox = gtk_vbox_new(FALSE, 8);
9038 gtk_container_add(GTK_CONTAINER(window), vbox);
9040 table = gtk_table_new(4, 2, FALSE);
9041 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9042 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9043 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9045 label = gtk_label_new(_("MIME type"));
9046 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9048 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9049 #if !GTK_CHECK_VERSION(2, 24, 0)
9050 mimetype_entry = gtk_combo_box_entry_new_text();
9052 mimetype_entry = gtk_combo_box_text_new_with_entry();
9054 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9055 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9057 /* stuff with list */
9058 mime_type_list = procmime_get_mime_type_list();
9060 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9061 MimeType *type = (MimeType *) mime_type_list->data;
9064 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9066 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9069 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9070 (GCompareFunc)strcmp2);
9073 for (mime_type_list = strlist; mime_type_list != NULL;
9074 mime_type_list = mime_type_list->next) {
9075 #if !GTK_CHECK_VERSION(2, 24, 0)
9076 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9078 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9080 g_free(mime_type_list->data);
9082 g_list_free(strlist);
9083 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9084 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9086 label = gtk_label_new(_("Encoding"));
9087 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9089 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9091 hbox = gtk_hbox_new(FALSE, 0);
9092 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9093 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9095 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9096 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9098 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9099 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9100 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9101 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9102 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9104 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9106 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9107 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9109 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9110 &ok_btn, GTK_STOCK_OK,
9112 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9113 gtk_widget_grab_default(ok_btn);
9115 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9116 G_CALLBACK(attach_property_ok),
9118 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9119 G_CALLBACK(attach_property_cancel),
9122 gtk_widget_show_all(vbox);
9124 attach_prop.window = window;
9125 attach_prop.mimetype_entry = mimetype_entry;
9126 attach_prop.encoding_optmenu = optmenu;
9127 attach_prop.path_entry = path_entry;
9128 attach_prop.filename_entry = filename_entry;
9129 attach_prop.ok_btn = ok_btn;
9130 attach_prop.cancel_btn = cancel_btn;
9133 #undef SET_LABEL_AND_ENTRY
9135 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9141 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9147 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9148 gboolean *cancelled)
9156 static gboolean attach_property_key_pressed(GtkWidget *widget,
9158 gboolean *cancelled)
9160 if (event && event->keyval == GDK_KEY_Escape) {
9164 if (event && event->keyval == GDK_KEY_Return) {
9172 static void compose_exec_ext_editor(Compose *compose)
9179 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9180 G_DIR_SEPARATOR, compose);
9182 if (pipe(pipe_fds) < 0) {
9188 if ((pid = fork()) < 0) {
9195 /* close the write side of the pipe */
9198 compose->exteditor_file = g_strdup(tmp);
9199 compose->exteditor_pid = pid;
9201 compose_set_ext_editor_sensitive(compose, FALSE);
9204 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9206 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9208 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9212 } else { /* process-monitoring process */
9218 /* close the read side of the pipe */
9221 if (compose_write_body_to_file(compose, tmp) < 0) {
9222 fd_write_all(pipe_fds[1], "2\n", 2);
9226 pid_ed = compose_exec_ext_editor_real(tmp);
9228 fd_write_all(pipe_fds[1], "1\n", 2);
9232 /* wait until editor is terminated */
9233 waitpid(pid_ed, NULL, 0);
9235 fd_write_all(pipe_fds[1], "0\n", 2);
9242 #endif /* G_OS_UNIX */
9246 static gint compose_exec_ext_editor_real(const gchar *file)
9253 cm_return_val_if_fail(file != NULL, -1);
9255 if ((pid = fork()) < 0) {
9260 if (pid != 0) return pid;
9262 /* grandchild process */
9264 if (setpgid(0, getppid()))
9267 if (prefs_common_get_ext_editor_cmd() &&
9268 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9269 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9270 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9272 if (prefs_common_get_ext_editor_cmd())
9273 g_warning("External editor command-line is invalid: '%s'\n",
9274 prefs_common_get_ext_editor_cmd());
9275 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9278 cmdline = strsplit_with_quote(buf, " ", 1024);
9279 execvp(cmdline[0], cmdline);
9282 g_strfreev(cmdline);
9287 static gboolean compose_ext_editor_kill(Compose *compose)
9289 pid_t pgid = compose->exteditor_pid * -1;
9292 ret = kill(pgid, 0);
9294 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9298 msg = g_strdup_printf
9299 (_("The external editor is still working.\n"
9300 "Force terminating the process?\n"
9301 "process group id: %d"), -pgid);
9302 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9303 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9307 if (val == G_ALERTALTERNATE) {
9308 g_source_remove(compose->exteditor_tag);
9309 g_io_channel_shutdown(compose->exteditor_ch,
9311 g_io_channel_unref(compose->exteditor_ch);
9313 if (kill(pgid, SIGTERM) < 0) perror("kill");
9314 waitpid(compose->exteditor_pid, NULL, 0);
9316 g_warning("Terminated process group id: %d", -pgid);
9317 g_warning("Temporary file: %s",
9318 compose->exteditor_file);
9320 compose_set_ext_editor_sensitive(compose, TRUE);
9322 g_free(compose->exteditor_file);
9323 compose->exteditor_file = NULL;
9324 compose->exteditor_pid = -1;
9325 compose->exteditor_ch = NULL;
9326 compose->exteditor_tag = -1;
9334 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9338 Compose *compose = (Compose *)data;
9341 debug_print("Compose: input from monitoring process\n");
9343 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9345 g_io_channel_shutdown(source, FALSE, NULL);
9346 g_io_channel_unref(source);
9348 waitpid(compose->exteditor_pid, NULL, 0);
9350 if (buf[0] == '0') { /* success */
9351 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9352 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9354 gtk_text_buffer_set_text(buffer, "", -1);
9355 compose_insert_file(compose, compose->exteditor_file);
9356 compose_changed_cb(NULL, compose);
9357 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9359 if (claws_unlink(compose->exteditor_file) < 0)
9360 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9361 } else if (buf[0] == '1') { /* failed */
9362 g_warning("Couldn't exec external editor\n");
9363 if (claws_unlink(compose->exteditor_file) < 0)
9364 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9365 } else if (buf[0] == '2') {
9366 g_warning("Couldn't write to file\n");
9367 } else if (buf[0] == '3') {
9368 g_warning("Pipe read failed\n");
9371 compose_set_ext_editor_sensitive(compose, TRUE);
9373 g_free(compose->exteditor_file);
9374 compose->exteditor_file = NULL;
9375 compose->exteditor_pid = -1;
9376 compose->exteditor_ch = NULL;
9377 compose->exteditor_tag = -1;
9382 static void compose_set_ext_editor_sensitive(Compose *compose,
9385 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9386 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9387 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9388 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9389 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9390 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9391 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9392 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9394 gtk_widget_set_sensitive(compose->text, sensitive);
9395 if (compose->toolbar->send_btn)
9396 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9397 if (compose->toolbar->sendl_btn)
9398 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9399 if (compose->toolbar->draft_btn)
9400 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9401 if (compose->toolbar->insert_btn)
9402 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9403 if (compose->toolbar->sig_btn)
9404 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9405 if (compose->toolbar->exteditor_btn)
9406 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9407 if (compose->toolbar->linewrap_current_btn)
9408 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9409 if (compose->toolbar->linewrap_all_btn)
9410 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9412 #endif /* G_OS_UNIX */
9415 * compose_undo_state_changed:
9417 * Change the sensivity of the menuentries undo and redo
9419 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9420 gint redo_state, gpointer data)
9422 Compose *compose = (Compose *)data;
9424 switch (undo_state) {
9425 case UNDO_STATE_TRUE:
9426 if (!undostruct->undo_state) {
9427 undostruct->undo_state = TRUE;
9428 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9431 case UNDO_STATE_FALSE:
9432 if (undostruct->undo_state) {
9433 undostruct->undo_state = FALSE;
9434 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9437 case UNDO_STATE_UNCHANGED:
9439 case UNDO_STATE_REFRESH:
9440 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9443 g_warning("Undo state not recognized");
9447 switch (redo_state) {
9448 case UNDO_STATE_TRUE:
9449 if (!undostruct->redo_state) {
9450 undostruct->redo_state = TRUE;
9451 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9454 case UNDO_STATE_FALSE:
9455 if (undostruct->redo_state) {
9456 undostruct->redo_state = FALSE;
9457 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9460 case UNDO_STATE_UNCHANGED:
9462 case UNDO_STATE_REFRESH:
9463 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9466 g_warning("Redo state not recognized");
9471 /* callback functions */
9473 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9474 GtkAllocation *allocation,
9477 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9480 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9481 * includes "non-client" (windows-izm) in calculation, so this calculation
9482 * may not be accurate.
9484 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9485 GtkAllocation *allocation,
9486 GtkSHRuler *shruler)
9488 if (prefs_common.show_ruler) {
9489 gint char_width = 0, char_height = 0;
9490 gint line_width_in_chars;
9492 gtkut_get_font_size(GTK_WIDGET(widget),
9493 &char_width, &char_height);
9494 line_width_in_chars =
9495 (allocation->width - allocation->x) / char_width;
9497 /* got the maximum */
9498 gtk_shruler_set_range(GTK_SHRULER(shruler),
9499 0.0, line_width_in_chars, 0);
9508 ComposePrefType type;
9509 gboolean entry_marked;
9512 static void account_activated(GtkComboBox *optmenu, gpointer data)
9514 Compose *compose = (Compose *)data;
9517 gchar *folderidentifier;
9518 gint account_id = 0;
9521 GSList *list, *saved_list = NULL;
9522 HeaderEntryState *state;
9523 GtkRcStyle *style = NULL;
9524 #if !GTK_CHECK_VERSION(3, 0, 0)
9525 static GdkColor yellow;
9526 static gboolean color_set = FALSE;
9528 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9531 /* Get ID of active account in the combo box */
9532 menu = gtk_combo_box_get_model(optmenu);
9533 gtk_combo_box_get_active_iter(optmenu, &iter);
9534 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9536 ac = account_find_from_id(account_id);
9537 cm_return_if_fail(ac != NULL);
9539 if (ac != compose->account) {
9540 compose_select_account(compose, ac, FALSE);
9542 for (list = compose->header_list; list; list = list->next) {
9543 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9545 if (hentry->type == PREF_ACCOUNT || !list->next) {
9546 compose_destroy_headerentry(compose, hentry);
9550 state = g_malloc0(sizeof(HeaderEntryState));
9551 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9552 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9553 state->entry = gtk_editable_get_chars(
9554 GTK_EDITABLE(hentry->entry), 0, -1);
9555 state->type = hentry->type;
9557 #if !GTK_CHECK_VERSION(3, 0, 0)
9559 gdk_color_parse("#f5f6be", &yellow);
9560 color_set = gdk_colormap_alloc_color(
9561 gdk_colormap_get_system(),
9562 &yellow, FALSE, TRUE);
9566 style = gtk_widget_get_modifier_style(hentry->entry);
9567 state->entry_marked = gdk_color_equal(&yellow,
9568 &style->base[GTK_STATE_NORMAL]);
9570 saved_list = g_slist_append(saved_list, state);
9571 compose_destroy_headerentry(compose, hentry);
9574 compose->header_last = NULL;
9575 g_slist_free(compose->header_list);
9576 compose->header_list = NULL;
9577 compose->header_nextrow = 1;
9578 compose_create_header_entry(compose);
9580 if (ac->set_autocc && ac->auto_cc)
9581 compose_entry_append(compose, ac->auto_cc,
9582 COMPOSE_CC, PREF_ACCOUNT);
9584 if (ac->set_autobcc && ac->auto_bcc)
9585 compose_entry_append(compose, ac->auto_bcc,
9586 COMPOSE_BCC, PREF_ACCOUNT);
9588 if (ac->set_autoreplyto && ac->auto_replyto)
9589 compose_entry_append(compose, ac->auto_replyto,
9590 COMPOSE_REPLYTO, PREF_ACCOUNT);
9592 for (list = saved_list; list; list = list->next) {
9593 state = (HeaderEntryState *) list->data;
9595 compose_add_header_entry(compose, state->header,
9596 state->entry, state->type);
9597 if (state->entry_marked)
9598 compose_entry_mark_default_to(compose, state->entry);
9600 g_free(state->header);
9601 g_free(state->entry);
9604 g_slist_free(saved_list);
9606 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9607 (ac->protocol == A_NNTP) ?
9608 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9611 /* Set message save folder */
9612 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9613 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9615 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9616 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9618 compose_set_save_to(compose, NULL);
9619 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9620 folderidentifier = folder_item_get_identifier(account_get_special_folder
9621 (compose->account, F_OUTBOX));
9622 compose_set_save_to(compose, folderidentifier);
9623 g_free(folderidentifier);
9627 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9628 GtkTreeViewColumn *column, Compose *compose)
9630 compose_attach_property(NULL, compose);
9633 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9636 Compose *compose = (Compose *)data;
9637 GtkTreeSelection *attach_selection;
9638 gint attach_nr_selected;
9640 if (!event) return FALSE;
9642 if (event->button == 3) {
9643 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9644 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9646 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9647 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9649 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9650 NULL, NULL, event->button, event->time);
9657 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9660 Compose *compose = (Compose *)data;
9662 if (!event) return FALSE;
9664 switch (event->keyval) {
9665 case GDK_KEY_Delete:
9666 compose_attach_remove_selected(NULL, compose);
9672 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9674 toolbar_comp_set_sensitive(compose, allow);
9675 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9676 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9678 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9680 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9681 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9682 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9684 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9688 static void compose_send_cb(GtkAction *action, gpointer data)
9690 Compose *compose = (Compose *)data;
9692 if (prefs_common.work_offline &&
9693 !inc_offline_should_override(TRUE,
9694 _("Claws Mail needs network access in order "
9695 "to send this email.")))
9698 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9699 g_source_remove(compose->draft_timeout_tag);
9700 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9703 compose_send(compose);
9706 static void compose_send_later_cb(GtkAction *action, gpointer data)
9708 Compose *compose = (Compose *)data;
9712 compose_allow_user_actions(compose, FALSE);
9713 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9714 compose_allow_user_actions(compose, TRUE);
9718 compose_close(compose);
9719 } else if (val == -1) {
9720 alertpanel_error(_("Could not queue message."));
9721 } else if (val == -2) {
9722 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9723 } else if (val == -3) {
9724 if (privacy_peek_error())
9725 alertpanel_error(_("Could not queue message for sending:\n\n"
9726 "Signature failed: %s"), privacy_get_error());
9727 } else if (val == -4) {
9728 alertpanel_error(_("Could not queue message for sending:\n\n"
9729 "Charset conversion failed."));
9730 } else if (val == -5) {
9731 alertpanel_error(_("Could not queue message for sending:\n\n"
9732 "Couldn't get recipient encryption key."));
9733 } else if (val == -6) {
9736 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9739 #define DRAFTED_AT_EXIT "drafted_at_exit"
9740 static void compose_register_draft(MsgInfo *info)
9742 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9743 DRAFTED_AT_EXIT, NULL);
9744 FILE *fp = g_fopen(filepath, "ab");
9747 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9755 gboolean compose_draft (gpointer data, guint action)
9757 Compose *compose = (Compose *)data;
9762 MsgFlags flag = {0, 0};
9763 static gboolean lock = FALSE;
9764 MsgInfo *newmsginfo;
9766 gboolean target_locked = FALSE;
9767 gboolean err = FALSE;
9769 if (lock) return FALSE;
9771 if (compose->sending)
9774 draft = account_get_special_folder(compose->account, F_DRAFT);
9775 cm_return_val_if_fail(draft != NULL, FALSE);
9777 if (!g_mutex_trylock(compose->mutex)) {
9778 /* we don't want to lock the mutex once it's available,
9779 * because as the only other part of compose.c locking
9780 * it is compose_close - which means once unlocked,
9781 * the compose struct will be freed */
9782 debug_print("couldn't lock mutex, probably sending\n");
9788 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9789 G_DIR_SEPARATOR, compose);
9790 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9791 FILE_OP_ERROR(tmp, "fopen");
9795 /* chmod for security */
9796 if (change_file_mode_rw(fp, tmp) < 0) {
9797 FILE_OP_ERROR(tmp, "chmod");
9798 g_warning("can't change file mode\n");
9801 /* Save draft infos */
9802 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9803 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9805 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9806 gchar *savefolderid;
9808 savefolderid = compose_get_save_to(compose);
9809 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9810 g_free(savefolderid);
9812 if (compose->return_receipt) {
9813 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9815 if (compose->privacy_system) {
9816 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9817 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9818 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9821 /* Message-ID of message replying to */
9822 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9823 gchar *folderid = NULL;
9825 if (compose->replyinfo->folder)
9826 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9827 if (folderid == NULL)
9828 folderid = g_strdup("NULL");
9830 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9833 /* Message-ID of message forwarding to */
9834 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9835 gchar *folderid = NULL;
9837 if (compose->fwdinfo->folder)
9838 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9839 if (folderid == NULL)
9840 folderid = g_strdup("NULL");
9842 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9846 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9847 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9849 sheaders = compose_get_manual_headers_info(compose);
9850 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9853 /* end of headers */
9854 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9861 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9865 if (fclose(fp) == EOF) {
9869 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9870 if (compose->targetinfo) {
9871 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9873 flag.perm_flags |= MSG_LOCKED;
9875 flag.tmp_flags = MSG_DRAFT;
9877 folder_item_scan(draft);
9878 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9879 MsgInfo *tmpinfo = NULL;
9880 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9881 if (compose->msgid) {
9882 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9885 msgnum = tmpinfo->msgnum;
9886 procmsg_msginfo_free(tmpinfo);
9887 debug_print("got draft msgnum %d from scanning\n", msgnum);
9889 debug_print("didn't get draft msgnum after scanning\n");
9892 debug_print("got draft msgnum %d from adding\n", msgnum);
9898 if (action != COMPOSE_AUTO_SAVE) {
9899 if (action != COMPOSE_DRAFT_FOR_EXIT)
9900 alertpanel_error(_("Could not save draft."));
9903 gtkut_window_popup(compose->window);
9904 val = alertpanel_full(_("Could not save draft"),
9905 _("Could not save draft.\n"
9906 "Do you want to cancel exit or discard this email?"),
9907 _("_Cancel exit"), _("_Discard email"), NULL,
9908 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9909 if (val == G_ALERTALTERNATE) {
9911 g_mutex_unlock(compose->mutex); /* must be done before closing */
9912 compose_close(compose);
9916 g_mutex_unlock(compose->mutex); /* must be done before closing */
9925 if (compose->mode == COMPOSE_REEDIT) {
9926 compose_remove_reedit_target(compose, TRUE);
9929 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9932 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9934 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9936 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9937 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9938 procmsg_msginfo_set_flags(newmsginfo, 0,
9939 MSG_HAS_ATTACHMENT);
9941 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9942 compose_register_draft(newmsginfo);
9944 procmsg_msginfo_free(newmsginfo);
9947 folder_item_scan(draft);
9949 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9951 g_mutex_unlock(compose->mutex); /* must be done before closing */
9952 compose_close(compose);
9958 path = folder_item_fetch_msg(draft, msgnum);
9960 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9963 if (g_stat(path, &s) < 0) {
9964 FILE_OP_ERROR(path, "stat");
9970 procmsg_msginfo_free(compose->targetinfo);
9971 compose->targetinfo = procmsg_msginfo_new();
9972 compose->targetinfo->msgnum = msgnum;
9973 compose->targetinfo->size = (goffset)s.st_size;
9974 compose->targetinfo->mtime = s.st_mtime;
9975 compose->targetinfo->folder = draft;
9977 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9978 compose->mode = COMPOSE_REEDIT;
9980 if (action == COMPOSE_AUTO_SAVE) {
9981 compose->autosaved_draft = compose->targetinfo;
9983 compose->modified = FALSE;
9984 compose_set_title(compose);
9988 g_mutex_unlock(compose->mutex);
9992 void compose_clear_exit_drafts(void)
9994 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9995 DRAFTED_AT_EXIT, NULL);
9996 if (is_file_exist(filepath))
9997 claws_unlink(filepath);
10002 void compose_reopen_exit_drafts(void)
10004 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10005 DRAFTED_AT_EXIT, NULL);
10006 FILE *fp = g_fopen(filepath, "rb");
10010 while (fgets(buf, sizeof(buf), fp)) {
10011 gchar **parts = g_strsplit(buf, "\t", 2);
10012 const gchar *folder = parts[0];
10013 int msgnum = parts[1] ? atoi(parts[1]):-1;
10015 if (folder && *folder && msgnum > -1) {
10016 FolderItem *item = folder_find_item_from_identifier(folder);
10017 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10019 compose_reedit(info, FALSE);
10026 compose_clear_exit_drafts();
10029 static void compose_save_cb(GtkAction *action, gpointer data)
10031 Compose *compose = (Compose *)data;
10032 compose_draft(compose, COMPOSE_KEEP_EDITING);
10033 compose->rmode = COMPOSE_REEDIT;
10036 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10038 if (compose && file_list) {
10041 for ( tmp = file_list; tmp; tmp = tmp->next) {
10042 gchar *file = (gchar *) tmp->data;
10043 gchar *utf8_filename = conv_filename_to_utf8(file);
10044 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10045 compose_changed_cb(NULL, compose);
10050 g_free(utf8_filename);
10055 static void compose_attach_cb(GtkAction *action, gpointer data)
10057 Compose *compose = (Compose *)data;
10060 if (compose->redirect_filename != NULL)
10063 /* Set focus_window properly, in case we were called via popup menu,
10064 * which unsets it (via focus_out_event callback on compose window). */
10065 manage_window_focus_in(compose->window, NULL, NULL);
10067 file_list = filesel_select_multiple_files_open(_("Select file"));
10070 compose_attach_from_list(compose, file_list, TRUE);
10071 g_list_free(file_list);
10075 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10077 Compose *compose = (Compose *)data;
10079 gint files_inserted = 0;
10081 file_list = filesel_select_multiple_files_open(_("Select file"));
10086 for ( tmp = file_list; tmp; tmp = tmp->next) {
10087 gchar *file = (gchar *) tmp->data;
10088 gchar *filedup = g_strdup(file);
10089 gchar *shortfile = g_path_get_basename(filedup);
10090 ComposeInsertResult res;
10091 /* insert the file if the file is short or if the user confirmed that
10092 he/she wants to insert the large file */
10093 res = compose_insert_file(compose, file);
10094 if (res == COMPOSE_INSERT_READ_ERROR) {
10095 alertpanel_error(_("File '%s' could not be read."), shortfile);
10096 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10097 alertpanel_error(_("File '%s' contained invalid characters\n"
10098 "for the current encoding, insertion may be incorrect."),
10100 } else if (res == COMPOSE_INSERT_SUCCESS)
10107 g_list_free(file_list);
10111 if (files_inserted > 0 && compose->gtkaspell &&
10112 compose->gtkaspell->check_while_typing)
10113 gtkaspell_highlight_all(compose->gtkaspell);
10117 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10119 Compose *compose = (Compose *)data;
10121 compose_insert_sig(compose, FALSE);
10124 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10126 Compose *compose = (Compose *)data;
10128 compose_insert_sig(compose, TRUE);
10131 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10135 Compose *compose = (Compose *)data;
10137 gtkut_widget_get_uposition(widget, &x, &y);
10138 if (!compose->batch) {
10139 prefs_common.compose_x = x;
10140 prefs_common.compose_y = y;
10142 if (compose->sending || compose->updating)
10144 compose_close_cb(NULL, compose);
10148 void compose_close_toolbar(Compose *compose)
10150 compose_close_cb(NULL, compose);
10153 static gboolean compose_can_autosave(Compose *compose)
10155 if (compose->privacy_system && compose->use_encryption)
10156 return prefs_common.autosave && prefs_common.autosave_encrypted;
10158 return prefs_common.autosave;
10161 static void compose_close_cb(GtkAction *action, gpointer data)
10163 Compose *compose = (Compose *)data;
10167 if (compose->exteditor_tag != -1) {
10168 if (!compose_ext_editor_kill(compose))
10173 if (compose->modified) {
10174 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10175 if (!g_mutex_trylock(compose->mutex)) {
10176 /* we don't want to lock the mutex once it's available,
10177 * because as the only other part of compose.c locking
10178 * it is compose_close - which means once unlocked,
10179 * the compose struct will be freed */
10180 debug_print("couldn't lock mutex, probably sending\n");
10184 val = alertpanel(_("Discard message"),
10185 _("This message has been modified. Discard it?"),
10186 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10188 val = alertpanel(_("Save changes"),
10189 _("This message has been modified. Save the latest changes?"),
10190 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10192 g_mutex_unlock(compose->mutex);
10194 case G_ALERTDEFAULT:
10195 if (compose_can_autosave(compose) && !reedit)
10196 compose_remove_draft(compose);
10198 case G_ALERTALTERNATE:
10199 compose_draft(data, COMPOSE_QUIT_EDITING);
10206 compose_close(compose);
10209 static void compose_print_cb(GtkAction *action, gpointer data)
10211 Compose *compose = (Compose *) data;
10213 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10214 if (compose->targetinfo)
10215 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10218 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10220 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10221 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10222 Compose *compose = (Compose *) data;
10225 compose->out_encoding = (CharSet)value;
10228 static void compose_address_cb(GtkAction *action, gpointer data)
10230 Compose *compose = (Compose *)data;
10232 #ifndef USE_NEW_ADDRBOOK
10233 addressbook_open(compose);
10235 GError* error = NULL;
10236 addressbook_connect_signals(compose);
10237 addressbook_dbus_open(TRUE, &error);
10239 g_warning("%s", error->message);
10240 g_error_free(error);
10245 static void about_show_cb(GtkAction *action, gpointer data)
10250 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10252 Compose *compose = (Compose *)data;
10257 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10258 cm_return_if_fail(tmpl != NULL);
10260 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10262 val = alertpanel(_("Apply template"), msg,
10263 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10266 if (val == G_ALERTDEFAULT)
10267 compose_template_apply(compose, tmpl, TRUE);
10268 else if (val == G_ALERTALTERNATE)
10269 compose_template_apply(compose, tmpl, FALSE);
10272 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10274 Compose *compose = (Compose *)data;
10276 compose_exec_ext_editor(compose);
10279 static void compose_undo_cb(GtkAction *action, gpointer data)
10281 Compose *compose = (Compose *)data;
10282 gboolean prev_autowrap = compose->autowrap;
10284 compose->autowrap = FALSE;
10285 undo_undo(compose->undostruct);
10286 compose->autowrap = prev_autowrap;
10289 static void compose_redo_cb(GtkAction *action, gpointer data)
10291 Compose *compose = (Compose *)data;
10292 gboolean prev_autowrap = compose->autowrap;
10294 compose->autowrap = FALSE;
10295 undo_redo(compose->undostruct);
10296 compose->autowrap = prev_autowrap;
10299 static void entry_cut_clipboard(GtkWidget *entry)
10301 if (GTK_IS_EDITABLE(entry))
10302 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10303 else if (GTK_IS_TEXT_VIEW(entry))
10304 gtk_text_buffer_cut_clipboard(
10305 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10306 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10310 static void entry_copy_clipboard(GtkWidget *entry)
10312 if (GTK_IS_EDITABLE(entry))
10313 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10314 else if (GTK_IS_TEXT_VIEW(entry))
10315 gtk_text_buffer_copy_clipboard(
10316 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10317 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10320 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10321 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10323 if (GTK_IS_TEXT_VIEW(entry)) {
10324 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10325 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10326 GtkTextIter start_iter, end_iter;
10328 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10330 if (contents == NULL)
10333 /* we shouldn't delete the selection when middle-click-pasting, or we
10334 * can't mid-click-paste our own selection */
10335 if (clip != GDK_SELECTION_PRIMARY) {
10336 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10337 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10340 if (insert_place == NULL) {
10341 /* if insert_place isn't specified, insert at the cursor.
10342 * used for Ctrl-V pasting */
10343 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10344 start = gtk_text_iter_get_offset(&start_iter);
10345 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10347 /* if insert_place is specified, paste here.
10348 * used for mid-click-pasting */
10349 start = gtk_text_iter_get_offset(insert_place);
10350 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10351 if (prefs_common.primary_paste_unselects)
10352 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10356 /* paste unwrapped: mark the paste so it's not wrapped later */
10357 end = start + strlen(contents);
10358 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10359 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10360 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10361 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10362 /* rewrap paragraph now (after a mid-click-paste) */
10363 mark_start = gtk_text_buffer_get_insert(buffer);
10364 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10365 gtk_text_iter_backward_char(&start_iter);
10366 compose_beautify_paragraph(compose, &start_iter, TRUE);
10368 } else if (GTK_IS_EDITABLE(entry))
10369 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10371 compose->modified = TRUE;
10374 static void entry_allsel(GtkWidget *entry)
10376 if (GTK_IS_EDITABLE(entry))
10377 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10378 else if (GTK_IS_TEXT_VIEW(entry)) {
10379 GtkTextIter startiter, enditer;
10380 GtkTextBuffer *textbuf;
10382 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10383 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10384 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10386 gtk_text_buffer_move_mark_by_name(textbuf,
10387 "selection_bound", &startiter);
10388 gtk_text_buffer_move_mark_by_name(textbuf,
10389 "insert", &enditer);
10393 static void compose_cut_cb(GtkAction *action, gpointer data)
10395 Compose *compose = (Compose *)data;
10396 if (compose->focused_editable
10397 #ifndef GENERIC_UMPC
10398 && gtk_widget_has_focus(compose->focused_editable)
10401 entry_cut_clipboard(compose->focused_editable);
10404 static void compose_copy_cb(GtkAction *action, gpointer data)
10406 Compose *compose = (Compose *)data;
10407 if (compose->focused_editable
10408 #ifndef GENERIC_UMPC
10409 && gtk_widget_has_focus(compose->focused_editable)
10412 entry_copy_clipboard(compose->focused_editable);
10415 static void compose_paste_cb(GtkAction *action, gpointer data)
10417 Compose *compose = (Compose *)data;
10418 gint prev_autowrap;
10419 GtkTextBuffer *buffer;
10421 if (compose->focused_editable &&
10422 #ifndef GENERIC_UMPC
10423 gtk_widget_has_focus(compose->focused_editable)
10426 entry_paste_clipboard(compose, compose->focused_editable,
10427 prefs_common.linewrap_pastes,
10428 GDK_SELECTION_CLIPBOARD, NULL);
10433 #ifndef GENERIC_UMPC
10434 gtk_widget_has_focus(compose->text) &&
10436 compose->gtkaspell &&
10437 compose->gtkaspell->check_while_typing)
10438 gtkaspell_highlight_all(compose->gtkaspell);
10442 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10444 Compose *compose = (Compose *)data;
10445 gint wrap_quote = prefs_common.linewrap_quote;
10446 if (compose->focused_editable
10447 #ifndef GENERIC_UMPC
10448 && gtk_widget_has_focus(compose->focused_editable)
10451 /* let text_insert() (called directly or at a later time
10452 * after the gtk_editable_paste_clipboard) know that
10453 * text is to be inserted as a quotation. implemented
10454 * by using a simple refcount... */
10455 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10456 G_OBJECT(compose->focused_editable),
10457 "paste_as_quotation"));
10458 g_object_set_data(G_OBJECT(compose->focused_editable),
10459 "paste_as_quotation",
10460 GINT_TO_POINTER(paste_as_quotation + 1));
10461 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10462 entry_paste_clipboard(compose, compose->focused_editable,
10463 prefs_common.linewrap_pastes,
10464 GDK_SELECTION_CLIPBOARD, NULL);
10465 prefs_common.linewrap_quote = wrap_quote;
10469 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10471 Compose *compose = (Compose *)data;
10472 gint prev_autowrap;
10473 GtkTextBuffer *buffer;
10475 if (compose->focused_editable
10476 #ifndef GENERIC_UMPC
10477 && gtk_widget_has_focus(compose->focused_editable)
10480 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10481 GDK_SELECTION_CLIPBOARD, NULL);
10486 #ifndef GENERIC_UMPC
10487 gtk_widget_has_focus(compose->text) &&
10489 compose->gtkaspell &&
10490 compose->gtkaspell->check_while_typing)
10491 gtkaspell_highlight_all(compose->gtkaspell);
10495 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10497 Compose *compose = (Compose *)data;
10498 gint prev_autowrap;
10499 GtkTextBuffer *buffer;
10501 if (compose->focused_editable
10502 #ifndef GENERIC_UMPC
10503 && gtk_widget_has_focus(compose->focused_editable)
10506 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10507 GDK_SELECTION_CLIPBOARD, NULL);
10512 #ifndef GENERIC_UMPC
10513 gtk_widget_has_focus(compose->text) &&
10515 compose->gtkaspell &&
10516 compose->gtkaspell->check_while_typing)
10517 gtkaspell_highlight_all(compose->gtkaspell);
10521 static void compose_allsel_cb(GtkAction *action, gpointer data)
10523 Compose *compose = (Compose *)data;
10524 if (compose->focused_editable
10525 #ifndef GENERIC_UMPC
10526 && gtk_widget_has_focus(compose->focused_editable)
10529 entry_allsel(compose->focused_editable);
10532 static void textview_move_beginning_of_line (GtkTextView *text)
10534 GtkTextBuffer *buffer;
10538 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10540 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10541 mark = gtk_text_buffer_get_insert(buffer);
10542 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10543 gtk_text_iter_set_line_offset(&ins, 0);
10544 gtk_text_buffer_place_cursor(buffer, &ins);
10547 static void textview_move_forward_character (GtkTextView *text)
10549 GtkTextBuffer *buffer;
10553 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10555 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10556 mark = gtk_text_buffer_get_insert(buffer);
10557 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10558 if (gtk_text_iter_forward_cursor_position(&ins))
10559 gtk_text_buffer_place_cursor(buffer, &ins);
10562 static void textview_move_backward_character (GtkTextView *text)
10564 GtkTextBuffer *buffer;
10568 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10570 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10571 mark = gtk_text_buffer_get_insert(buffer);
10572 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10573 if (gtk_text_iter_backward_cursor_position(&ins))
10574 gtk_text_buffer_place_cursor(buffer, &ins);
10577 static void textview_move_forward_word (GtkTextView *text)
10579 GtkTextBuffer *buffer;
10584 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10586 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10587 mark = gtk_text_buffer_get_insert(buffer);
10588 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10589 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10590 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10591 gtk_text_iter_backward_word_start(&ins);
10592 gtk_text_buffer_place_cursor(buffer, &ins);
10596 static void textview_move_backward_word (GtkTextView *text)
10598 GtkTextBuffer *buffer;
10602 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10604 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10605 mark = gtk_text_buffer_get_insert(buffer);
10606 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10607 if (gtk_text_iter_backward_word_starts(&ins, 1))
10608 gtk_text_buffer_place_cursor(buffer, &ins);
10611 static void textview_move_end_of_line (GtkTextView *text)
10613 GtkTextBuffer *buffer;
10617 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10619 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10620 mark = gtk_text_buffer_get_insert(buffer);
10621 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10622 if (gtk_text_iter_forward_to_line_end(&ins))
10623 gtk_text_buffer_place_cursor(buffer, &ins);
10626 static void textview_move_next_line (GtkTextView *text)
10628 GtkTextBuffer *buffer;
10633 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10635 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10636 mark = gtk_text_buffer_get_insert(buffer);
10637 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10638 offset = gtk_text_iter_get_line_offset(&ins);
10639 if (gtk_text_iter_forward_line(&ins)) {
10640 gtk_text_iter_set_line_offset(&ins, offset);
10641 gtk_text_buffer_place_cursor(buffer, &ins);
10645 static void textview_move_previous_line (GtkTextView *text)
10647 GtkTextBuffer *buffer;
10652 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10654 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10655 mark = gtk_text_buffer_get_insert(buffer);
10656 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10657 offset = gtk_text_iter_get_line_offset(&ins);
10658 if (gtk_text_iter_backward_line(&ins)) {
10659 gtk_text_iter_set_line_offset(&ins, offset);
10660 gtk_text_buffer_place_cursor(buffer, &ins);
10664 static void textview_delete_forward_character (GtkTextView *text)
10666 GtkTextBuffer *buffer;
10668 GtkTextIter ins, end_iter;
10670 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10672 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10673 mark = gtk_text_buffer_get_insert(buffer);
10674 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10676 if (gtk_text_iter_forward_char(&end_iter)) {
10677 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10681 static void textview_delete_backward_character (GtkTextView *text)
10683 GtkTextBuffer *buffer;
10685 GtkTextIter ins, end_iter;
10687 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10689 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10690 mark = gtk_text_buffer_get_insert(buffer);
10691 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10693 if (gtk_text_iter_backward_char(&end_iter)) {
10694 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10698 static void textview_delete_forward_word (GtkTextView *text)
10700 GtkTextBuffer *buffer;
10702 GtkTextIter ins, end_iter;
10704 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10706 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10707 mark = gtk_text_buffer_get_insert(buffer);
10708 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10710 if (gtk_text_iter_forward_word_end(&end_iter)) {
10711 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10715 static void textview_delete_backward_word (GtkTextView *text)
10717 GtkTextBuffer *buffer;
10719 GtkTextIter ins, end_iter;
10721 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10723 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10724 mark = gtk_text_buffer_get_insert(buffer);
10725 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10727 if (gtk_text_iter_backward_word_start(&end_iter)) {
10728 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10732 static void textview_delete_line (GtkTextView *text)
10734 GtkTextBuffer *buffer;
10736 GtkTextIter ins, start_iter, end_iter;
10738 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10740 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10741 mark = gtk_text_buffer_get_insert(buffer);
10742 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10745 gtk_text_iter_set_line_offset(&start_iter, 0);
10748 if (gtk_text_iter_ends_line(&end_iter)){
10749 if (!gtk_text_iter_forward_char(&end_iter))
10750 gtk_text_iter_backward_char(&start_iter);
10753 gtk_text_iter_forward_to_line_end(&end_iter);
10754 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10757 static void textview_delete_to_line_end (GtkTextView *text)
10759 GtkTextBuffer *buffer;
10761 GtkTextIter ins, end_iter;
10763 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10765 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10766 mark = gtk_text_buffer_get_insert(buffer);
10767 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10769 if (gtk_text_iter_ends_line(&end_iter))
10770 gtk_text_iter_forward_char(&end_iter);
10772 gtk_text_iter_forward_to_line_end(&end_iter);
10773 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10776 #define DO_ACTION(name, act) { \
10777 if(!strcmp(name, a_name)) { \
10781 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10783 const gchar *a_name = gtk_action_get_name(action);
10784 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10785 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10786 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10787 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10788 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10789 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10790 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10791 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10792 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10793 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10794 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10795 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10796 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10797 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10801 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10803 Compose *compose = (Compose *)data;
10804 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10805 ComposeCallAdvancedAction action = -1;
10807 action = compose_call_advanced_action_from_path(gaction);
10810 void (*do_action) (GtkTextView *text);
10811 } action_table[] = {
10812 {textview_move_beginning_of_line},
10813 {textview_move_forward_character},
10814 {textview_move_backward_character},
10815 {textview_move_forward_word},
10816 {textview_move_backward_word},
10817 {textview_move_end_of_line},
10818 {textview_move_next_line},
10819 {textview_move_previous_line},
10820 {textview_delete_forward_character},
10821 {textview_delete_backward_character},
10822 {textview_delete_forward_word},
10823 {textview_delete_backward_word},
10824 {textview_delete_line},
10825 {textview_delete_to_line_end}
10828 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10830 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10831 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10832 if (action_table[action].do_action)
10833 action_table[action].do_action(text);
10835 g_warning("Not implemented yet.");
10839 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10841 GtkAllocation allocation;
10845 if (GTK_IS_EDITABLE(widget)) {
10846 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10847 gtk_editable_set_position(GTK_EDITABLE(widget),
10850 if ((parent = gtk_widget_get_parent(widget))
10851 && (parent = gtk_widget_get_parent(parent))
10852 && (parent = gtk_widget_get_parent(parent))) {
10853 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10854 gtk_widget_get_allocation(widget, &allocation);
10855 gint y = allocation.y;
10856 gint height = allocation.height;
10857 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10858 (GTK_SCROLLED_WINDOW(parent));
10860 gfloat value = gtk_adjustment_get_value(shown);
10861 gfloat upper = gtk_adjustment_get_upper(shown);
10862 gfloat page_size = gtk_adjustment_get_page_size(shown);
10863 if (y < (int)value) {
10864 gtk_adjustment_set_value(shown, y - 1);
10866 if ((y + height) > ((int)value + (int)page_size)) {
10867 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10868 gtk_adjustment_set_value(shown,
10869 y + height - (int)page_size - 1);
10871 gtk_adjustment_set_value(shown,
10872 (int)upper - (int)page_size - 1);
10879 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10880 compose->focused_editable = widget;
10882 #ifdef GENERIC_UMPC
10883 if (GTK_IS_TEXT_VIEW(widget)
10884 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10885 g_object_ref(compose->notebook);
10886 g_object_ref(compose->edit_vbox);
10887 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10888 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10889 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10890 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10891 g_object_unref(compose->notebook);
10892 g_object_unref(compose->edit_vbox);
10893 g_signal_handlers_block_by_func(G_OBJECT(widget),
10894 G_CALLBACK(compose_grab_focus_cb),
10896 gtk_widget_grab_focus(widget);
10897 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10898 G_CALLBACK(compose_grab_focus_cb),
10900 } else if (!GTK_IS_TEXT_VIEW(widget)
10901 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10902 g_object_ref(compose->notebook);
10903 g_object_ref(compose->edit_vbox);
10904 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10905 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10906 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10907 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10908 g_object_unref(compose->notebook);
10909 g_object_unref(compose->edit_vbox);
10910 g_signal_handlers_block_by_func(G_OBJECT(widget),
10911 G_CALLBACK(compose_grab_focus_cb),
10913 gtk_widget_grab_focus(widget);
10914 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10915 G_CALLBACK(compose_grab_focus_cb),
10921 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10923 compose->modified = TRUE;
10924 // compose_beautify_paragraph(compose, NULL, TRUE);
10925 #ifndef GENERIC_UMPC
10926 compose_set_title(compose);
10930 static void compose_wrap_cb(GtkAction *action, gpointer data)
10932 Compose *compose = (Compose *)data;
10933 compose_beautify_paragraph(compose, NULL, TRUE);
10936 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10938 Compose *compose = (Compose *)data;
10939 compose_wrap_all_full(compose, TRUE);
10942 static void compose_find_cb(GtkAction *action, gpointer data)
10944 Compose *compose = (Compose *)data;
10946 message_search_compose(compose);
10949 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10952 Compose *compose = (Compose *)data;
10953 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10954 if (compose->autowrap)
10955 compose_wrap_all_full(compose, TRUE);
10956 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10959 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10962 Compose *compose = (Compose *)data;
10963 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10966 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10968 Compose *compose = (Compose *)data;
10970 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10973 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10975 Compose *compose = (Compose *)data;
10977 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10980 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10982 g_free(compose->privacy_system);
10984 compose->privacy_system = g_strdup(account->default_privacy_system);
10985 compose_update_privacy_system_menu_item(compose, warn);
10988 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10990 Compose *compose = (Compose *)data;
10992 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10993 gtk_widget_show(compose->ruler_hbox);
10994 prefs_common.show_ruler = TRUE;
10996 gtk_widget_hide(compose->ruler_hbox);
10997 gtk_widget_queue_resize(compose->edit_vbox);
10998 prefs_common.show_ruler = FALSE;
11002 static void compose_attach_drag_received_cb (GtkWidget *widget,
11003 GdkDragContext *context,
11006 GtkSelectionData *data,
11009 gpointer user_data)
11011 Compose *compose = (Compose *)user_data;
11015 type = gtk_selection_data_get_data_type(data);
11016 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11018 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11020 ) && gtk_drag_get_source_widget(context) !=
11021 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11022 list = uri_list_extract_filenames(
11023 (const gchar *)gtk_selection_data_get_data(data));
11024 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11025 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11026 compose_attach_append
11027 (compose, (const gchar *)tmp->data,
11028 utf8_filename, NULL, NULL);
11029 g_free(utf8_filename);
11031 if (list) compose_changed_cb(NULL, compose);
11032 list_free_strings(list);
11034 } else if (gtk_drag_get_source_widget(context)
11035 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11036 /* comes from our summaryview */
11037 SummaryView * summaryview = NULL;
11038 GSList * list = NULL, *cur = NULL;
11040 if (mainwindow_get_mainwindow())
11041 summaryview = mainwindow_get_mainwindow()->summaryview;
11044 list = summary_get_selected_msg_list(summaryview);
11046 for (cur = list; cur; cur = cur->next) {
11047 MsgInfo *msginfo = (MsgInfo *)cur->data;
11048 gchar *file = NULL;
11050 file = procmsg_get_message_file_full(msginfo,
11053 compose_attach_append(compose, (const gchar *)file,
11054 (const gchar *)file, "message/rfc822", NULL);
11058 g_slist_free(list);
11062 static gboolean compose_drag_drop(GtkWidget *widget,
11063 GdkDragContext *drag_context,
11065 guint time, gpointer user_data)
11067 /* not handling this signal makes compose_insert_drag_received_cb
11072 static gboolean completion_set_focus_to_subject
11073 (GtkWidget *widget,
11074 GdkEventKey *event,
11077 GtkTextBuffer *buffer;
11081 cm_return_val_if_fail(compose != NULL, FALSE);
11083 /* make backtab move to subject field */
11084 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11085 gtk_widget_grab_focus(compose->subject_entry);
11089 // Up key should also move the focus to subject field, if the cursor
11090 // is on the first line.
11091 if ((event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up)
11092 && (event->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == 0) {
11093 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
11094 g_return_val_if_fail(buffer != NULL, FALSE);
11096 mark = gtk_text_buffer_get_mark(buffer, "insert");
11097 g_return_val_if_fail(mark != NULL, FALSE);
11099 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
11101 if (gtk_text_iter_get_line(&iter) == 0) {
11102 gtk_widget_grab_focus(compose->subject_entry);
11110 static void compose_insert_drag_received_cb (GtkWidget *widget,
11111 GdkDragContext *drag_context,
11114 GtkSelectionData *data,
11117 gpointer user_data)
11119 Compose *compose = (Compose *)user_data;
11123 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11125 type = gtk_selection_data_get_data_type(data);
11127 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11129 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11131 AlertValue val = G_ALERTDEFAULT;
11132 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11134 list = uri_list_extract_filenames(ddata);
11135 if (list == NULL && strstr(ddata, "://")) {
11136 /* Assume a list of no files, and data has ://, is a remote link */
11137 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11138 gchar *tmpfile = get_tmp_file();
11139 str_write_to_file(tmpdata, tmpfile);
11141 compose_insert_file(compose, tmpfile);
11142 claws_unlink(tmpfile);
11144 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11145 compose_beautify_paragraph(compose, NULL, TRUE);
11148 switch (prefs_common.compose_dnd_mode) {
11149 case COMPOSE_DND_ASK:
11150 val = alertpanel_full(_("Insert or attach?"),
11151 _("Do you want to insert the contents of the file(s) "
11152 "into the message body, or attach it to the email?"),
11153 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11154 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11156 case COMPOSE_DND_INSERT:
11157 val = G_ALERTALTERNATE;
11159 case COMPOSE_DND_ATTACH:
11160 val = G_ALERTOTHER;
11163 /* unexpected case */
11164 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11167 if (val & G_ALERTDISABLE) {
11168 val &= ~G_ALERTDISABLE;
11169 /* remember what action to perform by default, only if we don't click Cancel */
11170 if (val == G_ALERTALTERNATE)
11171 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11172 else if (val == G_ALERTOTHER)
11173 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11176 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11177 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11178 list_free_strings(list);
11181 } else if (val == G_ALERTOTHER) {
11182 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11183 list_free_strings(list);
11188 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11189 compose_insert_file(compose, (const gchar *)tmp->data);
11191 list_free_strings(list);
11193 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11198 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11201 static void compose_header_drag_received_cb (GtkWidget *widget,
11202 GdkDragContext *drag_context,
11205 GtkSelectionData *data,
11208 gpointer user_data)
11210 GtkEditable *entry = (GtkEditable *)user_data;
11211 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11213 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11216 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11217 gchar *decoded=g_new(gchar, strlen(email));
11220 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11221 gtk_editable_delete_text(entry, 0, -1);
11222 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11223 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11227 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11230 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11232 Compose *compose = (Compose *)data;
11234 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11235 compose->return_receipt = TRUE;
11237 compose->return_receipt = FALSE;
11240 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11242 Compose *compose = (Compose *)data;
11244 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11245 compose->remove_references = TRUE;
11247 compose->remove_references = FALSE;
11250 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11251 ComposeHeaderEntry *headerentry)
11253 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11257 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11258 GdkEventKey *event,
11259 ComposeHeaderEntry *headerentry)
11261 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11262 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11263 !(event->state & GDK_MODIFIER_MASK) &&
11264 (event->keyval == GDK_KEY_BackSpace) &&
11265 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11266 gtk_container_remove
11267 (GTK_CONTAINER(headerentry->compose->header_table),
11268 headerentry->combo);
11269 gtk_container_remove
11270 (GTK_CONTAINER(headerentry->compose->header_table),
11271 headerentry->entry);
11272 headerentry->compose->header_list =
11273 g_slist_remove(headerentry->compose->header_list,
11275 g_free(headerentry);
11276 } else if (event->keyval == GDK_KEY_Tab) {
11277 if (headerentry->compose->header_last == headerentry) {
11278 /* Override default next focus, and give it to subject_entry
11279 * instead of notebook tabs
11281 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11282 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11289 static gboolean scroll_postpone(gpointer data)
11291 Compose *compose = (Compose *)data;
11293 if (compose->batch)
11296 GTK_EVENTS_FLUSH();
11297 compose_show_first_last_header(compose, FALSE);
11301 static void compose_headerentry_changed_cb(GtkWidget *entry,
11302 ComposeHeaderEntry *headerentry)
11304 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11305 compose_create_header_entry(headerentry->compose);
11306 g_signal_handlers_disconnect_matched
11307 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11308 0, 0, NULL, NULL, headerentry);
11310 if (!headerentry->compose->batch)
11311 g_timeout_add(0, scroll_postpone, headerentry->compose);
11315 static gboolean compose_defer_auto_save_draft(Compose *compose)
11317 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11318 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11322 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11324 GtkAdjustment *vadj;
11326 cm_return_if_fail(compose);
11331 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11332 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11333 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11334 gtk_widget_get_parent(compose->header_table)));
11335 gtk_adjustment_set_value(vadj, (show_first ?
11336 gtk_adjustment_get_lower(vadj) :
11337 (gtk_adjustment_get_upper(vadj) -
11338 gtk_adjustment_get_page_size(vadj))));
11339 gtk_adjustment_changed(vadj);
11342 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11343 const gchar *text, gint len, Compose *compose)
11345 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11346 (G_OBJECT(compose->text), "paste_as_quotation"));
11349 cm_return_if_fail(text != NULL);
11351 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11352 G_CALLBACK(text_inserted),
11354 if (paste_as_quotation) {
11356 const gchar *qmark;
11358 GtkTextIter start_iter;
11361 len = strlen(text);
11363 new_text = g_strndup(text, len);
11365 qmark = compose_quote_char_from_context(compose);
11367 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11368 gtk_text_buffer_place_cursor(buffer, iter);
11370 pos = gtk_text_iter_get_offset(iter);
11372 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11373 _("Quote format error at line %d."));
11374 quote_fmt_reset_vartable();
11376 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11377 GINT_TO_POINTER(paste_as_quotation - 1));
11379 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11380 gtk_text_buffer_place_cursor(buffer, iter);
11381 gtk_text_buffer_delete_mark(buffer, mark);
11383 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11384 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11385 compose_beautify_paragraph(compose, &start_iter, FALSE);
11386 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11387 gtk_text_buffer_delete_mark(buffer, mark);
11389 if (strcmp(text, "\n") || compose->automatic_break
11390 || gtk_text_iter_starts_line(iter)) {
11391 GtkTextIter before_ins;
11392 gtk_text_buffer_insert(buffer, iter, text, len);
11393 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11394 before_ins = *iter;
11395 gtk_text_iter_backward_chars(&before_ins, len);
11396 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11399 /* check if the preceding is just whitespace or quote */
11400 GtkTextIter start_line;
11401 gchar *tmp = NULL, *quote = NULL;
11402 gint quote_len = 0, is_normal = 0;
11403 start_line = *iter;
11404 gtk_text_iter_set_line_offset(&start_line, 0);
11405 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11408 if (*tmp == '\0') {
11411 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11419 gtk_text_buffer_insert(buffer, iter, text, len);
11421 gtk_text_buffer_insert_with_tags_by_name(buffer,
11422 iter, text, len, "no_join", NULL);
11427 if (!paste_as_quotation) {
11428 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11429 compose_beautify_paragraph(compose, iter, FALSE);
11430 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11431 gtk_text_buffer_delete_mark(buffer, mark);
11434 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11435 G_CALLBACK(text_inserted),
11437 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11439 if (compose_can_autosave(compose) &&
11440 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11441 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11442 compose->draft_timeout_tag = g_timeout_add
11443 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11447 static void compose_check_all(GtkAction *action, gpointer data)
11449 Compose *compose = (Compose *)data;
11450 if (!compose->gtkaspell)
11453 if (gtk_widget_has_focus(compose->subject_entry))
11454 claws_spell_entry_check_all(
11455 CLAWS_SPELL_ENTRY(compose->subject_entry));
11457 gtkaspell_check_all(compose->gtkaspell);
11460 static void compose_highlight_all(GtkAction *action, gpointer data)
11462 Compose *compose = (Compose *)data;
11463 if (compose->gtkaspell) {
11464 claws_spell_entry_recheck_all(
11465 CLAWS_SPELL_ENTRY(compose->subject_entry));
11466 gtkaspell_highlight_all(compose->gtkaspell);
11470 static void compose_check_backwards(GtkAction *action, gpointer data)
11472 Compose *compose = (Compose *)data;
11473 if (!compose->gtkaspell) {
11474 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11478 if (gtk_widget_has_focus(compose->subject_entry))
11479 claws_spell_entry_check_backwards(
11480 CLAWS_SPELL_ENTRY(compose->subject_entry));
11482 gtkaspell_check_backwards(compose->gtkaspell);
11485 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11487 Compose *compose = (Compose *)data;
11488 if (!compose->gtkaspell) {
11489 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11493 if (gtk_widget_has_focus(compose->subject_entry))
11494 claws_spell_entry_check_forwards_go(
11495 CLAWS_SPELL_ENTRY(compose->subject_entry));
11497 gtkaspell_check_forwards_go(compose->gtkaspell);
11502 *\brief Guess originating forward account from MsgInfo and several
11503 * "common preference" settings. Return NULL if no guess.
11505 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11507 PrefsAccount *account = NULL;
11509 cm_return_val_if_fail(msginfo, NULL);
11510 cm_return_val_if_fail(msginfo->folder, NULL);
11511 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11513 if (msginfo->folder->prefs->enable_default_account)
11514 account = account_find_from_id(msginfo->folder->prefs->default_account);
11517 account = msginfo->folder->folder->account;
11519 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11521 Xstrdup_a(to, msginfo->to, return NULL);
11522 extract_address(to);
11523 account = account_find_from_address(to, FALSE);
11526 if (!account && prefs_common.forward_account_autosel) {
11527 gchar cc[BUFFSIZE];
11528 if (!procheader_get_header_from_msginfo
11529 (msginfo, cc,sizeof cc , "Cc:")) {
11530 gchar *buf = cc + strlen("Cc:");
11531 extract_address(buf);
11532 account = account_find_from_address(buf, FALSE);
11536 if (!account && prefs_common.forward_account_autosel) {
11537 gchar deliveredto[BUFFSIZE];
11538 if (!procheader_get_header_from_msginfo
11539 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11540 gchar *buf = deliveredto + strlen("Delivered-To:");
11541 extract_address(buf);
11542 account = account_find_from_address(buf, FALSE);
11549 gboolean compose_close(Compose *compose)
11553 cm_return_val_if_fail(compose, FALSE);
11555 if (!g_mutex_trylock(compose->mutex)) {
11556 /* we have to wait for the (possibly deferred by auto-save)
11557 * drafting to be done, before destroying the compose under
11559 debug_print("waiting for drafting to finish...\n");
11560 compose_allow_user_actions(compose, FALSE);
11561 if (compose->close_timeout_tag == 0) {
11562 compose->close_timeout_tag =
11563 g_timeout_add (500, (GSourceFunc) compose_close,
11569 if (compose->draft_timeout_tag >= 0) {
11570 g_source_remove(compose->draft_timeout_tag);
11571 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11574 gtkut_widget_get_uposition(compose->window, &x, &y);
11575 if (!compose->batch) {
11576 prefs_common.compose_x = x;
11577 prefs_common.compose_y = y;
11579 g_mutex_unlock(compose->mutex);
11580 compose_destroy(compose);
11585 * Add entry field for each address in list.
11586 * \param compose E-Mail composition object.
11587 * \param listAddress List of (formatted) E-Mail addresses.
11589 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11592 node = listAddress;
11594 addr = ( gchar * ) node->data;
11595 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11596 node = g_list_next( node );
11600 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11601 guint action, gboolean opening_multiple)
11603 gchar *body = NULL;
11604 GSList *new_msglist = NULL;
11605 MsgInfo *tmp_msginfo = NULL;
11606 gboolean originally_enc = FALSE;
11607 gboolean originally_sig = FALSE;
11608 Compose *compose = NULL;
11609 gchar *s_system = NULL;
11611 cm_return_if_fail(msgview != NULL);
11613 cm_return_if_fail(msginfo_list != NULL);
11615 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11616 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11617 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11619 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11620 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11621 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11622 orig_msginfo, mimeinfo);
11623 if (tmp_msginfo != NULL) {
11624 new_msglist = g_slist_append(NULL, tmp_msginfo);
11626 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11627 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11628 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11630 tmp_msginfo->folder = orig_msginfo->folder;
11631 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11632 if (orig_msginfo->tags) {
11633 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11634 tmp_msginfo->folder->tags_dirty = TRUE;
11640 if (!opening_multiple)
11641 body = messageview_get_selection(msgview);
11644 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11645 procmsg_msginfo_free(tmp_msginfo);
11646 g_slist_free(new_msglist);
11648 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11650 if (compose && originally_enc) {
11651 compose_force_encryption(compose, compose->account, FALSE, s_system);
11654 if (compose && originally_sig && compose->account->default_sign_reply) {
11655 compose_force_signing(compose, compose->account, s_system);
11659 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11662 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11665 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11666 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11667 GSList *cur = msginfo_list;
11668 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11669 "messages. Opening the windows "
11670 "could take some time. Do you "
11671 "want to continue?"),
11672 g_slist_length(msginfo_list));
11673 if (g_slist_length(msginfo_list) > 9
11674 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11675 != G_ALERTALTERNATE) {
11680 /* We'll open multiple compose windows */
11681 /* let the WM place the next windows */
11682 compose_force_window_origin = FALSE;
11683 for (; cur; cur = cur->next) {
11685 tmplist.data = cur->data;
11686 tmplist.next = NULL;
11687 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11689 compose_force_window_origin = TRUE;
11691 /* forwarding multiple mails as attachments is done via a
11692 * single compose window */
11693 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11697 void compose_check_for_email_account(Compose *compose)
11699 PrefsAccount *ac = NULL, *curr = NULL;
11705 if (compose->account && compose->account->protocol == A_NNTP) {
11706 ac = account_get_cur_account();
11707 if (ac->protocol == A_NNTP) {
11708 list = account_get_list();
11710 for( ; list != NULL ; list = g_list_next(list)) {
11711 curr = (PrefsAccount *) list->data;
11712 if (curr->protocol != A_NNTP) {
11718 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11723 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11724 const gchar *address)
11726 GSList *msginfo_list = NULL;
11727 gchar *body = messageview_get_selection(msgview);
11730 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11732 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11733 compose_check_for_email_account(compose);
11734 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11735 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11736 compose_reply_set_subject(compose, msginfo);
11739 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11742 void compose_set_position(Compose *compose, gint pos)
11744 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11746 gtkut_text_view_set_position(text, pos);
11749 gboolean compose_search_string(Compose *compose,
11750 const gchar *str, gboolean case_sens)
11752 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11754 return gtkut_text_view_search_string(text, str, case_sens);
11757 gboolean compose_search_string_backward(Compose *compose,
11758 const gchar *str, gboolean case_sens)
11760 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11762 return gtkut_text_view_search_string_backward(text, str, case_sens);
11765 /* allocate a msginfo structure and populate its data from a compose data structure */
11766 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11768 MsgInfo *newmsginfo;
11770 gchar buf[BUFFSIZE];
11772 cm_return_val_if_fail( compose != NULL, NULL );
11774 newmsginfo = procmsg_msginfo_new();
11777 get_rfc822_date(buf, sizeof(buf));
11778 newmsginfo->date = g_strdup(buf);
11781 if (compose->from_name) {
11782 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11783 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11787 if (compose->subject_entry)
11788 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11790 /* to, cc, reply-to, newsgroups */
11791 for (list = compose->header_list; list; list = list->next) {
11792 gchar *header = gtk_editable_get_chars(
11794 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11795 gchar *entry = gtk_editable_get_chars(
11796 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11798 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11799 if ( newmsginfo->to == NULL ) {
11800 newmsginfo->to = g_strdup(entry);
11801 } else if (entry && *entry) {
11802 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11803 g_free(newmsginfo->to);
11804 newmsginfo->to = tmp;
11807 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11808 if ( newmsginfo->cc == NULL ) {
11809 newmsginfo->cc = g_strdup(entry);
11810 } else if (entry && *entry) {
11811 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11812 g_free(newmsginfo->cc);
11813 newmsginfo->cc = tmp;
11816 if ( strcasecmp(header,
11817 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11818 if ( newmsginfo->newsgroups == NULL ) {
11819 newmsginfo->newsgroups = g_strdup(entry);
11820 } else if (entry && *entry) {
11821 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11822 g_free(newmsginfo->newsgroups);
11823 newmsginfo->newsgroups = tmp;
11831 /* other data is unset */
11837 /* update compose's dictionaries from folder dict settings */
11838 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11839 FolderItem *folder_item)
11841 cm_return_if_fail(compose != NULL);
11843 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11844 FolderItemPrefs *prefs = folder_item->prefs;
11846 if (prefs->enable_default_dictionary)
11847 gtkaspell_change_dict(compose->gtkaspell,
11848 prefs->default_dictionary, FALSE);
11849 if (folder_item->prefs->enable_default_alt_dictionary)
11850 gtkaspell_change_alt_dict(compose->gtkaspell,
11851 prefs->default_alt_dictionary);
11852 if (prefs->enable_default_dictionary
11853 || prefs->enable_default_alt_dictionary)
11854 compose_spell_menu_changed(compose);
11859 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11861 Compose *compose = (Compose *)data;
11863 cm_return_if_fail(compose != NULL);
11865 gtk_widget_grab_focus(compose->text);