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_FORBIDEN -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 GList *compose_list = compose_get_compose_list();
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->name,
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 void compose_colorize_signature(Compose *compose)
2114 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2116 GtkTextIter end_iter;
2117 gtk_text_buffer_get_start_iter(buffer, &iter);
2118 while (gtk_text_iter_forward_line(&iter))
2119 if (compose_is_sig_separator(compose, buffer, &iter)) {
2120 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2121 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2125 #define BLOCK_WRAP() { \
2126 prev_autowrap = compose->autowrap; \
2127 buffer = gtk_text_view_get_buffer( \
2128 GTK_TEXT_VIEW(compose->text)); \
2129 compose->autowrap = FALSE; \
2131 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2132 G_CALLBACK(compose_changed_cb), \
2134 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2135 G_CALLBACK(text_inserted), \
2138 #define UNBLOCK_WRAP() { \
2139 compose->autowrap = prev_autowrap; \
2140 if (compose->autowrap) { \
2141 gint old = compose->draft_timeout_tag; \
2142 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2143 compose_wrap_all(compose); \
2144 compose->draft_timeout_tag = old; \
2147 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2148 G_CALLBACK(compose_changed_cb), \
2150 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2151 G_CALLBACK(text_inserted), \
2155 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2157 Compose *compose = NULL;
2158 PrefsAccount *account = NULL;
2159 GtkTextView *textview;
2160 GtkTextBuffer *textbuf;
2164 gchar buf[BUFFSIZE];
2165 gboolean use_signing = FALSE;
2166 gboolean use_encryption = FALSE;
2167 gchar *privacy_system = NULL;
2168 int priority = PRIORITY_NORMAL;
2169 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2170 gboolean autowrap = prefs_common.autowrap;
2171 gboolean autoindent = prefs_common.auto_indent;
2172 HeaderEntry *manual_headers = NULL;
2174 cm_return_val_if_fail(msginfo != NULL, NULL);
2175 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2177 if (compose_put_existing_to_front(msginfo)) {
2181 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2182 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2183 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2184 gchar queueheader_buf[BUFFSIZE];
2187 /* Select Account from queue headers */
2188 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2189 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2190 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2191 account = account_find_from_id(id);
2193 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2194 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2195 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2196 account = account_find_from_id(id);
2198 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2199 sizeof(queueheader_buf), "NAID:")) {
2200 id = atoi(&queueheader_buf[strlen("NAID:")]);
2201 account = account_find_from_id(id);
2203 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2204 sizeof(queueheader_buf), "MAID:")) {
2205 id = atoi(&queueheader_buf[strlen("MAID:")]);
2206 account = account_find_from_id(id);
2208 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2209 sizeof(queueheader_buf), "S:")) {
2210 account = account_find_from_address(queueheader_buf, FALSE);
2212 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2213 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2214 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2215 use_signing = param;
2218 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2219 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2220 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2221 use_signing = param;
2224 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2225 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2226 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2227 use_encryption = param;
2229 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2230 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2231 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2232 use_encryption = param;
2234 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2235 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2236 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2239 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2240 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2241 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2244 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2245 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2246 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2248 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2249 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2250 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2252 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2253 sizeof(queueheader_buf), "X-Priority: ")) {
2254 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2257 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2258 sizeof(queueheader_buf), "RMID:")) {
2259 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2260 if (tokens[0] && tokens[1] && tokens[2]) {
2261 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2262 if (orig_item != NULL) {
2263 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2268 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2269 sizeof(queueheader_buf), "FMID:")) {
2270 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2271 if (tokens[0] && tokens[1] && tokens[2]) {
2272 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2273 if (orig_item != NULL) {
2274 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2279 /* Get manual headers */
2280 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2281 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2282 if (*listmh != '\0') {
2283 debug_print("Got manual headers: %s\n", listmh);
2284 manual_headers = procheader_entries_from_str(listmh);
2289 account = msginfo->folder->folder->account;
2292 if (!account && prefs_common.reedit_account_autosel) {
2293 gchar from[BUFFSIZE];
2294 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2295 extract_address(from);
2296 account = account_find_from_address(from, FALSE);
2300 account = cur_account;
2302 cm_return_val_if_fail(account != NULL, NULL);
2304 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2306 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2307 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2308 compose->autowrap = autowrap;
2309 compose->replyinfo = replyinfo;
2310 compose->fwdinfo = fwdinfo;
2312 compose->updating = TRUE;
2313 compose->priority = priority;
2315 if (privacy_system != NULL) {
2316 compose->privacy_system = privacy_system;
2317 compose_use_signing(compose, use_signing);
2318 compose_use_encryption(compose, use_encryption);
2319 compose_update_privacy_system_menu_item(compose, FALSE);
2321 activate_privacy_system(compose, account, FALSE);
2324 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2326 compose_extract_original_charset(compose);
2328 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2329 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2330 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2331 gchar queueheader_buf[BUFFSIZE];
2333 /* Set message save folder */
2334 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2335 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2336 compose_set_save_to(compose, &queueheader_buf[4]);
2338 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2339 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2341 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2346 if (compose_parse_header(compose, msginfo) < 0) {
2347 compose->updating = FALSE;
2348 compose_destroy(compose);
2351 compose_reedit_set_entry(compose, msginfo);
2353 textview = GTK_TEXT_VIEW(compose->text);
2354 textbuf = gtk_text_view_get_buffer(textview);
2355 compose_create_tags(textview, compose);
2357 mark = gtk_text_buffer_get_insert(textbuf);
2358 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2360 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2361 G_CALLBACK(compose_changed_cb),
2364 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2365 fp = procmime_get_first_encrypted_text_content(msginfo);
2367 compose_force_encryption(compose, account, TRUE, NULL);
2370 fp = procmime_get_first_text_content(msginfo);
2373 g_warning("Can't get text part\n");
2377 gboolean prev_autowrap;
2378 GtkTextBuffer *buffer;
2380 while (fgets(buf, sizeof(buf), fp) != NULL) {
2382 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2388 compose_attach_parts(compose, msginfo);
2390 compose_colorize_signature(compose);
2392 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2393 G_CALLBACK(compose_changed_cb),
2396 if (manual_headers != NULL) {
2397 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2398 procheader_entries_free(manual_headers);
2399 compose->updating = FALSE;
2400 compose_destroy(compose);
2403 procheader_entries_free(manual_headers);
2406 gtk_widget_grab_focus(compose->text);
2408 if (prefs_common.auto_exteditor) {
2409 compose_exec_ext_editor(compose);
2411 compose->modified = FALSE;
2412 compose_set_title(compose);
2414 compose->updating = FALSE;
2415 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2416 SCROLL_TO_CURSOR(compose);
2418 if (compose->deferred_destroy) {
2419 compose_destroy(compose);
2423 compose->sig_str = account_get_signature_str(compose->account);
2425 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2430 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2437 cm_return_val_if_fail(msginfo != NULL, NULL);
2440 account = account_get_reply_account(msginfo,
2441 prefs_common.reply_account_autosel);
2442 cm_return_val_if_fail(account != NULL, NULL);
2444 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2446 compose->updating = TRUE;
2448 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2449 compose->replyinfo = NULL;
2450 compose->fwdinfo = NULL;
2452 compose_show_first_last_header(compose, TRUE);
2454 gtk_widget_grab_focus(compose->header_last->entry);
2456 filename = procmsg_get_message_file(msginfo);
2458 if (filename == NULL) {
2459 compose->updating = FALSE;
2460 compose_destroy(compose);
2465 compose->redirect_filename = filename;
2467 /* Set save folder */
2468 item = msginfo->folder;
2469 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2470 gchar *folderidentifier;
2472 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2473 folderidentifier = folder_item_get_identifier(item);
2474 compose_set_save_to(compose, folderidentifier);
2475 g_free(folderidentifier);
2478 compose_attach_parts(compose, msginfo);
2480 if (msginfo->subject)
2481 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2483 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2485 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2486 _("The body of the \"Redirect\" template has an error at line %d."));
2487 quote_fmt_reset_vartable();
2488 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2490 compose_colorize_signature(compose);
2493 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2494 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2495 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2497 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2498 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2499 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2500 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2501 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2503 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2504 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2505 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2507 if (compose->toolbar->draft_btn)
2508 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2509 if (compose->toolbar->insert_btn)
2510 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2511 if (compose->toolbar->attach_btn)
2512 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2513 if (compose->toolbar->sig_btn)
2514 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2515 if (compose->toolbar->exteditor_btn)
2516 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2517 if (compose->toolbar->linewrap_current_btn)
2518 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2519 if (compose->toolbar->linewrap_all_btn)
2520 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2522 compose->modified = FALSE;
2523 compose_set_title(compose);
2524 compose->updating = FALSE;
2525 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2526 SCROLL_TO_CURSOR(compose);
2528 if (compose->deferred_destroy) {
2529 compose_destroy(compose);
2533 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2538 GList *compose_get_compose_list(void)
2540 return compose_list;
2543 void compose_entry_append(Compose *compose, const gchar *address,
2544 ComposeEntryType type, ComposePrefType pref_type)
2546 const gchar *header;
2548 gboolean in_quote = FALSE;
2549 if (!address || *address == '\0') return;
2556 header = N_("Bcc:");
2558 case COMPOSE_REPLYTO:
2559 header = N_("Reply-To:");
2561 case COMPOSE_NEWSGROUPS:
2562 header = N_("Newsgroups:");
2564 case COMPOSE_FOLLOWUPTO:
2565 header = N_( "Followup-To:");
2567 case COMPOSE_INREPLYTO:
2568 header = N_( "In-Reply-To:");
2575 header = prefs_common_translated_header_name(header);
2577 cur = begin = (gchar *)address;
2579 /* we separate the line by commas, but not if we're inside a quoted
2581 while (*cur != '\0') {
2583 in_quote = !in_quote;
2584 if (*cur == ',' && !in_quote) {
2585 gchar *tmp = g_strdup(begin);
2587 tmp[cur-begin]='\0';
2590 while (*tmp == ' ' || *tmp == '\t')
2592 compose_add_header_entry(compose, header, tmp, pref_type);
2599 gchar *tmp = g_strdup(begin);
2601 tmp[cur-begin]='\0';
2602 while (*tmp == ' ' || *tmp == '\t')
2604 compose_add_header_entry(compose, header, tmp, pref_type);
2609 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2611 #if !GTK_CHECK_VERSION(3, 0, 0)
2612 static GdkColor yellow;
2613 static GdkColor black;
2614 static gboolean yellow_initialised = FALSE;
2616 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2617 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2622 #if !GTK_CHECK_VERSION(3, 0, 0)
2623 if (!yellow_initialised) {
2624 gdk_color_parse("#f5f6be", &yellow);
2625 gdk_color_parse("#000000", &black);
2626 yellow_initialised = gdk_colormap_alloc_color(
2627 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2628 yellow_initialised &= gdk_colormap_alloc_color(
2629 gdk_colormap_get_system(), &black, FALSE, TRUE);
2633 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2634 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2635 if (gtk_entry_get_text(entry) &&
2636 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2637 #if !GTK_CHECK_VERSION(3, 0, 0)
2638 if (yellow_initialised) {
2640 gtk_widget_modify_base(
2641 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2642 GTK_STATE_NORMAL, &yellow);
2643 gtk_widget_modify_text(
2644 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2645 GTK_STATE_NORMAL, &black);
2646 #if !GTK_CHECK_VERSION(3, 0, 0)
2653 void compose_toolbar_cb(gint action, gpointer data)
2655 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2656 Compose *compose = (Compose*)toolbar_item->parent;
2658 cm_return_if_fail(compose != NULL);
2662 compose_send_cb(NULL, compose);
2665 compose_send_later_cb(NULL, compose);
2668 compose_draft(compose, COMPOSE_QUIT_EDITING);
2671 compose_insert_file_cb(NULL, compose);
2674 compose_attach_cb(NULL, compose);
2677 compose_insert_sig(compose, FALSE);
2680 compose_insert_sig(compose, TRUE);
2683 compose_ext_editor_cb(NULL, compose);
2685 case A_LINEWRAP_CURRENT:
2686 compose_beautify_paragraph(compose, NULL, TRUE);
2688 case A_LINEWRAP_ALL:
2689 compose_wrap_all_full(compose, TRUE);
2692 compose_address_cb(NULL, compose);
2695 case A_CHECK_SPELLING:
2696 compose_check_all(NULL, compose);
2704 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2709 gchar *subject = NULL;
2713 gchar **attach = NULL;
2714 gchar *inreplyto = NULL;
2715 MailField mfield = NO_FIELD_PRESENT;
2717 /* get mailto parts but skip from */
2718 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2721 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2722 mfield = TO_FIELD_PRESENT;
2725 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2727 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2729 if (!g_utf8_validate (subject, -1, NULL)) {
2730 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2731 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2734 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2736 mfield = SUBJECT_FIELD_PRESENT;
2739 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2740 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2743 gboolean prev_autowrap = compose->autowrap;
2745 compose->autowrap = FALSE;
2747 mark = gtk_text_buffer_get_insert(buffer);
2748 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2750 if (!g_utf8_validate (body, -1, NULL)) {
2751 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2752 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2755 gtk_text_buffer_insert(buffer, &iter, body, -1);
2757 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2759 compose->autowrap = prev_autowrap;
2760 if (compose->autowrap)
2761 compose_wrap_all(compose);
2762 mfield = BODY_FIELD_PRESENT;
2766 gint i = 0, att = 0;
2767 gchar *warn_files = NULL;
2768 while (attach[i] != NULL) {
2769 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2770 if (utf8_filename) {
2771 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2772 gchar *tmp = g_strdup_printf("%s%s\n",
2773 warn_files?warn_files:"",
2779 g_free(utf8_filename);
2781 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2786 alertpanel_notice(ngettext(
2787 "The following file has been attached: \n%s",
2788 "The following files have been attached: \n%s", att), warn_files);
2793 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2806 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2808 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2809 {"Cc:", NULL, TRUE},
2810 {"References:", NULL, FALSE},
2811 {"Bcc:", NULL, TRUE},
2812 {"Newsgroups:", NULL, TRUE},
2813 {"Followup-To:", NULL, TRUE},
2814 {"List-Post:", NULL, FALSE},
2815 {"X-Priority:", NULL, FALSE},
2816 {NULL, NULL, FALSE}};
2832 cm_return_val_if_fail(msginfo != NULL, -1);
2834 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2835 procheader_get_header_fields(fp, hentry);
2838 if (hentry[H_REPLY_TO].body != NULL) {
2839 if (hentry[H_REPLY_TO].body[0] != '\0') {
2841 conv_unmime_header(hentry[H_REPLY_TO].body,
2844 g_free(hentry[H_REPLY_TO].body);
2845 hentry[H_REPLY_TO].body = NULL;
2847 if (hentry[H_CC].body != NULL) {
2848 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2849 g_free(hentry[H_CC].body);
2850 hentry[H_CC].body = NULL;
2852 if (hentry[H_REFERENCES].body != NULL) {
2853 if (compose->mode == COMPOSE_REEDIT)
2854 compose->references = hentry[H_REFERENCES].body;
2856 compose->references = compose_parse_references
2857 (hentry[H_REFERENCES].body, msginfo->msgid);
2858 g_free(hentry[H_REFERENCES].body);
2860 hentry[H_REFERENCES].body = NULL;
2862 if (hentry[H_BCC].body != NULL) {
2863 if (compose->mode == COMPOSE_REEDIT)
2865 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2866 g_free(hentry[H_BCC].body);
2867 hentry[H_BCC].body = NULL;
2869 if (hentry[H_NEWSGROUPS].body != NULL) {
2870 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2871 hentry[H_NEWSGROUPS].body = NULL;
2873 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2874 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2875 compose->followup_to =
2876 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2879 g_free(hentry[H_FOLLOWUP_TO].body);
2880 hentry[H_FOLLOWUP_TO].body = NULL;
2882 if (hentry[H_LIST_POST].body != NULL) {
2883 gchar *to = NULL, *start = NULL;
2885 extract_address(hentry[H_LIST_POST].body);
2886 if (hentry[H_LIST_POST].body[0] != '\0') {
2887 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2889 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2890 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2893 g_free(compose->ml_post);
2894 compose->ml_post = to;
2897 g_free(hentry[H_LIST_POST].body);
2898 hentry[H_LIST_POST].body = NULL;
2901 /* CLAWS - X-Priority */
2902 if (compose->mode == COMPOSE_REEDIT)
2903 if (hentry[H_X_PRIORITY].body != NULL) {
2906 priority = atoi(hentry[H_X_PRIORITY].body);
2907 g_free(hentry[H_X_PRIORITY].body);
2909 hentry[H_X_PRIORITY].body = NULL;
2911 if (priority < PRIORITY_HIGHEST ||
2912 priority > PRIORITY_LOWEST)
2913 priority = PRIORITY_NORMAL;
2915 compose->priority = priority;
2918 if (compose->mode == COMPOSE_REEDIT) {
2919 if (msginfo->inreplyto && *msginfo->inreplyto)
2920 compose->inreplyto = g_strdup(msginfo->inreplyto);
2924 if (msginfo->msgid && *msginfo->msgid)
2925 compose->inreplyto = g_strdup(msginfo->msgid);
2927 if (!compose->references) {
2928 if (msginfo->msgid && *msginfo->msgid) {
2929 if (msginfo->inreplyto && *msginfo->inreplyto)
2930 compose->references =
2931 g_strdup_printf("<%s>\n\t<%s>",
2935 compose->references =
2936 g_strconcat("<", msginfo->msgid, ">",
2938 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2939 compose->references =
2940 g_strconcat("<", msginfo->inreplyto, ">",
2948 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2953 cm_return_val_if_fail(msginfo != NULL, -1);
2955 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2956 procheader_get_header_fields(fp, entries);
2960 while (he != NULL && he->name != NULL) {
2962 GtkListStore *model = NULL;
2964 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2965 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2966 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2967 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2968 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2975 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2977 GSList *ref_id_list, *cur;
2981 ref_id_list = references_list_append(NULL, ref);
2982 if (!ref_id_list) return NULL;
2983 if (msgid && *msgid)
2984 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2989 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2990 /* "<" + Message-ID + ">" + CR+LF+TAB */
2991 len += strlen((gchar *)cur->data) + 5;
2993 if (len > MAX_REFERENCES_LEN) {
2994 /* remove second message-ID */
2995 if (ref_id_list && ref_id_list->next &&
2996 ref_id_list->next->next) {
2997 g_free(ref_id_list->next->data);
2998 ref_id_list = g_slist_remove
2999 (ref_id_list, ref_id_list->next->data);
3001 slist_free_strings_full(ref_id_list);
3008 new_ref = g_string_new("");
3009 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3010 if (new_ref->len > 0)
3011 g_string_append(new_ref, "\n\t");
3012 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3015 slist_free_strings_full(ref_id_list);
3017 new_ref_str = new_ref->str;
3018 g_string_free(new_ref, FALSE);
3023 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3024 const gchar *fmt, const gchar *qmark,
3025 const gchar *body, gboolean rewrap,
3026 gboolean need_unescape,
3027 const gchar *err_msg)
3029 MsgInfo* dummyinfo = NULL;
3030 gchar *quote_str = NULL;
3032 gboolean prev_autowrap;
3033 const gchar *trimmed_body = body;
3034 gint cursor_pos = -1;
3035 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3036 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3041 SIGNAL_BLOCK(buffer);
3044 dummyinfo = compose_msginfo_new_from_compose(compose);
3045 msginfo = dummyinfo;
3048 if (qmark != NULL) {
3050 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3051 compose->gtkaspell);
3053 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3055 quote_fmt_scan_string(qmark);
3058 buf = quote_fmt_get_buffer();
3060 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3062 Xstrdup_a(quote_str, buf, goto error)
3065 if (fmt && *fmt != '\0') {
3068 while (*trimmed_body == '\n')
3072 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3073 compose->gtkaspell);
3075 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3077 if (need_unescape) {
3080 /* decode \-escape sequences in the internal representation of the quote format */
3081 tmp = g_malloc(strlen(fmt)+1);
3082 pref_get_unescaped_pref(tmp, fmt);
3083 quote_fmt_scan_string(tmp);
3087 quote_fmt_scan_string(fmt);
3091 buf = quote_fmt_get_buffer();
3093 gint line = quote_fmt_get_line();
3094 alertpanel_error(err_msg, line);
3100 prev_autowrap = compose->autowrap;
3101 compose->autowrap = FALSE;
3103 mark = gtk_text_buffer_get_insert(buffer);
3104 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3105 if (g_utf8_validate(buf, -1, NULL)) {
3106 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3108 gchar *tmpout = NULL;
3109 tmpout = conv_codeset_strdup
3110 (buf, conv_get_locale_charset_str_no_utf8(),
3112 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3114 tmpout = g_malloc(strlen(buf)*2+1);
3115 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3117 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3121 cursor_pos = quote_fmt_get_cursor_pos();
3122 if (cursor_pos == -1)
3123 cursor_pos = gtk_text_iter_get_offset(&iter);
3124 compose->set_cursor_pos = cursor_pos;
3126 gtk_text_buffer_get_start_iter(buffer, &iter);
3127 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3128 gtk_text_buffer_place_cursor(buffer, &iter);
3130 compose->autowrap = prev_autowrap;
3131 if (compose->autowrap && rewrap)
3132 compose_wrap_all(compose);
3139 SIGNAL_UNBLOCK(buffer);
3141 procmsg_msginfo_free( dummyinfo );
3146 /* if ml_post is of type addr@host and from is of type
3147 * addr-anything@host, return TRUE
3149 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3151 gchar *left_ml = NULL;
3152 gchar *right_ml = NULL;
3153 gchar *left_from = NULL;
3154 gchar *right_from = NULL;
3155 gboolean result = FALSE;
3157 if (!ml_post || !from)
3160 left_ml = g_strdup(ml_post);
3161 if (strstr(left_ml, "@")) {
3162 right_ml = strstr(left_ml, "@")+1;
3163 *(strstr(left_ml, "@")) = '\0';
3166 left_from = g_strdup(from);
3167 if (strstr(left_from, "@")) {
3168 right_from = strstr(left_from, "@")+1;
3169 *(strstr(left_from, "@")) = '\0';
3172 if (left_ml && left_from && right_ml && right_from
3173 && !strncmp(left_from, left_ml, strlen(left_ml))
3174 && !strcmp(right_from, right_ml)) {
3183 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3184 gboolean respect_default_to)
3188 if (!folder || !folder->prefs)
3191 if (respect_default_to && folder->prefs->enable_default_to) {
3192 compose_entry_append(compose, folder->prefs->default_to,
3193 COMPOSE_TO, PREF_FOLDER);
3194 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3196 if (folder->prefs->enable_default_cc)
3197 compose_entry_append(compose, folder->prefs->default_cc,
3198 COMPOSE_CC, PREF_FOLDER);
3199 if (folder->prefs->enable_default_bcc)
3200 compose_entry_append(compose, folder->prefs->default_bcc,
3201 COMPOSE_BCC, PREF_FOLDER);
3202 if (folder->prefs->enable_default_replyto)
3203 compose_entry_append(compose, folder->prefs->default_replyto,
3204 COMPOSE_REPLYTO, PREF_FOLDER);
3207 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3212 if (!compose || !msginfo)
3215 if (msginfo->subject && *msginfo->subject) {
3216 buf = p = g_strdup(msginfo->subject);
3217 p += subject_get_prefix_length(p);
3218 memmove(buf, p, strlen(p) + 1);
3220 buf2 = g_strdup_printf("Re: %s", buf);
3221 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3226 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3229 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3230 gboolean to_all, gboolean to_ml,
3232 gboolean followup_and_reply_to)
3234 GSList *cc_list = NULL;
3237 gchar *replyto = NULL;
3238 gchar *ac_email = NULL;
3240 gboolean reply_to_ml = FALSE;
3241 gboolean default_reply_to = FALSE;
3243 cm_return_if_fail(compose->account != NULL);
3244 cm_return_if_fail(msginfo != NULL);
3246 reply_to_ml = to_ml && compose->ml_post;
3248 default_reply_to = msginfo->folder &&
3249 msginfo->folder->prefs->enable_default_reply_to;
3251 if (compose->account->protocol != A_NNTP) {
3252 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3254 if (reply_to_ml && !default_reply_to) {
3256 gboolean is_subscr = is_subscription(compose->ml_post,
3259 /* normal answer to ml post with a reply-to */
3260 compose_entry_append(compose,
3262 COMPOSE_TO, PREF_ML);
3263 if (compose->replyto)
3264 compose_entry_append(compose,
3266 COMPOSE_CC, PREF_ML);
3268 /* answer to subscription confirmation */
3269 if (compose->replyto)
3270 compose_entry_append(compose,
3272 COMPOSE_TO, PREF_ML);
3273 else if (msginfo->from)
3274 compose_entry_append(compose,
3276 COMPOSE_TO, PREF_ML);
3279 else if (!(to_all || to_sender) && default_reply_to) {
3280 compose_entry_append(compose,
3281 msginfo->folder->prefs->default_reply_to,
3282 COMPOSE_TO, PREF_FOLDER);
3283 compose_entry_mark_default_to(compose,
3284 msginfo->folder->prefs->default_reply_to);
3290 compose_entry_append(compose, msginfo->from,
3291 COMPOSE_TO, PREF_NONE);
3293 Xstrdup_a(tmp1, msginfo->from, return);
3294 extract_address(tmp1);
3295 compose_entry_append(compose,
3296 (!account_find_from_address(tmp1, FALSE))
3299 COMPOSE_TO, PREF_NONE);
3301 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3302 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3303 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3304 if (compose->replyto) {
3305 compose_entry_append(compose,
3307 COMPOSE_TO, PREF_NONE);
3309 compose_entry_append(compose,
3310 msginfo->from ? msginfo->from : "",
3311 COMPOSE_TO, PREF_NONE);
3314 /* replying to own mail, use original recp */
3315 compose_entry_append(compose,
3316 msginfo->to ? msginfo->to : "",
3317 COMPOSE_TO, PREF_NONE);
3318 compose_entry_append(compose,
3319 msginfo->cc ? msginfo->cc : "",
3320 COMPOSE_CC, PREF_NONE);
3325 if (to_sender || (compose->followup_to &&
3326 !strncmp(compose->followup_to, "poster", 6)))
3327 compose_entry_append
3329 (compose->replyto ? compose->replyto :
3330 msginfo->from ? msginfo->from : ""),
3331 COMPOSE_TO, PREF_NONE);
3333 else if (followup_and_reply_to || to_all) {
3334 compose_entry_append
3336 (compose->replyto ? compose->replyto :
3337 msginfo->from ? msginfo->from : ""),
3338 COMPOSE_TO, PREF_NONE);
3340 compose_entry_append
3342 compose->followup_to ? compose->followup_to :
3343 compose->newsgroups ? compose->newsgroups : "",
3344 COMPOSE_NEWSGROUPS, PREF_NONE);
3347 compose_entry_append
3349 compose->followup_to ? compose->followup_to :
3350 compose->newsgroups ? compose->newsgroups : "",
3351 COMPOSE_NEWSGROUPS, PREF_NONE);
3353 compose_reply_set_subject(compose, msginfo);
3355 if (to_ml && compose->ml_post) return;
3356 if (!to_all || compose->account->protocol == A_NNTP) return;
3358 if (compose->replyto) {
3359 Xstrdup_a(replyto, compose->replyto, return);
3360 extract_address(replyto);
3362 if (msginfo->from) {
3363 Xstrdup_a(from, msginfo->from, return);
3364 extract_address(from);
3367 if (replyto && from)
3368 cc_list = address_list_append_with_comments(cc_list, from);
3369 if (to_all && msginfo->folder &&
3370 msginfo->folder->prefs->enable_default_reply_to)
3371 cc_list = address_list_append_with_comments(cc_list,
3372 msginfo->folder->prefs->default_reply_to);
3373 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3374 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3376 ac_email = g_utf8_strdown(compose->account->address, -1);
3379 for (cur = cc_list; cur != NULL; cur = cur->next) {
3380 gchar *addr = g_utf8_strdown(cur->data, -1);
3381 extract_address(addr);
3383 if (strcmp(ac_email, addr))
3384 compose_entry_append(compose, (gchar *)cur->data,
3385 COMPOSE_CC, PREF_NONE);
3387 debug_print("Cc address same as compose account's, ignoring\n");
3392 slist_free_strings_full(cc_list);
3398 #define SET_ENTRY(entry, str) \
3401 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3404 #define SET_ADDRESS(type, str) \
3407 compose_entry_append(compose, str, type, PREF_NONE); \
3410 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3412 cm_return_if_fail(msginfo != NULL);
3414 SET_ENTRY(subject_entry, msginfo->subject);
3415 SET_ENTRY(from_name, msginfo->from);
3416 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3417 SET_ADDRESS(COMPOSE_CC, compose->cc);
3418 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3419 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3420 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3421 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3423 compose_update_priority_menu_item(compose);
3424 compose_update_privacy_system_menu_item(compose, FALSE);
3425 compose_show_first_last_header(compose, TRUE);
3431 static void compose_insert_sig(Compose *compose, gboolean replace)
3433 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3434 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3436 GtkTextIter iter, iter_end;
3437 gint cur_pos, ins_pos;
3438 gboolean prev_autowrap;
3439 gboolean found = FALSE;
3440 gboolean exists = FALSE;
3442 cm_return_if_fail(compose->account != NULL);
3446 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3447 G_CALLBACK(compose_changed_cb),
3450 mark = gtk_text_buffer_get_insert(buffer);
3451 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3452 cur_pos = gtk_text_iter_get_offset (&iter);
3455 gtk_text_buffer_get_end_iter(buffer, &iter);
3457 exists = (compose->sig_str != NULL);
3460 GtkTextIter first_iter, start_iter, end_iter;
3462 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3464 if (!exists || compose->sig_str[0] == '\0')
3467 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3468 compose->signature_tag);
3471 /* include previous \n\n */
3472 gtk_text_iter_backward_chars(&first_iter, 1);
3473 start_iter = first_iter;
3474 end_iter = first_iter;
3476 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3477 compose->signature_tag);
3478 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3479 compose->signature_tag);
3481 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3487 g_free(compose->sig_str);
3488 compose->sig_str = account_get_signature_str(compose->account);
3490 cur_pos = gtk_text_iter_get_offset(&iter);
3492 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3493 g_free(compose->sig_str);
3494 compose->sig_str = NULL;
3496 if (compose->sig_inserted == FALSE)
3497 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3498 compose->sig_inserted = TRUE;
3500 cur_pos = gtk_text_iter_get_offset(&iter);
3501 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3503 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3504 gtk_text_iter_forward_chars(&iter, 1);
3505 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3506 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3508 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3509 cur_pos = gtk_text_buffer_get_char_count (buffer);
3512 /* put the cursor where it should be
3513 * either where the quote_fmt says, either where it was */
3514 if (compose->set_cursor_pos < 0)
3515 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3517 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3518 compose->set_cursor_pos);
3520 compose->set_cursor_pos = -1;
3521 gtk_text_buffer_place_cursor(buffer, &iter);
3522 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3523 G_CALLBACK(compose_changed_cb),
3529 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3532 GtkTextBuffer *buffer;
3535 const gchar *cur_encoding;
3536 gchar buf[BUFFSIZE];
3539 gboolean prev_autowrap;
3540 gboolean badtxt = FALSE;
3541 struct stat file_stat;
3543 GString *file_contents = NULL;
3545 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3547 /* get the size of the file we are about to insert */
3548 ret = g_stat(file, &file_stat);
3550 gchar *shortfile = g_path_get_basename(file);
3551 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3553 return COMPOSE_INSERT_NO_FILE;
3554 } else if (prefs_common.warn_large_insert == TRUE) {
3556 /* ask user for confirmation if the file is large */
3557 if (prefs_common.warn_large_insert_size < 0 ||
3558 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3562 msg = g_strdup_printf(_("You are about to insert a file of %s "
3563 "in the message body. Are you sure you want to do that?"),
3564 to_human_readable(file_stat.st_size));
3565 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3566 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3569 /* do we ask for confirmation next time? */
3570 if (aval & G_ALERTDISABLE) {
3571 /* no confirmation next time, disable feature in preferences */
3572 aval &= ~G_ALERTDISABLE;
3573 prefs_common.warn_large_insert = FALSE;
3576 /* abort file insertion if user canceled action */
3577 if (aval != G_ALERTALTERNATE) {
3578 return COMPOSE_INSERT_NO_FILE;
3584 if ((fp = g_fopen(file, "rb")) == NULL) {
3585 FILE_OP_ERROR(file, "fopen");
3586 return COMPOSE_INSERT_READ_ERROR;
3589 prev_autowrap = compose->autowrap;
3590 compose->autowrap = FALSE;
3592 text = GTK_TEXT_VIEW(compose->text);
3593 buffer = gtk_text_view_get_buffer(text);
3594 mark = gtk_text_buffer_get_insert(buffer);
3595 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3597 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3598 G_CALLBACK(text_inserted),
3601 cur_encoding = conv_get_locale_charset_str_no_utf8();
3603 file_contents = g_string_new("");
3604 while (fgets(buf, sizeof(buf), fp) != NULL) {
3607 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3608 str = g_strdup(buf);
3610 str = conv_codeset_strdup
3611 (buf, cur_encoding, CS_INTERNAL);
3614 /* strip <CR> if DOS/Windows file,
3615 replace <CR> with <LF> if Macintosh file. */
3618 if (len > 0 && str[len - 1] != '\n') {
3620 if (str[len] == '\r') str[len] = '\n';
3623 file_contents = g_string_append(file_contents, str);
3627 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3628 g_string_free(file_contents, TRUE);
3630 compose_changed_cb(NULL, compose);
3631 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3632 G_CALLBACK(text_inserted),
3634 compose->autowrap = prev_autowrap;
3635 if (compose->autowrap)
3636 compose_wrap_all(compose);
3641 return COMPOSE_INSERT_INVALID_CHARACTER;
3643 return COMPOSE_INSERT_SUCCESS;
3646 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3647 const gchar *filename,
3648 const gchar *content_type,
3649 const gchar *charset)
3657 GtkListStore *store;
3659 gboolean has_binary = FALSE;
3661 if (!is_file_exist(file)) {
3662 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3663 gboolean result = FALSE;
3664 if (file_from_uri && is_file_exist(file_from_uri)) {
3665 result = compose_attach_append(
3666 compose, file_from_uri,
3667 filename, content_type,
3670 g_free(file_from_uri);
3673 alertpanel_error("File %s doesn't exist\n", filename);
3676 if ((size = get_file_size(file)) < 0) {
3677 alertpanel_error("Can't get file size of %s\n", filename);
3681 /* In batch mode, we allow 0-length files to be attached no questions asked */
3682 if (size == 0 && !compose->batch) {
3683 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3684 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3685 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3686 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3689 if (aval != G_ALERTALTERNATE) {
3693 if ((fp = g_fopen(file, "rb")) == NULL) {
3694 alertpanel_error(_("Can't read %s."), filename);
3699 ainfo = g_new0(AttachInfo, 1);
3700 auto_ainfo = g_auto_pointer_new_with_free
3701 (ainfo, (GFreeFunc) compose_attach_info_free);
3702 ainfo->file = g_strdup(file);
3705 ainfo->content_type = g_strdup(content_type);
3706 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3708 MsgFlags flags = {0, 0};
3710 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3711 ainfo->encoding = ENC_7BIT;
3713 ainfo->encoding = ENC_8BIT;
3715 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3716 if (msginfo && msginfo->subject)
3717 name = g_strdup(msginfo->subject);
3719 name = g_path_get_basename(filename ? filename : file);
3721 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3723 procmsg_msginfo_free(msginfo);
3725 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3726 ainfo->charset = g_strdup(charset);
3727 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3729 ainfo->encoding = ENC_BASE64;
3731 name = g_path_get_basename(filename ? filename : file);
3732 ainfo->name = g_strdup(name);
3736 ainfo->content_type = procmime_get_mime_type(file);
3737 if (!ainfo->content_type) {
3738 ainfo->content_type =
3739 g_strdup("application/octet-stream");
3740 ainfo->encoding = ENC_BASE64;
3741 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3743 procmime_get_encoding_for_text_file(file, &has_binary);
3745 ainfo->encoding = ENC_BASE64;
3746 name = g_path_get_basename(filename ? filename : file);
3747 ainfo->name = g_strdup(name);
3751 if (ainfo->name != NULL
3752 && !strcmp(ainfo->name, ".")) {
3753 g_free(ainfo->name);
3757 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3758 g_free(ainfo->content_type);
3759 ainfo->content_type = g_strdup("application/octet-stream");
3760 g_free(ainfo->charset);
3761 ainfo->charset = NULL;
3764 ainfo->size = (goffset)size;
3765 size_text = to_human_readable((goffset)size);
3767 store = GTK_LIST_STORE(gtk_tree_view_get_model
3768 (GTK_TREE_VIEW(compose->attach_clist)));
3770 gtk_list_store_append(store, &iter);
3771 gtk_list_store_set(store, &iter,
3772 COL_MIMETYPE, ainfo->content_type,
3773 COL_SIZE, size_text,
3774 COL_NAME, ainfo->name,
3775 COL_CHARSET, ainfo->charset,
3777 COL_AUTODATA, auto_ainfo,
3780 g_auto_pointer_free(auto_ainfo);
3781 compose_attach_update_label(compose);
3785 static void compose_use_signing(Compose *compose, gboolean use_signing)
3787 compose->use_signing = use_signing;
3788 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3791 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3793 compose->use_encryption = use_encryption;
3794 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3797 #define NEXT_PART_NOT_CHILD(info) \
3799 node = info->node; \
3800 while (node->children) \
3801 node = g_node_last_child(node); \
3802 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3805 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3809 MimeInfo *firsttext = NULL;
3810 MimeInfo *encrypted = NULL;
3813 const gchar *partname = NULL;
3815 mimeinfo = procmime_scan_message(msginfo);
3816 if (!mimeinfo) return;
3818 if (mimeinfo->node->children == NULL) {
3819 procmime_mimeinfo_free_all(mimeinfo);
3823 /* find first content part */
3824 child = (MimeInfo *) mimeinfo->node->children->data;
3825 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3826 child = (MimeInfo *)child->node->children->data;
3829 if (child->type == MIMETYPE_TEXT) {
3831 debug_print("First text part found\n");
3832 } else if (compose->mode == COMPOSE_REEDIT &&
3833 child->type == MIMETYPE_APPLICATION &&
3834 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3835 encrypted = (MimeInfo *)child->node->parent->data;
3838 child = (MimeInfo *) mimeinfo->node->children->data;
3839 while (child != NULL) {
3842 if (child == encrypted) {
3843 /* skip this part of tree */
3844 NEXT_PART_NOT_CHILD(child);
3848 if (child->type == MIMETYPE_MULTIPART) {
3849 /* get the actual content */
3850 child = procmime_mimeinfo_next(child);
3854 if (child == firsttext) {
3855 child = procmime_mimeinfo_next(child);
3859 outfile = procmime_get_tmp_file_name(child);
3860 if ((err = procmime_get_part(outfile, child)) < 0)
3861 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3863 gchar *content_type;
3865 content_type = procmime_get_content_type_str(child->type, child->subtype);
3867 /* if we meet a pgp signature, we don't attach it, but
3868 * we force signing. */
3869 if ((strcmp(content_type, "application/pgp-signature") &&
3870 strcmp(content_type, "application/pkcs7-signature") &&
3871 strcmp(content_type, "application/x-pkcs7-signature"))
3872 || compose->mode == COMPOSE_REDIRECT) {
3873 partname = procmime_mimeinfo_get_parameter(child, "filename");
3874 if (partname == NULL)
3875 partname = procmime_mimeinfo_get_parameter(child, "name");
3876 if (partname == NULL)
3878 compose_attach_append(compose, outfile,
3879 partname, content_type,
3880 procmime_mimeinfo_get_parameter(child, "charset"));
3882 compose_force_signing(compose, compose->account, NULL);
3884 g_free(content_type);
3887 NEXT_PART_NOT_CHILD(child);
3889 procmime_mimeinfo_free_all(mimeinfo);
3892 #undef NEXT_PART_NOT_CHILD
3897 WAIT_FOR_INDENT_CHAR,
3898 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3901 /* return indent length, we allow:
3902 indent characters followed by indent characters or spaces/tabs,
3903 alphabets and numbers immediately followed by indent characters,
3904 and the repeating sequences of the above
3905 If quote ends with multiple spaces, only the first one is included. */
3906 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3907 const GtkTextIter *start, gint *len)
3909 GtkTextIter iter = *start;
3913 IndentState state = WAIT_FOR_INDENT_CHAR;
3916 gint alnum_count = 0;
3917 gint space_count = 0;
3920 if (prefs_common.quote_chars == NULL) {
3924 while (!gtk_text_iter_ends_line(&iter)) {
3925 wc = gtk_text_iter_get_char(&iter);
3926 if (g_unichar_iswide(wc))
3928 clen = g_unichar_to_utf8(wc, ch);
3932 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3933 is_space = g_unichar_isspace(wc);
3935 if (state == WAIT_FOR_INDENT_CHAR) {
3936 if (!is_indent && !g_unichar_isalnum(wc))
3939 quote_len += alnum_count + space_count + 1;
3940 alnum_count = space_count = 0;
3941 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3944 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3945 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3949 else if (is_indent) {
3950 quote_len += alnum_count + space_count + 1;
3951 alnum_count = space_count = 0;
3954 state = WAIT_FOR_INDENT_CHAR;
3958 gtk_text_iter_forward_char(&iter);
3961 if (quote_len > 0 && space_count > 0)
3967 if (quote_len > 0) {
3969 gtk_text_iter_forward_chars(&iter, quote_len);
3970 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3976 /* return >0 if the line is itemized */
3977 static int compose_itemized_length(GtkTextBuffer *buffer,
3978 const GtkTextIter *start)
3980 GtkTextIter iter = *start;
3985 if (gtk_text_iter_ends_line(&iter))
3990 wc = gtk_text_iter_get_char(&iter);
3991 if (!g_unichar_isspace(wc))
3993 gtk_text_iter_forward_char(&iter);
3994 if (gtk_text_iter_ends_line(&iter))
3998 clen = g_unichar_to_utf8(wc, ch);
4002 if (!strchr("*-+", ch[0]))
4005 gtk_text_iter_forward_char(&iter);
4006 if (gtk_text_iter_ends_line(&iter))
4008 wc = gtk_text_iter_get_char(&iter);
4009 if (g_unichar_isspace(wc)) {
4015 /* return the string at the start of the itemization */
4016 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4017 const GtkTextIter *start)
4019 GtkTextIter iter = *start;
4022 GString *item_chars = g_string_new("");
4025 if (gtk_text_iter_ends_line(&iter))
4030 wc = gtk_text_iter_get_char(&iter);
4031 if (!g_unichar_isspace(wc))
4033 gtk_text_iter_forward_char(&iter);
4034 if (gtk_text_iter_ends_line(&iter))
4036 g_string_append_unichar(item_chars, wc);
4039 str = item_chars->str;
4040 g_string_free(item_chars, FALSE);
4044 /* return the number of spaces at a line's start */
4045 static int compose_left_offset_length(GtkTextBuffer *buffer,
4046 const GtkTextIter *start)
4048 GtkTextIter iter = *start;
4051 if (gtk_text_iter_ends_line(&iter))
4055 wc = gtk_text_iter_get_char(&iter);
4056 if (!g_unichar_isspace(wc))
4059 gtk_text_iter_forward_char(&iter);
4060 if (gtk_text_iter_ends_line(&iter))
4064 gtk_text_iter_forward_char(&iter);
4065 if (gtk_text_iter_ends_line(&iter))
4070 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4071 const GtkTextIter *start,
4072 GtkTextIter *break_pos,
4076 GtkTextIter iter = *start, line_end = *start;
4077 PangoLogAttr *attrs;
4084 gboolean can_break = FALSE;
4085 gboolean do_break = FALSE;
4086 gboolean was_white = FALSE;
4087 gboolean prev_dont_break = FALSE;
4089 gtk_text_iter_forward_to_line_end(&line_end);
4090 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4091 len = g_utf8_strlen(str, -1);
4095 g_warning("compose_get_line_break_pos: len = 0!\n");
4099 /* g_print("breaking line: %d: %s (len = %d)\n",
4100 gtk_text_iter_get_line(&iter), str, len); */
4102 attrs = g_new(PangoLogAttr, len + 1);
4104 pango_default_break(str, -1, NULL, attrs, len + 1);
4108 /* skip quote and leading spaces */
4109 for (i = 0; *p != '\0' && i < len; i++) {
4112 wc = g_utf8_get_char(p);
4113 if (i >= quote_len && !g_unichar_isspace(wc))
4115 if (g_unichar_iswide(wc))
4117 else if (*p == '\t')
4121 p = g_utf8_next_char(p);
4124 for (; *p != '\0' && i < len; i++) {
4125 PangoLogAttr *attr = attrs + i;
4129 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4132 was_white = attr->is_white;
4134 /* don't wrap URI */
4135 if ((uri_len = get_uri_len(p)) > 0) {
4137 if (pos > 0 && col > max_col) {
4147 wc = g_utf8_get_char(p);
4148 if (g_unichar_iswide(wc)) {
4150 if (prev_dont_break && can_break && attr->is_line_break)
4152 } else if (*p == '\t')
4156 if (pos > 0 && col > max_col) {
4161 if (*p == '-' || *p == '/')
4162 prev_dont_break = TRUE;
4164 prev_dont_break = FALSE;
4166 p = g_utf8_next_char(p);
4170 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4175 *break_pos = *start;
4176 gtk_text_iter_set_line_offset(break_pos, pos);
4181 static gboolean compose_join_next_line(Compose *compose,
4182 GtkTextBuffer *buffer,
4184 const gchar *quote_str)
4186 GtkTextIter iter_ = *iter, cur, prev, next, end;
4187 PangoLogAttr attrs[3];
4189 gchar *next_quote_str;
4192 gboolean keep_cursor = FALSE;
4194 if (!gtk_text_iter_forward_line(&iter_) ||
4195 gtk_text_iter_ends_line(&iter_)) {
4198 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4200 if ((quote_str || next_quote_str) &&
4201 strcmp2(quote_str, next_quote_str) != 0) {
4202 g_free(next_quote_str);
4205 g_free(next_quote_str);
4208 if (quote_len > 0) {
4209 gtk_text_iter_forward_chars(&end, quote_len);
4210 if (gtk_text_iter_ends_line(&end)) {
4215 /* don't join itemized lines */
4216 if (compose_itemized_length(buffer, &end) > 0) {
4220 /* don't join signature separator */
4221 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4224 /* delete quote str */
4226 gtk_text_buffer_delete(buffer, &iter_, &end);
4228 /* don't join line breaks put by the user */
4230 gtk_text_iter_backward_char(&cur);
4231 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4232 gtk_text_iter_forward_char(&cur);
4236 gtk_text_iter_forward_char(&cur);
4237 /* delete linebreak and extra spaces */
4238 while (gtk_text_iter_backward_char(&cur)) {
4239 wc1 = gtk_text_iter_get_char(&cur);
4240 if (!g_unichar_isspace(wc1))
4245 while (!gtk_text_iter_ends_line(&cur)) {
4246 wc1 = gtk_text_iter_get_char(&cur);
4247 if (!g_unichar_isspace(wc1))
4249 gtk_text_iter_forward_char(&cur);
4252 if (!gtk_text_iter_equal(&prev, &next)) {
4255 mark = gtk_text_buffer_get_insert(buffer);
4256 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4257 if (gtk_text_iter_equal(&prev, &cur))
4259 gtk_text_buffer_delete(buffer, &prev, &next);
4263 /* insert space if required */
4264 gtk_text_iter_backward_char(&prev);
4265 wc1 = gtk_text_iter_get_char(&prev);
4266 wc2 = gtk_text_iter_get_char(&next);
4267 gtk_text_iter_forward_char(&next);
4268 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4269 pango_default_break(str, -1, NULL, attrs, 3);
4270 if (!attrs[1].is_line_break ||
4271 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4272 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4274 gtk_text_iter_backward_char(&iter_);
4275 gtk_text_buffer_place_cursor(buffer, &iter_);
4284 #define ADD_TXT_POS(bp_, ep_, pti_) \
4285 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4286 last = last->next; \
4287 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4288 last->next = NULL; \
4290 g_warning("alloc error scanning URIs\n"); \
4293 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4295 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4296 GtkTextBuffer *buffer;
4297 GtkTextIter iter, break_pos, end_of_line;
4298 gchar *quote_str = NULL;
4300 gboolean wrap_quote = prefs_common.linewrap_quote;
4301 gboolean prev_autowrap = compose->autowrap;
4302 gint startq_offset = -1, noq_offset = -1;
4303 gint uri_start = -1, uri_stop = -1;
4304 gint nouri_start = -1, nouri_stop = -1;
4305 gint num_blocks = 0;
4306 gint quotelevel = -1;
4307 gboolean modified = force;
4308 gboolean removed = FALSE;
4309 gboolean modified_before_remove = FALSE;
4311 gboolean start = TRUE;
4312 gint itemized_len = 0, rem_item_len = 0;
4313 gchar *itemized_chars = NULL;
4314 gboolean item_continuation = FALSE;
4319 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4323 compose->autowrap = FALSE;
4325 buffer = gtk_text_view_get_buffer(text);
4326 undo_wrapping(compose->undostruct, TRUE);
4331 mark = gtk_text_buffer_get_insert(buffer);
4332 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4336 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4337 if (gtk_text_iter_ends_line(&iter)) {
4338 while (gtk_text_iter_ends_line(&iter) &&
4339 gtk_text_iter_forward_line(&iter))
4342 while (gtk_text_iter_backward_line(&iter)) {
4343 if (gtk_text_iter_ends_line(&iter)) {
4344 gtk_text_iter_forward_line(&iter);
4350 /* move to line start */
4351 gtk_text_iter_set_line_offset(&iter, 0);
4354 itemized_len = compose_itemized_length(buffer, &iter);
4356 if (!itemized_len) {
4357 itemized_len = compose_left_offset_length(buffer, &iter);
4358 item_continuation = TRUE;
4362 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4364 /* go until paragraph end (empty line) */
4365 while (start || !gtk_text_iter_ends_line(&iter)) {
4366 gchar *scanpos = NULL;
4367 /* parse table - in order of priority */
4369 const gchar *needle; /* token */
4371 /* token search function */
4372 gchar *(*search) (const gchar *haystack,
4373 const gchar *needle);
4374 /* part parsing function */
4375 gboolean (*parse) (const gchar *start,
4376 const gchar *scanpos,
4380 /* part to URI function */
4381 gchar *(*build_uri) (const gchar *bp,
4385 static struct table parser[] = {
4386 {"http://", strcasestr, get_uri_part, make_uri_string},
4387 {"https://", strcasestr, get_uri_part, make_uri_string},
4388 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4389 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4390 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4391 {"www.", strcasestr, get_uri_part, make_http_string},
4392 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4393 {"@", strcasestr, get_email_part, make_email_string}
4395 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4396 gint last_index = PARSE_ELEMS;
4398 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4402 if (!prev_autowrap && num_blocks == 0) {
4404 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4405 G_CALLBACK(text_inserted),
4408 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4411 uri_start = uri_stop = -1;
4413 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4416 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4417 if (startq_offset == -1)
4418 startq_offset = gtk_text_iter_get_offset(&iter);
4419 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4420 if (quotelevel > 2) {
4421 /* recycle colors */
4422 if (prefs_common.recycle_quote_colors)
4431 if (startq_offset == -1)
4432 noq_offset = gtk_text_iter_get_offset(&iter);
4436 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4439 if (gtk_text_iter_ends_line(&iter)) {
4441 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4442 prefs_common.linewrap_len,
4444 GtkTextIter prev, next, cur;
4445 if (prev_autowrap != FALSE || force) {
4446 compose->automatic_break = TRUE;
4448 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4449 compose->automatic_break = FALSE;
4450 if (itemized_len && compose->autoindent) {
4451 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4452 if (!item_continuation)
4453 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4455 } else if (quote_str && wrap_quote) {
4456 compose->automatic_break = TRUE;
4458 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4459 compose->automatic_break = FALSE;
4460 if (itemized_len && compose->autoindent) {
4461 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4462 if (!item_continuation)
4463 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4467 /* remove trailing spaces */
4469 rem_item_len = itemized_len;
4470 while (compose->autoindent && rem_item_len-- > 0)
4471 gtk_text_iter_backward_char(&cur);
4472 gtk_text_iter_backward_char(&cur);
4475 while (!gtk_text_iter_starts_line(&cur)) {
4478 gtk_text_iter_backward_char(&cur);
4479 wc = gtk_text_iter_get_char(&cur);
4480 if (!g_unichar_isspace(wc))
4484 if (!gtk_text_iter_equal(&prev, &next)) {
4485 gtk_text_buffer_delete(buffer, &prev, &next);
4487 gtk_text_iter_forward_char(&break_pos);
4491 gtk_text_buffer_insert(buffer, &break_pos,
4495 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4497 /* move iter to current line start */
4498 gtk_text_iter_set_line_offset(&iter, 0);
4505 /* move iter to next line start */
4511 if (!prev_autowrap && num_blocks > 0) {
4513 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4514 G_CALLBACK(text_inserted),
4518 while (!gtk_text_iter_ends_line(&end_of_line)) {
4519 gtk_text_iter_forward_char(&end_of_line);
4521 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4523 nouri_start = gtk_text_iter_get_offset(&iter);
4524 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4526 walk_pos = gtk_text_iter_get_offset(&iter);
4527 /* FIXME: this looks phony. scanning for anything in the parse table */
4528 for (n = 0; n < PARSE_ELEMS; n++) {
4531 tmp = parser[n].search(walk, parser[n].needle);
4533 if (scanpos == NULL || tmp < scanpos) {
4542 /* check if URI can be parsed */
4543 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4544 (const gchar **)&ep, FALSE)
4545 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4549 strlen(parser[last_index].needle);
4552 uri_start = walk_pos + (bp - o_walk);
4553 uri_stop = walk_pos + (ep - o_walk);
4557 gtk_text_iter_forward_line(&iter);
4560 if (startq_offset != -1) {
4561 GtkTextIter startquote, endquote;
4562 gtk_text_buffer_get_iter_at_offset(
4563 buffer, &startquote, startq_offset);
4566 switch (quotelevel) {
4568 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4569 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4570 gtk_text_buffer_apply_tag_by_name(
4571 buffer, "quote0", &startquote, &endquote);
4572 gtk_text_buffer_remove_tag_by_name(
4573 buffer, "quote1", &startquote, &endquote);
4574 gtk_text_buffer_remove_tag_by_name(
4575 buffer, "quote2", &startquote, &endquote);
4580 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4581 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4582 gtk_text_buffer_apply_tag_by_name(
4583 buffer, "quote1", &startquote, &endquote);
4584 gtk_text_buffer_remove_tag_by_name(
4585 buffer, "quote0", &startquote, &endquote);
4586 gtk_text_buffer_remove_tag_by_name(
4587 buffer, "quote2", &startquote, &endquote);
4592 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4593 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4594 gtk_text_buffer_apply_tag_by_name(
4595 buffer, "quote2", &startquote, &endquote);
4596 gtk_text_buffer_remove_tag_by_name(
4597 buffer, "quote0", &startquote, &endquote);
4598 gtk_text_buffer_remove_tag_by_name(
4599 buffer, "quote1", &startquote, &endquote);
4605 } else if (noq_offset != -1) {
4606 GtkTextIter startnoquote, endnoquote;
4607 gtk_text_buffer_get_iter_at_offset(
4608 buffer, &startnoquote, noq_offset);
4611 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4612 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4613 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4614 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4615 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4616 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4617 gtk_text_buffer_remove_tag_by_name(
4618 buffer, "quote0", &startnoquote, &endnoquote);
4619 gtk_text_buffer_remove_tag_by_name(
4620 buffer, "quote1", &startnoquote, &endnoquote);
4621 gtk_text_buffer_remove_tag_by_name(
4622 buffer, "quote2", &startnoquote, &endnoquote);
4628 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4629 GtkTextIter nouri_start_iter, nouri_end_iter;
4630 gtk_text_buffer_get_iter_at_offset(
4631 buffer, &nouri_start_iter, nouri_start);
4632 gtk_text_buffer_get_iter_at_offset(
4633 buffer, &nouri_end_iter, nouri_stop);
4634 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4635 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4636 gtk_text_buffer_remove_tag_by_name(
4637 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4638 modified_before_remove = modified;
4643 if (uri_start >= 0 && uri_stop > 0) {
4644 GtkTextIter uri_start_iter, uri_end_iter, back;
4645 gtk_text_buffer_get_iter_at_offset(
4646 buffer, &uri_start_iter, uri_start);
4647 gtk_text_buffer_get_iter_at_offset(
4648 buffer, &uri_end_iter, uri_stop);
4649 back = uri_end_iter;
4650 gtk_text_iter_backward_char(&back);
4651 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4652 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4653 gtk_text_buffer_apply_tag_by_name(
4654 buffer, "link", &uri_start_iter, &uri_end_iter);
4656 if (removed && !modified_before_remove) {
4662 // debug_print("not modified, out after %d lines\n", lines);
4666 // debug_print("modified, out after %d lines\n", lines);
4668 g_free(itemized_chars);
4671 undo_wrapping(compose->undostruct, FALSE);
4672 compose->autowrap = prev_autowrap;
4677 void compose_action_cb(void *data)
4679 Compose *compose = (Compose *)data;
4680 compose_wrap_all(compose);
4683 static void compose_wrap_all(Compose *compose)
4685 compose_wrap_all_full(compose, FALSE);
4688 static void compose_wrap_all_full(Compose *compose, gboolean force)
4690 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4691 GtkTextBuffer *buffer;
4693 gboolean modified = TRUE;
4695 buffer = gtk_text_view_get_buffer(text);
4697 gtk_text_buffer_get_start_iter(buffer, &iter);
4698 while (!gtk_text_iter_is_end(&iter) && modified)
4699 modified = compose_beautify_paragraph(compose, &iter, force);
4703 static void compose_set_title(Compose *compose)
4709 edited = compose->modified ? _(" [Edited]") : "";
4711 subject = gtk_editable_get_chars(
4712 GTK_EDITABLE(compose->subject_entry), 0, -1);
4714 #ifndef GENERIC_UMPC
4715 if (subject && strlen(subject))
4716 str = g_strdup_printf(_("%s - Compose message%s"),
4719 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4721 str = g_strdup(_("Compose message"));
4724 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4730 * compose_current_mail_account:
4732 * Find a current mail account (the currently selected account, or the
4733 * default account, if a news account is currently selected). If a
4734 * mail account cannot be found, display an error message.
4736 * Return value: Mail account, or NULL if not found.
4738 static PrefsAccount *
4739 compose_current_mail_account(void)
4743 if (cur_account && cur_account->protocol != A_NNTP)
4746 ac = account_get_default();
4747 if (!ac || ac->protocol == A_NNTP) {
4748 alertpanel_error(_("Account for sending mail is not specified.\n"
4749 "Please select a mail account before sending."));
4756 #define QUOTE_IF_REQUIRED(out, str) \
4758 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4762 len = strlen(str) + 3; \
4763 if ((__tmp = alloca(len)) == NULL) { \
4764 g_warning("can't allocate memory\n"); \
4765 g_string_free(header, TRUE); \
4768 g_snprintf(__tmp, len, "\"%s\"", str); \
4773 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4774 g_warning("can't allocate memory\n"); \
4775 g_string_free(header, TRUE); \
4778 strcpy(__tmp, str); \
4784 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4786 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4790 len = strlen(str) + 3; \
4791 if ((__tmp = alloca(len)) == NULL) { \
4792 g_warning("can't allocate memory\n"); \
4795 g_snprintf(__tmp, len, "\"%s\"", str); \
4800 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4801 g_warning("can't allocate memory\n"); \
4804 strcpy(__tmp, str); \
4810 static void compose_select_account(Compose *compose, PrefsAccount *account,
4813 gchar *from = NULL, *header = NULL;
4814 ComposeHeaderEntry *header_entry;
4815 #if GTK_CHECK_VERSION(2, 24, 0)
4819 cm_return_if_fail(account != NULL);
4821 compose->account = account;
4822 if (account->name && *account->name) {
4824 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4825 qbuf = escape_internal_quotes(buf, '"');
4826 from = g_strdup_printf("%s <%s>",
4827 qbuf, account->address);
4830 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4832 from = g_strdup_printf("<%s>",
4834 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4839 compose_set_title(compose);
4841 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4842 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4844 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4845 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4846 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4848 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4850 activate_privacy_system(compose, account, FALSE);
4852 if (!init && compose->mode != COMPOSE_REDIRECT) {
4853 undo_block(compose->undostruct);
4854 compose_insert_sig(compose, TRUE);
4855 undo_unblock(compose->undostruct);
4858 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4859 #if !GTK_CHECK_VERSION(2, 24, 0)
4860 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4862 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4863 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4864 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4867 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4868 if (account->protocol == A_NNTP) {
4869 if (!strcmp(header, _("To:")))
4870 combobox_select_by_text(
4871 GTK_COMBO_BOX(header_entry->combo),
4874 if (!strcmp(header, _("Newsgroups:")))
4875 combobox_select_by_text(
4876 GTK_COMBO_BOX(header_entry->combo),
4884 /* use account's dict info if set */
4885 if (compose->gtkaspell) {
4886 if (account->enable_default_dictionary)
4887 gtkaspell_change_dict(compose->gtkaspell,
4888 account->default_dictionary, FALSE);
4889 if (account->enable_default_alt_dictionary)
4890 gtkaspell_change_alt_dict(compose->gtkaspell,
4891 account->default_alt_dictionary);
4892 if (account->enable_default_dictionary
4893 || account->enable_default_alt_dictionary)
4894 compose_spell_menu_changed(compose);
4899 gboolean compose_check_for_valid_recipient(Compose *compose) {
4900 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4901 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4902 gboolean recipient_found = FALSE;
4906 /* free to and newsgroup list */
4907 slist_free_strings_full(compose->to_list);
4908 compose->to_list = NULL;
4910 slist_free_strings_full(compose->newsgroup_list);
4911 compose->newsgroup_list = NULL;
4913 /* search header entries for to and newsgroup entries */
4914 for (list = compose->header_list; list; list = list->next) {
4917 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4918 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4921 if (entry[0] != '\0') {
4922 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4923 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4924 compose->to_list = address_list_append(compose->to_list, entry);
4925 recipient_found = TRUE;
4928 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4929 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4930 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4931 recipient_found = TRUE;
4938 return recipient_found;
4941 static gboolean compose_check_for_set_recipients(Compose *compose)
4943 if (compose->account->set_autocc && compose->account->auto_cc) {
4944 gboolean found_other = FALSE;
4946 /* search header entries for to and newsgroup entries */
4947 for (list = compose->header_list; list; list = list->next) {
4950 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4951 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4954 if (strcmp(entry, compose->account->auto_cc)
4955 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4965 if (compose->batch) {
4966 gtk_widget_show_all(compose->window);
4968 aval = alertpanel(_("Send"),
4969 _("The only recipient is the default CC address. Send anyway?"),
4970 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4971 if (aval != G_ALERTALTERNATE)
4975 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4976 gboolean found_other = FALSE;
4978 /* search header entries for to and newsgroup entries */
4979 for (list = compose->header_list; list; list = list->next) {
4982 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4983 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4986 if (strcmp(entry, compose->account->auto_bcc)
4987 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4997 if (compose->batch) {
4998 gtk_widget_show_all(compose->window);
5000 aval = alertpanel(_("Send"),
5001 _("The only recipient is the default BCC address. Send anyway?"),
5002 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5003 if (aval != G_ALERTALTERNATE)
5010 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5014 if (compose_check_for_valid_recipient(compose) == FALSE) {
5015 if (compose->batch) {
5016 gtk_widget_show_all(compose->window);
5018 alertpanel_error(_("Recipient is not specified."));
5022 if (compose_check_for_set_recipients(compose) == FALSE) {
5026 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5027 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5028 if (*str == '\0' && check_everything == TRUE &&
5029 compose->mode != COMPOSE_REDIRECT) {
5031 gchar *button_label;
5034 if (compose->sending)
5035 button_label = _("+_Send");
5037 button_label = _("+_Queue");
5038 message = g_strdup_printf(_("Subject is empty. %s"),
5039 compose->sending?_("Send it anyway?"):
5040 _("Queue it anyway?"));
5042 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5043 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5044 ALERT_QUESTION, G_ALERTDEFAULT);
5046 if (aval & G_ALERTDISABLE) {
5047 aval &= ~G_ALERTDISABLE;
5048 prefs_common.warn_empty_subj = FALSE;
5050 if (aval != G_ALERTALTERNATE)
5055 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5061 gint compose_send(Compose *compose)
5064 FolderItem *folder = NULL;
5066 gchar *msgpath = NULL;
5067 gboolean discard_window = FALSE;
5068 gchar *errstr = NULL;
5069 gchar *tmsgid = NULL;
5070 MainWindow *mainwin = mainwindow_get_mainwindow();
5071 gboolean queued_removed = FALSE;
5073 if (prefs_common.send_dialog_invisible
5074 || compose->batch == TRUE)
5075 discard_window = TRUE;
5077 compose_allow_user_actions (compose, FALSE);
5078 compose->sending = TRUE;
5080 if (compose_check_entries(compose, TRUE) == FALSE) {
5081 if (compose->batch) {
5082 gtk_widget_show_all(compose->window);
5088 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5091 if (compose->batch) {
5092 gtk_widget_show_all(compose->window);
5095 alertpanel_error(_("Could not queue message for sending:\n\n"
5096 "Charset conversion failed."));
5097 } else if (val == -5) {
5098 alertpanel_error(_("Could not queue message for sending:\n\n"
5099 "Couldn't get recipient encryption key."));
5100 } else if (val == -6) {
5102 } else if (val == -3) {
5103 if (privacy_peek_error())
5104 alertpanel_error(_("Could not queue message for sending:\n\n"
5105 "Signature failed: %s"), privacy_get_error());
5106 } else if (val == -2 && errno != 0) {
5107 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5109 alertpanel_error(_("Could not queue message for sending."));
5114 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5115 if (discard_window) {
5116 compose->sending = FALSE;
5117 compose_close(compose);
5118 /* No more compose access in the normal codepath
5119 * after this point! */
5124 alertpanel_error(_("The message was queued but could not be "
5125 "sent.\nUse \"Send queued messages\" from "
5126 "the main window to retry."));
5127 if (!discard_window) {
5134 if (msgpath == NULL) {
5135 msgpath = folder_item_fetch_msg(folder, msgnum);
5136 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5139 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5140 claws_unlink(msgpath);
5143 if (!discard_window) {
5145 if (!queued_removed)
5146 folder_item_remove_msg(folder, msgnum);
5147 folder_item_scan(folder);
5149 /* make sure we delete that */
5150 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5152 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5153 folder_item_remove_msg(folder, tmp->msgnum);
5154 procmsg_msginfo_free(tmp);
5161 if (!queued_removed)
5162 folder_item_remove_msg(folder, msgnum);
5163 folder_item_scan(folder);
5165 /* make sure we delete that */
5166 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5168 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5169 folder_item_remove_msg(folder, tmp->msgnum);
5170 procmsg_msginfo_free(tmp);
5173 if (!discard_window) {
5174 compose->sending = FALSE;
5175 compose_allow_user_actions (compose, TRUE);
5176 compose_close(compose);
5180 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5181 "the main window to retry."), errstr);
5184 alertpanel_error_log(_("The message was queued but could not be "
5185 "sent.\nUse \"Send queued messages\" from "
5186 "the main window to retry."));
5188 if (!discard_window) {
5197 toolbar_main_set_sensitive(mainwin);
5198 main_window_set_menu_sensitive(mainwin);
5204 compose_allow_user_actions (compose, TRUE);
5205 compose->sending = FALSE;
5206 compose->modified = TRUE;
5207 toolbar_main_set_sensitive(mainwin);
5208 main_window_set_menu_sensitive(mainwin);
5213 static gboolean compose_use_attach(Compose *compose)
5215 GtkTreeModel *model = gtk_tree_view_get_model
5216 (GTK_TREE_VIEW(compose->attach_clist));
5217 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5220 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5223 gchar buf[BUFFSIZE];
5225 gboolean first_to_address;
5226 gboolean first_cc_address;
5228 ComposeHeaderEntry *headerentry;
5229 const gchar *headerentryname;
5230 const gchar *cc_hdr;
5231 const gchar *to_hdr;
5232 gboolean err = FALSE;
5234 debug_print("Writing redirect header\n");
5236 cc_hdr = prefs_common_translated_header_name("Cc:");
5237 to_hdr = prefs_common_translated_header_name("To:");
5239 first_to_address = TRUE;
5240 for (list = compose->header_list; list; list = list->next) {
5241 headerentry = ((ComposeHeaderEntry *)list->data);
5242 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5244 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5245 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5246 Xstrdup_a(str, entstr, return -1);
5248 if (str[0] != '\0') {
5249 compose_convert_header
5250 (compose, buf, sizeof(buf), str,
5251 strlen("Resent-To") + 2, TRUE);
5253 if (first_to_address) {
5254 err |= (fprintf(fp, "Resent-To: ") < 0);
5255 first_to_address = FALSE;
5257 err |= (fprintf(fp, ",") < 0);
5259 err |= (fprintf(fp, "%s", buf) < 0);
5263 if (!first_to_address) {
5264 err |= (fprintf(fp, "\n") < 0);
5267 first_cc_address = TRUE;
5268 for (list = compose->header_list; list; list = list->next) {
5269 headerentry = ((ComposeHeaderEntry *)list->data);
5270 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5272 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5273 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5274 Xstrdup_a(str, strg, return -1);
5276 if (str[0] != '\0') {
5277 compose_convert_header
5278 (compose, buf, sizeof(buf), str,
5279 strlen("Resent-Cc") + 2, TRUE);
5281 if (first_cc_address) {
5282 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5283 first_cc_address = FALSE;
5285 err |= (fprintf(fp, ",") < 0);
5287 err |= (fprintf(fp, "%s", buf) < 0);
5291 if (!first_cc_address) {
5292 err |= (fprintf(fp, "\n") < 0);
5295 return (err ? -1:0);
5298 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5300 gchar buf[BUFFSIZE];
5302 const gchar *entstr;
5303 /* struct utsname utsbuf; */
5304 gboolean err = FALSE;
5306 cm_return_val_if_fail(fp != NULL, -1);
5307 cm_return_val_if_fail(compose->account != NULL, -1);
5308 cm_return_val_if_fail(compose->account->address != NULL, -1);
5311 get_rfc822_date(buf, sizeof(buf));
5312 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5315 if (compose->account->name && *compose->account->name) {
5316 compose_convert_header
5317 (compose, buf, sizeof(buf), compose->account->name,
5318 strlen("From: "), TRUE);
5319 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5320 buf, compose->account->address) < 0);
5322 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5325 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5326 if (*entstr != '\0') {
5327 Xstrdup_a(str, entstr, return -1);
5330 compose_convert_header(compose, buf, sizeof(buf), str,
5331 strlen("Subject: "), FALSE);
5332 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5336 /* Resent-Message-ID */
5337 if (compose->account->set_domain && compose->account->domain) {
5338 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5339 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5340 g_snprintf(buf, sizeof(buf), "%s",
5341 strchr(compose->account->address, '@') ?
5342 strchr(compose->account->address, '@')+1 :
5343 compose->account->address);
5345 g_snprintf(buf, sizeof(buf), "%s", "");
5348 if (compose->account->gen_msgid) {
5350 if (compose->account->msgid_with_addr) {
5351 addr = compose->account->address;
5353 generate_msgid(buf, sizeof(buf), addr);
5354 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5356 g_free(compose->msgid);
5357 compose->msgid = g_strdup(buf);
5359 compose->msgid = NULL;
5362 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5365 /* separator between header and body */
5366 err |= (fputs("\n", fp) == EOF);
5368 return (err ? -1:0);
5371 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5375 gchar buf[BUFFSIZE];
5377 gboolean skip = FALSE;
5378 gboolean err = FALSE;
5379 gchar *not_included[]={
5380 "Return-Path:", "Delivered-To:", "Received:",
5381 "Subject:", "X-UIDL:", "AF:",
5382 "NF:", "PS:", "SRH:",
5383 "SFN:", "DSR:", "MID:",
5384 "CFG:", "PT:", "S:",
5385 "RQ:", "SSV:", "NSV:",
5386 "SSH:", "R:", "MAID:",
5387 "NAID:", "RMID:", "FMID:",
5388 "SCF:", "RRCPT:", "NG:",
5389 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5390 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5391 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5392 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5393 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5396 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5397 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5401 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5403 for (i = 0; not_included[i] != NULL; i++) {
5404 if (g_ascii_strncasecmp(buf, not_included[i],
5405 strlen(not_included[i])) == 0) {
5412 if (fputs(buf, fdest) == -1)
5415 if (!prefs_common.redirect_keep_from) {
5416 if (g_ascii_strncasecmp(buf, "From:",
5417 strlen("From:")) == 0) {
5418 err |= (fputs(" (by way of ", fdest) == EOF);
5419 if (compose->account->name
5420 && *compose->account->name) {
5421 compose_convert_header
5422 (compose, buf, sizeof(buf),
5423 compose->account->name,
5426 err |= (fprintf(fdest, "%s <%s>",
5428 compose->account->address) < 0);
5430 err |= (fprintf(fdest, "%s",
5431 compose->account->address) < 0);
5432 err |= (fputs(")", fdest) == EOF);
5436 if (fputs("\n", fdest) == -1)
5443 if (compose_redirect_write_headers(compose, fdest))
5446 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5447 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5460 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5462 GtkTextBuffer *buffer;
5463 GtkTextIter start, end;
5466 const gchar *out_codeset;
5467 EncodingType encoding = ENC_UNKNOWN;
5468 MimeInfo *mimemsg, *mimetext;
5470 const gchar *src_codeset = CS_INTERNAL;
5471 gchar *from_addr = NULL;
5472 gchar *from_name = NULL;
5474 if (action == COMPOSE_WRITE_FOR_SEND)
5475 attach_parts = TRUE;
5477 /* create message MimeInfo */
5478 mimemsg = procmime_mimeinfo_new();
5479 mimemsg->type = MIMETYPE_MESSAGE;
5480 mimemsg->subtype = g_strdup("rfc822");
5481 mimemsg->content = MIMECONTENT_MEM;
5482 mimemsg->tmp = TRUE; /* must free content later */
5483 mimemsg->data.mem = compose_get_header(compose);
5485 /* Create text part MimeInfo */
5486 /* get all composed text */
5487 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5488 gtk_text_buffer_get_start_iter(buffer, &start);
5489 gtk_text_buffer_get_end_iter(buffer, &end);
5490 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5492 out_codeset = conv_get_charset_str(compose->out_encoding);
5494 if (!out_codeset && is_ascii_str(chars)) {
5495 out_codeset = CS_US_ASCII;
5496 } else if (prefs_common.outgoing_fallback_to_ascii &&
5497 is_ascii_str(chars)) {
5498 out_codeset = CS_US_ASCII;
5499 encoding = ENC_7BIT;
5503 gchar *test_conv_global_out = NULL;
5504 gchar *test_conv_reply = NULL;
5506 /* automatic mode. be automatic. */
5507 codeconv_set_strict(TRUE);
5509 out_codeset = conv_get_outgoing_charset_str();
5511 debug_print("trying to convert to %s\n", out_codeset);
5512 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5515 if (!test_conv_global_out && compose->orig_charset
5516 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5517 out_codeset = compose->orig_charset;
5518 debug_print("failure; trying to convert to %s\n", out_codeset);
5519 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5522 if (!test_conv_global_out && !test_conv_reply) {
5524 out_codeset = CS_INTERNAL;
5525 debug_print("failure; finally using %s\n", out_codeset);
5527 g_free(test_conv_global_out);
5528 g_free(test_conv_reply);
5529 codeconv_set_strict(FALSE);
5532 if (encoding == ENC_UNKNOWN) {
5533 if (prefs_common.encoding_method == CTE_BASE64)
5534 encoding = ENC_BASE64;
5535 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5536 encoding = ENC_QUOTED_PRINTABLE;
5537 else if (prefs_common.encoding_method == CTE_8BIT)
5538 encoding = ENC_8BIT;
5540 encoding = procmime_get_encoding_for_charset(out_codeset);
5543 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5544 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5546 if (action == COMPOSE_WRITE_FOR_SEND) {
5547 codeconv_set_strict(TRUE);
5548 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5549 codeconv_set_strict(FALSE);
5555 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5556 "to the specified %s charset.\n"
5557 "Send it as %s?"), out_codeset, src_codeset);
5558 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5559 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5562 if (aval != G_ALERTALTERNATE) {
5567 out_codeset = src_codeset;
5573 out_codeset = src_codeset;
5578 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5579 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5580 strstr(buf, "\nFrom ") != NULL) {
5581 encoding = ENC_QUOTED_PRINTABLE;
5585 mimetext = procmime_mimeinfo_new();
5586 mimetext->content = MIMECONTENT_MEM;
5587 mimetext->tmp = TRUE; /* must free content later */
5588 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5589 * and free the data, which we need later. */
5590 mimetext->data.mem = g_strdup(buf);
5591 mimetext->type = MIMETYPE_TEXT;
5592 mimetext->subtype = g_strdup("plain");
5593 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5594 g_strdup(out_codeset));
5596 /* protect trailing spaces when signing message */
5597 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5598 privacy_system_can_sign(compose->privacy_system)) {
5599 encoding = ENC_QUOTED_PRINTABLE;
5602 debug_print("main text: %zd bytes encoded as %s in %d\n",
5603 strlen(buf), out_codeset, encoding);
5605 /* check for line length limit */
5606 if (action == COMPOSE_WRITE_FOR_SEND &&
5607 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5608 check_line_length(buf, 1000, &line) < 0) {
5612 msg = g_strdup_printf
5613 (_("Line %d exceeds the line length limit (998 bytes).\n"
5614 "The contents of the message might be broken on the way to the delivery.\n"
5616 "Send it anyway?"), line + 1);
5617 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5619 if (aval != G_ALERTALTERNATE) {
5625 if (encoding != ENC_UNKNOWN)
5626 procmime_encode_content(mimetext, encoding);
5628 /* append attachment parts */
5629 if (compose_use_attach(compose) && attach_parts) {
5630 MimeInfo *mimempart;
5631 gchar *boundary = NULL;
5632 mimempart = procmime_mimeinfo_new();
5633 mimempart->content = MIMECONTENT_EMPTY;
5634 mimempart->type = MIMETYPE_MULTIPART;
5635 mimempart->subtype = g_strdup("mixed");
5639 boundary = generate_mime_boundary(NULL);
5640 } while (strstr(buf, boundary) != NULL);
5642 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5645 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5647 g_node_append(mimempart->node, mimetext->node);
5648 g_node_append(mimemsg->node, mimempart->node);
5650 if (compose_add_attachments(compose, mimempart) < 0)
5653 g_node_append(mimemsg->node, mimetext->node);
5657 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5658 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5659 /* extract name and address */
5660 if (strstr(spec, " <") && strstr(spec, ">")) {
5661 from_addr = g_strdup(strrchr(spec, '<')+1);
5662 *(strrchr(from_addr, '>')) = '\0';
5663 from_name = g_strdup(spec);
5664 *(strrchr(from_name, '<')) = '\0';
5671 /* sign message if sending */
5672 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5673 privacy_system_can_sign(compose->privacy_system))
5674 if (!privacy_sign(compose->privacy_system, mimemsg,
5675 compose->account, from_addr)) {
5682 procmime_write_mimeinfo(mimemsg, fp);
5684 procmime_mimeinfo_free_all(mimemsg);
5689 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5691 GtkTextBuffer *buffer;
5692 GtkTextIter start, end;
5697 if ((fp = g_fopen(file, "wb")) == NULL) {
5698 FILE_OP_ERROR(file, "fopen");
5702 /* chmod for security */
5703 if (change_file_mode_rw(fp, file) < 0) {
5704 FILE_OP_ERROR(file, "chmod");
5705 g_warning("can't change file mode\n");
5708 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5709 gtk_text_buffer_get_start_iter(buffer, &start);
5710 gtk_text_buffer_get_end_iter(buffer, &end);
5711 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5713 chars = conv_codeset_strdup
5714 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5717 if (!chars) return -1;
5720 len = strlen(chars);
5721 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5722 FILE_OP_ERROR(file, "fwrite");
5731 if (fclose(fp) == EOF) {
5732 FILE_OP_ERROR(file, "fclose");
5739 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5742 MsgInfo *msginfo = compose->targetinfo;
5744 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5745 if (!msginfo) return -1;
5747 if (!force && MSG_IS_LOCKED(msginfo->flags))
5750 item = msginfo->folder;
5751 cm_return_val_if_fail(item != NULL, -1);
5753 if (procmsg_msg_exist(msginfo) &&
5754 (folder_has_parent_of_type(item, F_QUEUE) ||
5755 folder_has_parent_of_type(item, F_DRAFT)
5756 || msginfo == compose->autosaved_draft)) {
5757 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5758 g_warning("can't remove the old message\n");
5761 debug_print("removed reedit target %d\n", msginfo->msgnum);
5768 static void compose_remove_draft(Compose *compose)
5771 MsgInfo *msginfo = compose->targetinfo;
5772 drafts = account_get_special_folder(compose->account, F_DRAFT);
5774 if (procmsg_msg_exist(msginfo)) {
5775 folder_item_remove_msg(drafts, msginfo->msgnum);
5780 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5781 gboolean remove_reedit_target)
5783 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5786 static gboolean compose_warn_encryption(Compose *compose)
5788 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5789 AlertValue val = G_ALERTALTERNATE;
5791 if (warning == NULL)
5794 val = alertpanel_full(_("Encryption warning"), warning,
5795 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5796 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5797 if (val & G_ALERTDISABLE) {
5798 val &= ~G_ALERTDISABLE;
5799 if (val == G_ALERTALTERNATE)
5800 privacy_inhibit_encrypt_warning(compose->privacy_system,
5804 if (val == G_ALERTALTERNATE) {
5811 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5812 gchar **msgpath, gboolean check_subject,
5813 gboolean remove_reedit_target)
5820 PrefsAccount *mailac = NULL, *newsac = NULL;
5821 gboolean err = FALSE;
5823 debug_print("queueing message...\n");
5824 cm_return_val_if_fail(compose->account != NULL, -1);
5826 if (compose_check_entries(compose, check_subject) == FALSE) {
5827 if (compose->batch) {
5828 gtk_widget_show_all(compose->window);
5833 if (!compose->to_list && !compose->newsgroup_list) {
5834 g_warning("can't get recipient list.");
5838 if (compose->to_list) {
5839 if (compose->account->protocol != A_NNTP)
5840 mailac = compose->account;
5841 else if (cur_account && cur_account->protocol != A_NNTP)
5842 mailac = cur_account;
5843 else if (!(mailac = compose_current_mail_account())) {
5844 alertpanel_error(_("No account for sending mails available!"));
5849 if (compose->newsgroup_list) {
5850 if (compose->account->protocol == A_NNTP)
5851 newsac = compose->account;
5853 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5858 /* write queue header */
5859 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5860 G_DIR_SEPARATOR, compose, (guint) rand());
5861 debug_print("queuing to %s\n", tmp);
5862 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5863 FILE_OP_ERROR(tmp, "fopen");
5868 if (change_file_mode_rw(fp, tmp) < 0) {
5869 FILE_OP_ERROR(tmp, "chmod");
5870 g_warning("can't change file mode\n");
5873 /* queueing variables */
5874 err |= (fprintf(fp, "AF:\n") < 0);
5875 err |= (fprintf(fp, "NF:0\n") < 0);
5876 err |= (fprintf(fp, "PS:10\n") < 0);
5877 err |= (fprintf(fp, "SRH:1\n") < 0);
5878 err |= (fprintf(fp, "SFN:\n") < 0);
5879 err |= (fprintf(fp, "DSR:\n") < 0);
5881 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5883 err |= (fprintf(fp, "MID:\n") < 0);
5884 err |= (fprintf(fp, "CFG:\n") < 0);
5885 err |= (fprintf(fp, "PT:0\n") < 0);
5886 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5887 err |= (fprintf(fp, "RQ:\n") < 0);
5889 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5891 err |= (fprintf(fp, "SSV:\n") < 0);
5893 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5895 err |= (fprintf(fp, "NSV:\n") < 0);
5896 err |= (fprintf(fp, "SSH:\n") < 0);
5897 /* write recepient list */
5898 if (compose->to_list) {
5899 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5900 for (cur = compose->to_list->next; cur != NULL;
5902 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5903 err |= (fprintf(fp, "\n") < 0);
5905 /* write newsgroup list */
5906 if (compose->newsgroup_list) {
5907 err |= (fprintf(fp, "NG:") < 0);
5908 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5909 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5910 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5911 err |= (fprintf(fp, "\n") < 0);
5913 /* Sylpheed account IDs */
5915 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5917 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5920 if (compose->privacy_system != NULL) {
5921 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5922 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5923 if (compose->use_encryption) {
5925 if (!compose_warn_encryption(compose)) {
5931 if (mailac && mailac->encrypt_to_self) {
5932 GSList *tmp_list = g_slist_copy(compose->to_list);
5933 tmp_list = g_slist_append(tmp_list, compose->account->address);
5934 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5935 g_slist_free(tmp_list);
5937 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5939 if (encdata != NULL) {
5940 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5941 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5942 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5944 } /* else we finally dont want to encrypt */
5946 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5947 /* and if encdata was null, it means there's been a problem in
5950 g_warning("failed to write queue message");
5960 /* Save copy folder */
5961 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5962 gchar *savefolderid;
5964 savefolderid = compose_get_save_to(compose);
5965 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5966 g_free(savefolderid);
5968 /* Save copy folder */
5969 if (compose->return_receipt) {
5970 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5972 /* Message-ID of message replying to */
5973 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5976 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5977 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5980 /* Message-ID of message forwarding to */
5981 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5984 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5985 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5989 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5990 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5992 /* end of headers */
5993 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5995 if (compose->redirect_filename != NULL) {
5996 if (compose_redirect_write_to_file(compose, fp) < 0) {
6004 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6008 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6012 g_warning("failed to write queue message\n");
6018 if (fclose(fp) == EOF) {
6019 FILE_OP_ERROR(tmp, "fclose");
6025 if (item && *item) {
6028 queue = account_get_special_folder(compose->account, F_QUEUE);
6031 g_warning("can't find queue folder\n");
6036 folder_item_scan(queue);
6037 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6038 g_warning("can't queue the message\n");
6044 if (msgpath == NULL) {
6050 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6051 compose_remove_reedit_target(compose, FALSE);
6054 if ((msgnum != NULL) && (item != NULL)) {
6062 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6065 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6067 struct stat statbuf;
6068 gchar *type, *subtype;
6069 GtkTreeModel *model;
6072 model = gtk_tree_view_get_model(tree_view);
6074 if (!gtk_tree_model_get_iter_first(model, &iter))
6077 gtk_tree_model_get(model, &iter,
6081 if (!is_file_exist(ainfo->file)) {
6082 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6083 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6084 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6086 if (val == G_ALERTDEFAULT) {
6091 mimepart = procmime_mimeinfo_new();
6092 mimepart->content = MIMECONTENT_FILE;
6093 mimepart->data.filename = g_strdup(ainfo->file);
6094 mimepart->tmp = FALSE; /* or we destroy our attachment */
6095 mimepart->offset = 0;
6097 g_stat(ainfo->file, &statbuf);
6098 mimepart->length = statbuf.st_size;
6100 type = g_strdup(ainfo->content_type);
6102 if (!strchr(type, '/')) {
6104 type = g_strdup("application/octet-stream");
6107 subtype = strchr(type, '/') + 1;
6108 *(subtype - 1) = '\0';
6109 mimepart->type = procmime_get_media_type(type);
6110 mimepart->subtype = g_strdup(subtype);
6113 if (mimepart->type == MIMETYPE_MESSAGE &&
6114 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6115 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6116 } else if (mimepart->type == MIMETYPE_TEXT) {
6117 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6118 /* Text parts with no name come from multipart/alternative
6119 * forwards. Make sure the recipient won't look at the
6120 * original HTML part by mistake. */
6121 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6122 ainfo->name = g_strdup_printf(_("Original %s part"),
6126 g_hash_table_insert(mimepart->typeparameters,
6127 g_strdup("charset"), g_strdup(ainfo->charset));
6129 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6130 if (mimepart->type == MIMETYPE_APPLICATION &&
6131 !strcmp2(mimepart->subtype, "octet-stream"))
6132 g_hash_table_insert(mimepart->typeparameters,
6133 g_strdup("name"), g_strdup(ainfo->name));
6134 g_hash_table_insert(mimepart->dispositionparameters,
6135 g_strdup("filename"), g_strdup(ainfo->name));
6136 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6139 if (mimepart->type == MIMETYPE_MESSAGE
6140 || mimepart->type == MIMETYPE_MULTIPART)
6141 ainfo->encoding = ENC_BINARY;
6142 else if (compose->use_signing) {
6143 if (ainfo->encoding == ENC_7BIT)
6144 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6145 else if (ainfo->encoding == ENC_8BIT)
6146 ainfo->encoding = ENC_BASE64;
6151 procmime_encode_content(mimepart, ainfo->encoding);
6153 g_node_append(parent->node, mimepart->node);
6154 } while (gtk_tree_model_iter_next(model, &iter));
6159 static gchar *compose_quote_list_of_addresses(gchar *str)
6161 GSList *list = NULL, *item = NULL;
6162 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6164 list = address_list_append_with_comments(list, str);
6165 for (item = list; item != NULL; item = item->next) {
6166 gchar *spec = item->data;
6167 gchar *endofname = strstr(spec, " <");
6168 if (endofname != NULL) {
6171 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6172 qqname = escape_internal_quotes(qname, '"');
6174 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6175 gchar *addr = g_strdup(endofname);
6176 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6177 faddr = g_strconcat(name, addr, NULL);
6180 debug_print("new auto-quoted address: '%s'", faddr);
6184 result = g_strdup((faddr != NULL)? faddr: spec);
6186 result = g_strconcat(result,
6188 (faddr != NULL)? faddr: spec,
6191 if (faddr != NULL) {
6196 slist_free_strings_full(list);
6201 #define IS_IN_CUSTOM_HEADER(header) \
6202 (compose->account->add_customhdr && \
6203 custom_header_find(compose->account->customhdr_list, header) != NULL)
6205 static void compose_add_headerfield_from_headerlist(Compose *compose,
6207 const gchar *fieldname,
6208 const gchar *seperator)
6210 gchar *str, *fieldname_w_colon;
6211 gboolean add_field = FALSE;
6213 ComposeHeaderEntry *headerentry;
6214 const gchar *headerentryname;
6215 const gchar *trans_fieldname;
6218 if (IS_IN_CUSTOM_HEADER(fieldname))
6221 debug_print("Adding %s-fields\n", fieldname);
6223 fieldstr = g_string_sized_new(64);
6225 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6226 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6228 for (list = compose->header_list; list; list = list->next) {
6229 headerentry = ((ComposeHeaderEntry *)list->data);
6230 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6232 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6233 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6235 str = compose_quote_list_of_addresses(ustr);
6237 if (str != NULL && str[0] != '\0') {
6239 g_string_append(fieldstr, seperator);
6240 g_string_append(fieldstr, str);
6249 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6250 compose_convert_header
6251 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6252 strlen(fieldname) + 2, TRUE);
6253 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6257 g_free(fieldname_w_colon);
6258 g_string_free(fieldstr, TRUE);
6263 static gchar *compose_get_manual_headers_info(Compose *compose)
6265 GString *sh_header = g_string_new(" ");
6267 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6269 for (list = compose->header_list; list; list = list->next) {
6270 ComposeHeaderEntry *headerentry;
6273 gchar *headername_wcolon;
6274 const gchar *headername_trans;
6276 gboolean standard_header = FALSE;
6278 headerentry = ((ComposeHeaderEntry *)list->data);
6280 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6282 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6287 if (!strstr(tmp, ":")) {
6288 headername_wcolon = g_strconcat(tmp, ":", NULL);
6289 headername = g_strdup(tmp);
6291 headername_wcolon = g_strdup(tmp);
6292 headername = g_strdup(strtok(tmp, ":"));
6296 string = std_headers;
6297 while (*string != NULL) {
6298 headername_trans = prefs_common_translated_header_name(*string);
6299 if (!strcmp(headername_trans, headername_wcolon))
6300 standard_header = TRUE;
6303 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6304 g_string_append_printf(sh_header, "%s ", headername);
6306 g_free(headername_wcolon);
6308 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6309 return g_string_free(sh_header, FALSE);
6312 static gchar *compose_get_header(Compose *compose)
6314 gchar buf[BUFFSIZE];
6315 const gchar *entry_str;
6319 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6321 gchar *from_name = NULL, *from_address = NULL;
6324 cm_return_val_if_fail(compose->account != NULL, NULL);
6325 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6327 header = g_string_sized_new(64);
6330 get_rfc822_date(buf, sizeof(buf));
6331 g_string_append_printf(header, "Date: %s\n", buf);
6335 if (compose->account->name && *compose->account->name) {
6337 QUOTE_IF_REQUIRED(buf, compose->account->name);
6338 tmp = g_strdup_printf("%s <%s>",
6339 buf, compose->account->address);
6341 tmp = g_strdup_printf("%s",
6342 compose->account->address);
6344 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6345 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6347 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6348 from_address = g_strdup(compose->account->address);
6350 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6351 /* extract name and address */
6352 if (strstr(spec, " <") && strstr(spec, ">")) {
6353 from_address = g_strdup(strrchr(spec, '<')+1);
6354 *(strrchr(from_address, '>')) = '\0';
6355 from_name = g_strdup(spec);
6356 *(strrchr(from_name, '<')) = '\0';
6359 from_address = g_strdup(spec);
6366 if (from_name && *from_name) {
6368 compose_convert_header
6369 (compose, buf, sizeof(buf), from_name,
6370 strlen("From: "), TRUE);
6371 QUOTE_IF_REQUIRED(name, buf);
6372 qname = escape_internal_quotes(name, '"');
6374 g_string_append_printf(header, "From: %s <%s>\n",
6375 qname, from_address);
6379 g_string_append_printf(header, "From: %s\n", from_address);
6382 g_free(from_address);
6385 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6388 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6391 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6395 * If this account is a NNTP account remove Bcc header from
6396 * message body since it otherwise will be publicly shown
6398 if (compose->account->protocol != A_NNTP)
6399 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6402 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6404 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6407 compose_convert_header(compose, buf, sizeof(buf), str,
6408 strlen("Subject: "), FALSE);
6409 g_string_append_printf(header, "Subject: %s\n", buf);
6415 if (compose->account->set_domain && compose->account->domain) {
6416 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6417 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6418 g_snprintf(buf, sizeof(buf), "%s",
6419 strchr(compose->account->address, '@') ?
6420 strchr(compose->account->address, '@')+1 :
6421 compose->account->address);
6423 g_snprintf(buf, sizeof(buf), "%s", "");
6426 if (compose->account->gen_msgid) {
6428 if (compose->account->msgid_with_addr) {
6429 addr = compose->account->address;
6431 generate_msgid(buf, sizeof(buf), addr);
6432 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6434 g_free(compose->msgid);
6435 compose->msgid = g_strdup(buf);
6437 compose->msgid = NULL;
6440 if (compose->remove_references == FALSE) {
6442 if (compose->inreplyto && compose->to_list)
6443 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6446 if (compose->references)
6447 g_string_append_printf(header, "References: %s\n", compose->references);
6451 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6454 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6457 if (compose->account->organization &&
6458 strlen(compose->account->organization) &&
6459 !IS_IN_CUSTOM_HEADER("Organization")) {
6460 compose_convert_header(compose, buf, sizeof(buf),
6461 compose->account->organization,
6462 strlen("Organization: "), FALSE);
6463 g_string_append_printf(header, "Organization: %s\n", buf);
6466 /* Program version and system info */
6467 if (compose->account->gen_xmailer &&
6468 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6469 !compose->newsgroup_list) {
6470 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6472 gtk_major_version, gtk_minor_version, gtk_micro_version,
6475 if (compose->account->gen_xmailer &&
6476 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6477 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6479 gtk_major_version, gtk_minor_version, gtk_micro_version,
6483 /* custom headers */
6484 if (compose->account->add_customhdr) {
6487 for (cur = compose->account->customhdr_list; cur != NULL;
6489 CustomHeader *chdr = (CustomHeader *)cur->data;
6491 if (custom_header_is_allowed(chdr->name)
6492 && chdr->value != NULL
6493 && *(chdr->value) != '\0') {
6494 compose_convert_header
6495 (compose, buf, sizeof(buf),
6497 strlen(chdr->name) + 2, FALSE);
6498 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6503 /* Automatic Faces and X-Faces */
6504 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6505 g_string_append_printf(header, "X-Face: %s\n", buf);
6507 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6508 g_string_append_printf(header, "X-Face: %s\n", buf);
6510 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6511 g_string_append_printf(header, "Face: %s\n", buf);
6513 else if (get_default_face (buf, sizeof(buf)) == 0) {
6514 g_string_append_printf(header, "Face: %s\n", buf);
6518 switch (compose->priority) {
6519 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6520 "X-Priority: 1 (Highest)\n");
6522 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6523 "X-Priority: 2 (High)\n");
6525 case PRIORITY_NORMAL: break;
6526 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6527 "X-Priority: 4 (Low)\n");
6529 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6530 "X-Priority: 5 (Lowest)\n");
6532 default: debug_print("compose: priority unknown : %d\n",
6536 /* Request Return Receipt */
6537 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6538 if (compose->return_receipt) {
6539 if (compose->account->name
6540 && *compose->account->name) {
6541 compose_convert_header(compose, buf, sizeof(buf),
6542 compose->account->name,
6543 strlen("Disposition-Notification-To: "),
6545 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6547 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6551 /* get special headers */
6552 for (list = compose->header_list; list; list = list->next) {
6553 ComposeHeaderEntry *headerentry;
6556 gchar *headername_wcolon;
6557 const gchar *headername_trans;
6560 gboolean standard_header = FALSE;
6562 headerentry = ((ComposeHeaderEntry *)list->data);
6564 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6566 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6571 if (!strstr(tmp, ":")) {
6572 headername_wcolon = g_strconcat(tmp, ":", NULL);
6573 headername = g_strdup(tmp);
6575 headername_wcolon = g_strdup(tmp);
6576 headername = g_strdup(strtok(tmp, ":"));
6580 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6581 Xstrdup_a(headervalue, entry_str, return NULL);
6582 subst_char(headervalue, '\r', ' ');
6583 subst_char(headervalue, '\n', ' ');
6584 string = std_headers;
6585 while (*string != NULL) {
6586 headername_trans = prefs_common_translated_header_name(*string);
6587 if (!strcmp(headername_trans, headername_wcolon))
6588 standard_header = TRUE;
6591 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6592 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6595 g_free(headername_wcolon);
6599 g_string_free(header, FALSE);
6604 #undef IS_IN_CUSTOM_HEADER
6606 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6607 gint header_len, gboolean addr_field)
6609 gchar *tmpstr = NULL;
6610 const gchar *out_codeset = NULL;
6612 cm_return_if_fail(src != NULL);
6613 cm_return_if_fail(dest != NULL);
6615 if (len < 1) return;
6617 tmpstr = g_strdup(src);
6619 subst_char(tmpstr, '\n', ' ');
6620 subst_char(tmpstr, '\r', ' ');
6623 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6624 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6625 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6630 codeconv_set_strict(TRUE);
6631 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6632 conv_get_charset_str(compose->out_encoding));
6633 codeconv_set_strict(FALSE);
6635 if (!dest || *dest == '\0') {
6636 gchar *test_conv_global_out = NULL;
6637 gchar *test_conv_reply = NULL;
6639 /* automatic mode. be automatic. */
6640 codeconv_set_strict(TRUE);
6642 out_codeset = conv_get_outgoing_charset_str();
6644 debug_print("trying to convert to %s\n", out_codeset);
6645 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6648 if (!test_conv_global_out && compose->orig_charset
6649 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6650 out_codeset = compose->orig_charset;
6651 debug_print("failure; trying to convert to %s\n", out_codeset);
6652 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6655 if (!test_conv_global_out && !test_conv_reply) {
6657 out_codeset = CS_INTERNAL;
6658 debug_print("finally using %s\n", out_codeset);
6660 g_free(test_conv_global_out);
6661 g_free(test_conv_reply);
6662 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6664 codeconv_set_strict(FALSE);
6669 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6673 cm_return_if_fail(user_data != NULL);
6675 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6676 g_strstrip(address);
6677 if (*address != '\0') {
6678 gchar *name = procheader_get_fromname(address);
6679 extract_address(address);
6680 #ifndef USE_NEW_ADDRBOOK
6681 addressbook_add_contact(name, address, NULL, NULL);
6683 debug_print("%s: %s\n", name, address);
6684 if (addressadd_selection(name, address, NULL, NULL)) {
6685 debug_print( "addressbook_add_contact - added\n" );
6692 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6694 GtkWidget *menuitem;
6697 cm_return_if_fail(menu != NULL);
6698 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6700 menuitem = gtk_separator_menu_item_new();
6701 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6702 gtk_widget_show(menuitem);
6704 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6705 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6707 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6708 g_strstrip(address);
6709 if (*address == '\0') {
6710 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6713 g_signal_connect(G_OBJECT(menuitem), "activate",
6714 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6715 gtk_widget_show(menuitem);
6718 void compose_add_extra_header(gchar *header, GtkListStore *model)
6721 if (strcmp(header, "")) {
6722 COMBOBOX_ADD(model, header, COMPOSE_TO);
6726 void compose_add_extra_header_entries(GtkListStore *model)
6730 gchar buf[BUFFSIZE];
6733 if (extra_headers == NULL) {
6734 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6735 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6736 debug_print("extra headers file not found\n");
6737 goto extra_headers_done;
6739 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6740 lastc = strlen(buf) - 1; /* remove trailing control chars */
6741 while (lastc >= 0 && buf[lastc] != ':')
6742 buf[lastc--] = '\0';
6743 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6744 buf[lastc] = '\0'; /* remove trailing : for comparison */
6745 if (custom_header_is_allowed(buf)) {
6747 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6750 g_message("disallowed extra header line: %s\n", buf);
6754 g_message("invalid extra header line: %s\n", buf);
6760 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6761 extra_headers = g_slist_reverse(extra_headers);
6763 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6766 static void compose_create_header_entry(Compose *compose)
6768 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6775 const gchar *header = NULL;
6776 ComposeHeaderEntry *headerentry;
6777 gboolean standard_header = FALSE;
6778 GtkListStore *model;
6780 #if !(GTK_CHECK_VERSION(2,12,0))
6781 GtkTooltips *tips = compose->tooltips;
6784 headerentry = g_new0(ComposeHeaderEntry, 1);
6786 /* Combo box model */
6787 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6788 #if !GTK_CHECK_VERSION(2, 24, 0)
6789 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6791 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6793 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6795 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6797 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6798 COMPOSE_NEWSGROUPS);
6799 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6801 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6802 COMPOSE_FOLLOWUPTO);
6803 compose_add_extra_header_entries(model);
6806 #if GTK_CHECK_VERSION(2, 24, 0)
6807 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6808 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6809 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6810 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6811 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6813 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6814 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6815 G_CALLBACK(compose_grab_focus_cb), compose);
6816 gtk_widget_show(combo);
6819 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6820 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6823 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6824 compose->header_nextrow, compose->header_nextrow+1,
6825 GTK_SHRINK, GTK_FILL, 0, 0);
6826 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6827 const gchar *last_header_entry = gtk_entry_get_text(
6828 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6830 while (*string != NULL) {
6831 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6832 standard_header = TRUE;
6835 if (standard_header)
6836 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6838 if (!compose->header_last || !standard_header) {
6839 switch(compose->account->protocol) {
6841 header = prefs_common_translated_header_name("Newsgroups:");
6844 header = prefs_common_translated_header_name("To:");
6849 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6851 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6852 G_CALLBACK(compose_grab_focus_cb), compose);
6854 /* Entry field with cleanup button */
6855 button = gtk_button_new();
6856 gtk_button_set_image(GTK_BUTTON(button),
6857 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6858 gtk_widget_show(button);
6859 CLAWS_SET_TIP(button,
6860 _("Delete entry contents"));
6861 entry = gtk_entry_new();
6862 gtk_widget_show(entry);
6863 CLAWS_SET_TIP(entry,
6864 _("Use <tab> to autocomplete from addressbook"));
6865 hbox = gtk_hbox_new (FALSE, 0);
6866 gtk_widget_show(hbox);
6867 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6868 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6869 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6870 compose->header_nextrow, compose->header_nextrow+1,
6871 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6873 g_signal_connect(G_OBJECT(entry), "key-press-event",
6874 G_CALLBACK(compose_headerentry_key_press_event_cb),
6876 g_signal_connect(G_OBJECT(entry), "changed",
6877 G_CALLBACK(compose_headerentry_changed_cb),
6879 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6880 G_CALLBACK(compose_grab_focus_cb), compose);
6882 g_signal_connect(G_OBJECT(button), "clicked",
6883 G_CALLBACK(compose_headerentry_button_clicked_cb),
6887 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6888 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6889 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6890 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6891 G_CALLBACK(compose_header_drag_received_cb),
6893 g_signal_connect(G_OBJECT(entry), "drag-drop",
6894 G_CALLBACK(compose_drag_drop),
6896 g_signal_connect(G_OBJECT(entry), "populate-popup",
6897 G_CALLBACK(compose_entry_popup_extend),
6900 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6902 headerentry->compose = compose;
6903 headerentry->combo = combo;
6904 headerentry->entry = entry;
6905 headerentry->button = button;
6906 headerentry->hbox = hbox;
6907 headerentry->headernum = compose->header_nextrow;
6908 headerentry->type = PREF_NONE;
6910 compose->header_nextrow++;
6911 compose->header_last = headerentry;
6912 compose->header_list =
6913 g_slist_append(compose->header_list,
6917 static void compose_add_header_entry(Compose *compose, const gchar *header,
6918 gchar *text, ComposePrefType pref_type)
6920 ComposeHeaderEntry *last_header = compose->header_last;
6921 gchar *tmp = g_strdup(text), *email;
6922 gboolean replyto_hdr;
6924 replyto_hdr = (!strcasecmp(header,
6925 prefs_common_translated_header_name("Reply-To:")) ||
6927 prefs_common_translated_header_name("Followup-To:")) ||
6929 prefs_common_translated_header_name("In-Reply-To:")));
6931 extract_address(tmp);
6932 email = g_utf8_strdown(tmp, -1);
6934 if (replyto_hdr == FALSE &&
6935 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6937 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6938 header, text, (gint) pref_type);
6944 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6945 gtk_entry_set_text(GTK_ENTRY(
6946 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6948 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6949 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6950 last_header->type = pref_type;
6952 if (replyto_hdr == FALSE)
6953 g_hash_table_insert(compose->email_hashtable, email,
6954 GUINT_TO_POINTER(1));
6961 static void compose_destroy_headerentry(Compose *compose,
6962 ComposeHeaderEntry *headerentry)
6964 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6967 extract_address(text);
6968 email = g_utf8_strdown(text, -1);
6969 g_hash_table_remove(compose->email_hashtable, email);
6973 gtk_widget_destroy(headerentry->combo);
6974 gtk_widget_destroy(headerentry->entry);
6975 gtk_widget_destroy(headerentry->button);
6976 gtk_widget_destroy(headerentry->hbox);
6977 g_free(headerentry);
6980 static void compose_remove_header_entries(Compose *compose)
6983 for (list = compose->header_list; list; list = list->next)
6984 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6986 compose->header_last = NULL;
6987 g_slist_free(compose->header_list);
6988 compose->header_list = NULL;
6989 compose->header_nextrow = 1;
6990 compose_create_header_entry(compose);
6993 static GtkWidget *compose_create_header(Compose *compose)
6995 GtkWidget *from_optmenu_hbox;
6996 GtkWidget *header_scrolledwin_main;
6997 GtkWidget *header_table_main;
6998 GtkWidget *header_scrolledwin;
6999 GtkWidget *header_table;
7001 /* parent with account selection and from header */
7002 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7003 gtk_widget_show(header_scrolledwin_main);
7004 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7006 header_table_main = gtk_table_new(2, 2, FALSE);
7007 gtk_widget_show(header_table_main);
7008 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7009 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7010 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7012 from_optmenu_hbox = compose_account_option_menu_create(compose);
7013 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7014 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7016 /* child with header labels and entries */
7017 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7018 gtk_widget_show(header_scrolledwin);
7019 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7021 header_table = gtk_table_new(2, 2, FALSE);
7022 gtk_widget_show(header_table);
7023 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7024 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7025 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7027 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7028 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7030 compose->header_table = header_table;
7031 compose->header_list = NULL;
7032 compose->header_nextrow = 0;
7034 compose_create_header_entry(compose);
7036 compose->table = NULL;
7038 return header_scrolledwin_main;
7041 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7043 Compose *compose = (Compose *)data;
7044 GdkEventButton event;
7047 event.time = gtk_get_current_event_time();
7049 return attach_button_pressed(compose->attach_clist, &event, compose);
7052 static GtkWidget *compose_create_attach(Compose *compose)
7054 GtkWidget *attach_scrwin;
7055 GtkWidget *attach_clist;
7057 GtkListStore *store;
7058 GtkCellRenderer *renderer;
7059 GtkTreeViewColumn *column;
7060 GtkTreeSelection *selection;
7062 /* attachment list */
7063 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7064 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7065 GTK_POLICY_AUTOMATIC,
7066 GTK_POLICY_AUTOMATIC);
7067 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7069 store = gtk_list_store_new(N_ATTACH_COLS,
7075 G_TYPE_AUTO_POINTER,
7077 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7078 (GTK_TREE_MODEL(store)));
7079 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7080 g_object_unref(store);
7082 renderer = gtk_cell_renderer_text_new();
7083 column = gtk_tree_view_column_new_with_attributes
7084 (_("Mime type"), renderer, "text",
7085 COL_MIMETYPE, NULL);
7086 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7088 renderer = gtk_cell_renderer_text_new();
7089 column = gtk_tree_view_column_new_with_attributes
7090 (_("Size"), renderer, "text",
7092 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7094 renderer = gtk_cell_renderer_text_new();
7095 column = gtk_tree_view_column_new_with_attributes
7096 (_("Name"), renderer, "text",
7098 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7100 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7101 prefs_common.use_stripes_everywhere);
7102 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7103 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7105 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7106 G_CALLBACK(attach_selected), compose);
7107 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7108 G_CALLBACK(attach_button_pressed), compose);
7109 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7110 G_CALLBACK(popup_attach_button_pressed), compose);
7111 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7112 G_CALLBACK(attach_key_pressed), compose);
7115 gtk_drag_dest_set(attach_clist,
7116 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7117 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7118 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7119 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7120 G_CALLBACK(compose_attach_drag_received_cb),
7122 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7123 G_CALLBACK(compose_drag_drop),
7126 compose->attach_scrwin = attach_scrwin;
7127 compose->attach_clist = attach_clist;
7129 return attach_scrwin;
7132 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7133 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7135 static GtkWidget *compose_create_others(Compose *compose)
7138 GtkWidget *savemsg_checkbtn;
7139 GtkWidget *savemsg_combo;
7140 GtkWidget *savemsg_select;
7143 gchar *folderidentifier;
7145 /* Table for settings */
7146 table = gtk_table_new(3, 1, FALSE);
7147 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7148 gtk_widget_show(table);
7149 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7152 /* Save Message to folder */
7153 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7154 gtk_widget_show(savemsg_checkbtn);
7155 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7156 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7159 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7160 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7162 #if !GTK_CHECK_VERSION(2, 24, 0)
7163 savemsg_combo = gtk_combo_box_entry_new_text();
7165 savemsg_combo = gtk_combo_box_text_new_with_entry();
7167 compose->savemsg_checkbtn = savemsg_checkbtn;
7168 compose->savemsg_combo = savemsg_combo;
7169 gtk_widget_show(savemsg_combo);
7171 if (prefs_common.compose_save_to_history)
7172 #if !GTK_CHECK_VERSION(2, 24, 0)
7173 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7174 prefs_common.compose_save_to_history);
7176 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7177 prefs_common.compose_save_to_history);
7179 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7180 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7181 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7182 G_CALLBACK(compose_grab_focus_cb), compose);
7183 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7184 folderidentifier = folder_item_get_identifier(account_get_special_folder
7185 (compose->account, F_OUTBOX));
7186 compose_set_save_to(compose, folderidentifier);
7187 g_free(folderidentifier);
7190 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7191 gtk_widget_show(savemsg_select);
7192 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7193 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7194 G_CALLBACK(compose_savemsg_select_cb),
7200 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7202 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7203 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7206 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7211 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7214 path = folder_item_get_identifier(dest);
7216 compose_set_save_to(compose, path);
7220 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7221 GdkAtom clip, GtkTextIter *insert_place);
7224 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7228 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7230 if (event->button == 3) {
7232 GtkTextIter sel_start, sel_end;
7233 gboolean stuff_selected;
7235 /* move the cursor to allow GtkAspell to check the word
7236 * under the mouse */
7237 if (event->x && event->y) {
7238 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7239 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7241 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7244 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7245 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7248 stuff_selected = gtk_text_buffer_get_selection_bounds(
7250 &sel_start, &sel_end);
7252 gtk_text_buffer_place_cursor (buffer, &iter);
7253 /* reselect stuff */
7255 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7256 gtk_text_buffer_select_range(buffer,
7257 &sel_start, &sel_end);
7259 return FALSE; /* pass the event so that the right-click goes through */
7262 if (event->button == 2) {
7267 /* get the middle-click position to paste at the correct place */
7268 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7269 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7271 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7274 entry_paste_clipboard(compose, text,
7275 prefs_common.linewrap_pastes,
7276 GDK_SELECTION_PRIMARY, &iter);
7284 static void compose_spell_menu_changed(void *data)
7286 Compose *compose = (Compose *)data;
7288 GtkWidget *menuitem;
7289 GtkWidget *parent_item;
7290 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7293 if (compose->gtkaspell == NULL)
7296 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7297 "/Menu/Spelling/Options");
7299 /* setting the submenu removes /Spelling/Options from the factory
7300 * so we need to save it */
7302 if (parent_item == NULL) {
7303 parent_item = compose->aspell_options_menu;
7304 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7306 compose->aspell_options_menu = parent_item;
7308 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7310 spell_menu = g_slist_reverse(spell_menu);
7311 for (items = spell_menu;
7312 items; items = items->next) {
7313 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7314 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7315 gtk_widget_show(GTK_WIDGET(menuitem));
7317 g_slist_free(spell_menu);
7319 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7320 gtk_widget_show(parent_item);
7323 static void compose_dict_changed(void *data)
7325 Compose *compose = (Compose *) data;
7327 if(compose->gtkaspell &&
7328 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7331 gtkaspell_highlight_all(compose->gtkaspell);
7332 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7336 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7338 Compose *compose = (Compose *)data;
7339 GdkEventButton event;
7342 event.time = gtk_get_current_event_time();
7346 return text_clicked(compose->text, &event, compose);
7349 static gboolean compose_force_window_origin = TRUE;
7350 static Compose *compose_create(PrefsAccount *account,
7359 GtkWidget *handlebox;
7361 GtkWidget *notebook;
7363 GtkWidget *attach_hbox;
7364 GtkWidget *attach_lab1;
7365 GtkWidget *attach_lab2;
7370 GtkWidget *subject_hbox;
7371 GtkWidget *subject_frame;
7372 GtkWidget *subject_entry;
7376 GtkWidget *edit_vbox;
7377 GtkWidget *ruler_hbox;
7379 GtkWidget *scrolledwin;
7381 GtkTextBuffer *buffer;
7382 GtkClipboard *clipboard;
7384 UndoMain *undostruct;
7386 GtkWidget *popupmenu;
7387 GtkWidget *tmpl_menu;
7388 GtkActionGroup *action_group = NULL;
7391 GtkAspell * gtkaspell = NULL;
7394 static GdkGeometry geometry;
7396 cm_return_val_if_fail(account != NULL, NULL);
7398 debug_print("Creating compose window...\n");
7399 compose = g_new0(Compose, 1);
7401 compose->batch = batch;
7402 compose->account = account;
7403 compose->folder = folder;
7405 compose->mutex = cm_mutex_new();
7406 compose->set_cursor_pos = -1;
7408 #if !(GTK_CHECK_VERSION(2,12,0))
7409 compose->tooltips = tips;
7412 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7414 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7415 gtk_widget_set_size_request(window, prefs_common.compose_width,
7416 prefs_common.compose_height);
7418 if (!geometry.max_width) {
7419 geometry.max_width = gdk_screen_width();
7420 geometry.max_height = gdk_screen_height();
7423 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7424 &geometry, GDK_HINT_MAX_SIZE);
7425 if (!geometry.min_width) {
7426 geometry.min_width = 600;
7427 geometry.min_height = 440;
7429 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7430 &geometry, GDK_HINT_MIN_SIZE);
7432 #ifndef GENERIC_UMPC
7433 if (compose_force_window_origin)
7434 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7435 prefs_common.compose_y);
7437 g_signal_connect(G_OBJECT(window), "delete_event",
7438 G_CALLBACK(compose_delete_cb), compose);
7439 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7440 gtk_widget_realize(window);
7442 gtkut_widget_set_composer_icon(window);
7444 vbox = gtk_vbox_new(FALSE, 0);
7445 gtk_container_add(GTK_CONTAINER(window), vbox);
7447 compose->ui_manager = gtk_ui_manager_new();
7448 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7449 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7450 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7451 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7452 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7453 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7454 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7455 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7456 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7457 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7461 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7481 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7492 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7497 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7499 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7505 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7576 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)
7577 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)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7583 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)
7584 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)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7588 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7589 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)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7593 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)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7599 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)
7600 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7601 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7605 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)
7606 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)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7615 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7617 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7619 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)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7623 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7628 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7630 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7632 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7637 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7638 gtk_widget_show_all(menubar);
7640 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7641 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7643 if (prefs_common.toolbar_detachable) {
7644 handlebox = gtk_handle_box_new();
7646 handlebox = gtk_hbox_new(FALSE, 0);
7648 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7650 gtk_widget_realize(handlebox);
7651 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7654 vbox2 = gtk_vbox_new(FALSE, 2);
7655 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7656 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7659 notebook = gtk_notebook_new();
7660 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7661 gtk_widget_show(notebook);
7663 /* header labels and entries */
7664 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7665 compose_create_header(compose),
7666 gtk_label_new_with_mnemonic(_("Hea_der")));
7667 /* attachment list */
7668 attach_hbox = gtk_hbox_new(FALSE, 0);
7669 gtk_widget_show(attach_hbox);
7671 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7672 gtk_widget_show(attach_lab1);
7673 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7675 attach_lab2 = gtk_label_new("");
7676 gtk_widget_show(attach_lab2);
7677 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7679 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7680 compose_create_attach(compose),
7683 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7684 compose_create_others(compose),
7685 gtk_label_new_with_mnemonic(_("Othe_rs")));
7688 subject_hbox = gtk_hbox_new(FALSE, 0);
7689 gtk_widget_show(subject_hbox);
7691 subject_frame = gtk_frame_new(NULL);
7692 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7693 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7694 gtk_widget_show(subject_frame);
7696 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7697 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7698 gtk_widget_show(subject);
7700 label = gtk_label_new(_("Subject:"));
7701 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7702 gtk_widget_show(label);
7705 subject_entry = claws_spell_entry_new();
7707 subject_entry = gtk_entry_new();
7709 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7710 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7711 G_CALLBACK(compose_grab_focus_cb), compose);
7712 gtk_widget_show(subject_entry);
7713 compose->subject_entry = subject_entry;
7714 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7716 edit_vbox = gtk_vbox_new(FALSE, 0);
7718 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7721 ruler_hbox = gtk_hbox_new(FALSE, 0);
7722 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7724 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7725 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7726 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7730 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7731 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7732 GTK_POLICY_AUTOMATIC,
7733 GTK_POLICY_AUTOMATIC);
7734 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7736 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7738 text = gtk_text_view_new();
7739 if (prefs_common.show_compose_margin) {
7740 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7741 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7743 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7744 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7745 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7746 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7747 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7749 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7750 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7751 G_CALLBACK(compose_notebook_size_alloc), compose);
7752 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7753 G_CALLBACK(compose_edit_size_alloc),
7755 g_signal_connect(G_OBJECT(buffer), "changed",
7756 G_CALLBACK(compose_changed_cb), compose);
7757 g_signal_connect(G_OBJECT(text), "grab_focus",
7758 G_CALLBACK(compose_grab_focus_cb), compose);
7759 g_signal_connect(G_OBJECT(buffer), "insert_text",
7760 G_CALLBACK(text_inserted), compose);
7761 g_signal_connect(G_OBJECT(text), "button_press_event",
7762 G_CALLBACK(text_clicked), compose);
7763 g_signal_connect(G_OBJECT(text), "popup-menu",
7764 G_CALLBACK(compose_popup_menu), compose);
7765 g_signal_connect(G_OBJECT(subject_entry), "changed",
7766 G_CALLBACK(compose_changed_cb), compose);
7767 g_signal_connect(G_OBJECT(subject_entry), "activate",
7768 G_CALLBACK(compose_subject_entry_activated), compose);
7771 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7772 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7773 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7774 g_signal_connect(G_OBJECT(text), "drag_data_received",
7775 G_CALLBACK(compose_insert_drag_received_cb),
7777 g_signal_connect(G_OBJECT(text), "drag-drop",
7778 G_CALLBACK(compose_drag_drop),
7780 g_signal_connect(G_OBJECT(text), "key-press-event",
7781 G_CALLBACK(completion_set_focus_to_subject),
7783 gtk_widget_show_all(vbox);
7785 /* pane between attach clist and text */
7786 paned = gtk_vpaned_new();
7787 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7788 gtk_paned_add1(GTK_PANED(paned), notebook);
7789 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7790 gtk_widget_show_all(paned);
7793 if (prefs_common.textfont) {
7794 PangoFontDescription *font_desc;
7796 font_desc = pango_font_description_from_string
7797 (prefs_common.textfont);
7799 gtk_widget_modify_font(text, font_desc);
7800 pango_font_description_free(font_desc);
7804 gtk_action_group_add_actions(action_group, compose_popup_entries,
7805 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7806 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7813 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7815 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7816 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7817 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7819 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7821 undostruct = undo_init(text);
7822 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7825 address_completion_start(window);
7827 compose->window = window;
7828 compose->vbox = vbox;
7829 compose->menubar = menubar;
7830 compose->handlebox = handlebox;
7832 compose->vbox2 = vbox2;
7834 compose->paned = paned;
7836 compose->attach_label = attach_lab2;
7838 compose->notebook = notebook;
7839 compose->edit_vbox = edit_vbox;
7840 compose->ruler_hbox = ruler_hbox;
7841 compose->ruler = ruler;
7842 compose->scrolledwin = scrolledwin;
7843 compose->text = text;
7845 compose->focused_editable = NULL;
7847 compose->popupmenu = popupmenu;
7849 compose->tmpl_menu = tmpl_menu;
7851 compose->mode = mode;
7852 compose->rmode = mode;
7854 compose->targetinfo = NULL;
7855 compose->replyinfo = NULL;
7856 compose->fwdinfo = NULL;
7858 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7859 g_str_equal, (GDestroyNotify) g_free, NULL);
7861 compose->replyto = NULL;
7863 compose->bcc = NULL;
7864 compose->followup_to = NULL;
7866 compose->ml_post = NULL;
7868 compose->inreplyto = NULL;
7869 compose->references = NULL;
7870 compose->msgid = NULL;
7871 compose->boundary = NULL;
7873 compose->autowrap = prefs_common.autowrap;
7874 compose->autoindent = prefs_common.auto_indent;
7875 compose->use_signing = FALSE;
7876 compose->use_encryption = FALSE;
7877 compose->privacy_system = NULL;
7879 compose->modified = FALSE;
7881 compose->return_receipt = FALSE;
7883 compose->to_list = NULL;
7884 compose->newsgroup_list = NULL;
7886 compose->undostruct = undostruct;
7888 compose->sig_str = NULL;
7890 compose->exteditor_file = NULL;
7891 compose->exteditor_pid = -1;
7892 compose->exteditor_tag = -1;
7893 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7896 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7897 if (mode != COMPOSE_REDIRECT) {
7898 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7899 strcmp(prefs_common.dictionary, "")) {
7900 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7901 prefs_common.alt_dictionary,
7902 conv_get_locale_charset_str(),
7903 prefs_common.misspelled_col,
7904 prefs_common.check_while_typing,
7905 prefs_common.recheck_when_changing_dict,
7906 prefs_common.use_alternate,
7907 prefs_common.use_both_dicts,
7908 GTK_TEXT_VIEW(text),
7909 GTK_WINDOW(compose->window),
7910 compose_dict_changed,
7911 compose_spell_menu_changed,
7914 alertpanel_error(_("Spell checker could not "
7916 gtkaspell_checkers_strerror());
7917 gtkaspell_checkers_reset_error();
7919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7923 compose->gtkaspell = gtkaspell;
7924 compose_spell_menu_changed(compose);
7925 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7928 compose_select_account(compose, account, TRUE);
7930 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7931 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7933 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7934 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7936 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7937 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7939 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7940 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7942 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7943 if (account->protocol != A_NNTP)
7944 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7945 prefs_common_translated_header_name("To:"));
7947 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7948 prefs_common_translated_header_name("Newsgroups:"));
7950 #ifndef USE_NEW_ADDRBOOK
7951 addressbook_set_target_compose(compose);
7953 if (mode != COMPOSE_REDIRECT)
7954 compose_set_template_menu(compose);
7956 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7959 compose_list = g_list_append(compose_list, compose);
7961 if (!prefs_common.show_ruler)
7962 gtk_widget_hide(ruler_hbox);
7964 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7967 compose->priority = PRIORITY_NORMAL;
7968 compose_update_priority_menu_item(compose);
7970 compose_set_out_encoding(compose);
7973 compose_update_actions_menu(compose);
7975 /* Privacy Systems menu */
7976 compose_update_privacy_systems_menu(compose);
7978 activate_privacy_system(compose, account, TRUE);
7979 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7981 gtk_widget_realize(window);
7983 gtk_widget_show(window);
7989 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7994 GtkWidget *optmenubox;
7997 GtkWidget *from_name = NULL;
7998 #if !(GTK_CHECK_VERSION(2,12,0))
7999 GtkTooltips *tips = compose->tooltips;
8002 gint num = 0, def_menu = 0;
8004 accounts = account_get_list();
8005 cm_return_val_if_fail(accounts != NULL, NULL);
8007 optmenubox = gtk_event_box_new();
8008 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8009 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8011 hbox = gtk_hbox_new(FALSE, 6);
8012 from_name = gtk_entry_new();
8014 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8015 G_CALLBACK(compose_grab_focus_cb), compose);
8017 for (; accounts != NULL; accounts = accounts->next, num++) {
8018 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8019 gchar *name, *from = NULL;
8021 if (ac == compose->account) def_menu = num;
8023 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8026 if (ac == compose->account) {
8027 if (ac->name && *ac->name) {
8029 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8030 from = g_strdup_printf("%s <%s>",
8032 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8034 from = g_strdup_printf("%s",
8036 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8039 COMBOBOX_ADD(menu, name, ac->account_id);
8044 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8046 g_signal_connect(G_OBJECT(optmenu), "changed",
8047 G_CALLBACK(account_activated),
8049 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8050 G_CALLBACK(compose_entry_popup_extend),
8053 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8054 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8056 CLAWS_SET_TIP(optmenubox,
8057 _("Account to use for this email"));
8058 CLAWS_SET_TIP(from_name,
8059 _("Sender address to be used"));
8061 compose->account_combo = optmenu;
8062 compose->from_name = from_name;
8067 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8069 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8070 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8071 Compose *compose = (Compose *) data;
8073 compose->priority = value;
8077 static void compose_reply_change_mode(Compose *compose,
8080 gboolean was_modified = compose->modified;
8082 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8084 cm_return_if_fail(compose->replyinfo != NULL);
8086 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8088 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8090 if (action == COMPOSE_REPLY_TO_ALL)
8092 if (action == COMPOSE_REPLY_TO_SENDER)
8094 if (action == COMPOSE_REPLY_TO_LIST)
8097 compose_remove_header_entries(compose);
8098 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8099 if (compose->account->set_autocc && compose->account->auto_cc)
8100 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8102 if (compose->account->set_autobcc && compose->account->auto_bcc)
8103 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8105 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8106 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8107 compose_show_first_last_header(compose, TRUE);
8108 compose->modified = was_modified;
8109 compose_set_title(compose);
8112 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8114 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8115 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8116 Compose *compose = (Compose *) data;
8119 compose_reply_change_mode(compose, value);
8122 static void compose_update_priority_menu_item(Compose * compose)
8124 GtkWidget *menuitem = NULL;
8125 switch (compose->priority) {
8126 case PRIORITY_HIGHEST:
8127 menuitem = gtk_ui_manager_get_widget
8128 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8131 menuitem = gtk_ui_manager_get_widget
8132 (compose->ui_manager, "/Menu/Options/Priority/High");
8134 case PRIORITY_NORMAL:
8135 menuitem = gtk_ui_manager_get_widget
8136 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8139 menuitem = gtk_ui_manager_get_widget
8140 (compose->ui_manager, "/Menu/Options/Priority/Low");
8142 case PRIORITY_LOWEST:
8143 menuitem = gtk_ui_manager_get_widget
8144 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8147 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8150 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8152 Compose *compose = (Compose *) data;
8154 gboolean can_sign = FALSE, can_encrypt = FALSE;
8156 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8158 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8161 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8162 g_free(compose->privacy_system);
8163 compose->privacy_system = NULL;
8164 if (systemid != NULL) {
8165 compose->privacy_system = g_strdup(systemid);
8167 can_sign = privacy_system_can_sign(systemid);
8168 can_encrypt = privacy_system_can_encrypt(systemid);
8171 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8173 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8174 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8177 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8179 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8180 GtkWidget *menuitem = NULL;
8181 GList *children, *amenu;
8182 gboolean can_sign = FALSE, can_encrypt = FALSE;
8183 gboolean found = FALSE;
8185 if (compose->privacy_system != NULL) {
8187 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8188 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8189 cm_return_if_fail(menuitem != NULL);
8191 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8194 while (amenu != NULL) {
8195 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8196 if (systemid != NULL) {
8197 if (strcmp(systemid, compose->privacy_system) == 0 &&
8198 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8199 menuitem = GTK_WIDGET(amenu->data);
8201 can_sign = privacy_system_can_sign(systemid);
8202 can_encrypt = privacy_system_can_encrypt(systemid);
8206 } else if (strlen(compose->privacy_system) == 0 &&
8207 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8208 menuitem = GTK_WIDGET(amenu->data);
8211 can_encrypt = FALSE;
8216 amenu = amenu->next;
8218 g_list_free(children);
8219 if (menuitem != NULL)
8220 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8222 if (warn && !found && strlen(compose->privacy_system)) {
8223 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8224 "will not be able to sign or encrypt this message."),
8225 compose->privacy_system);
8229 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8230 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8233 static void compose_set_out_encoding(Compose *compose)
8235 CharSet out_encoding;
8236 const gchar *branch = NULL;
8237 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8239 switch(out_encoding) {
8240 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8241 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8242 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8243 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8244 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8245 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8246 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8247 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8248 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8249 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8250 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8251 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8252 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8253 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8254 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8255 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8256 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8257 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8258 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8259 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8260 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8261 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8262 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8263 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8264 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8265 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8266 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8267 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8268 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8269 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8270 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8271 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8272 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8274 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8277 static void compose_set_template_menu(Compose *compose)
8279 GSList *tmpl_list, *cur;
8283 tmpl_list = template_get_config();
8285 menu = gtk_menu_new();
8287 gtk_menu_set_accel_group (GTK_MENU (menu),
8288 gtk_ui_manager_get_accel_group(compose->ui_manager));
8289 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8290 Template *tmpl = (Template *)cur->data;
8291 gchar *accel_path = NULL;
8292 item = gtk_menu_item_new_with_label(tmpl->name);
8293 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8294 g_signal_connect(G_OBJECT(item), "activate",
8295 G_CALLBACK(compose_template_activate_cb),
8297 g_object_set_data(G_OBJECT(item), "template", tmpl);
8298 gtk_widget_show(item);
8299 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8300 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8304 gtk_widget_show(menu);
8305 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8308 void compose_update_actions_menu(Compose *compose)
8310 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8313 static void compose_update_privacy_systems_menu(Compose *compose)
8315 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8316 GSList *systems, *cur;
8318 GtkWidget *system_none;
8320 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8321 GtkWidget *privacy_menu = gtk_menu_new();
8323 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8324 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8326 g_signal_connect(G_OBJECT(system_none), "activate",
8327 G_CALLBACK(compose_set_privacy_system_cb), compose);
8329 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8330 gtk_widget_show(system_none);
8332 systems = privacy_get_system_ids();
8333 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8334 gchar *systemid = cur->data;
8336 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8337 widget = gtk_radio_menu_item_new_with_label(group,
8338 privacy_system_get_name(systemid));
8339 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8340 g_strdup(systemid), g_free);
8341 g_signal_connect(G_OBJECT(widget), "activate",
8342 G_CALLBACK(compose_set_privacy_system_cb), compose);
8344 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8345 gtk_widget_show(widget);
8348 g_slist_free(systems);
8349 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8350 gtk_widget_show_all(privacy_menu);
8351 gtk_widget_show_all(privacy_menuitem);
8354 void compose_reflect_prefs_all(void)
8359 for (cur = compose_list; cur != NULL; cur = cur->next) {
8360 compose = (Compose *)cur->data;
8361 compose_set_template_menu(compose);
8365 void compose_reflect_prefs_pixmap_theme(void)
8370 for (cur = compose_list; cur != NULL; cur = cur->next) {
8371 compose = (Compose *)cur->data;
8372 toolbar_update(TOOLBAR_COMPOSE, compose);
8376 static const gchar *compose_quote_char_from_context(Compose *compose)
8378 const gchar *qmark = NULL;
8380 cm_return_val_if_fail(compose != NULL, NULL);
8382 switch (compose->mode) {
8383 /* use forward-specific quote char */
8384 case COMPOSE_FORWARD:
8385 case COMPOSE_FORWARD_AS_ATTACH:
8386 case COMPOSE_FORWARD_INLINE:
8387 if (compose->folder && compose->folder->prefs &&
8388 compose->folder->prefs->forward_with_format)
8389 qmark = compose->folder->prefs->forward_quotemark;
8390 else if (compose->account->forward_with_format)
8391 qmark = compose->account->forward_quotemark;
8393 qmark = prefs_common.fw_quotemark;
8396 /* use reply-specific quote char in all other modes */
8398 if (compose->folder && compose->folder->prefs &&
8399 compose->folder->prefs->reply_with_format)
8400 qmark = compose->folder->prefs->reply_quotemark;
8401 else if (compose->account->reply_with_format)
8402 qmark = compose->account->reply_quotemark;
8404 qmark = prefs_common.quotemark;
8408 if (qmark == NULL || *qmark == '\0')
8414 static void compose_template_apply(Compose *compose, Template *tmpl,
8418 GtkTextBuffer *buffer;
8422 gchar *parsed_str = NULL;
8423 gint cursor_pos = 0;
8424 const gchar *err_msg = _("The body of the template has an error at line %d.");
8427 /* process the body */
8429 text = GTK_TEXT_VIEW(compose->text);
8430 buffer = gtk_text_view_get_buffer(text);
8433 qmark = compose_quote_char_from_context(compose);
8435 if (compose->replyinfo != NULL) {
8438 gtk_text_buffer_set_text(buffer, "", -1);
8439 mark = gtk_text_buffer_get_insert(buffer);
8440 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8442 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8443 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8445 } else if (compose->fwdinfo != NULL) {
8448 gtk_text_buffer_set_text(buffer, "", -1);
8449 mark = gtk_text_buffer_get_insert(buffer);
8450 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8452 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8453 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8456 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8458 GtkTextIter start, end;
8461 gtk_text_buffer_get_start_iter(buffer, &start);
8462 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8463 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8465 /* clear the buffer now */
8467 gtk_text_buffer_set_text(buffer, "", -1);
8469 parsed_str = compose_quote_fmt(compose, dummyinfo,
8470 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8471 procmsg_msginfo_free( dummyinfo );
8477 gtk_text_buffer_set_text(buffer, "", -1);
8478 mark = gtk_text_buffer_get_insert(buffer);
8479 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8482 if (replace && parsed_str && compose->account->auto_sig)
8483 compose_insert_sig(compose, FALSE);
8485 if (replace && parsed_str) {
8486 gtk_text_buffer_get_start_iter(buffer, &iter);
8487 gtk_text_buffer_place_cursor(buffer, &iter);
8491 cursor_pos = quote_fmt_get_cursor_pos();
8492 compose->set_cursor_pos = cursor_pos;
8493 if (cursor_pos == -1)
8495 gtk_text_buffer_get_start_iter(buffer, &iter);
8496 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8497 gtk_text_buffer_place_cursor(buffer, &iter);
8500 /* process the other fields */
8502 compose_template_apply_fields(compose, tmpl);
8503 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8504 quote_fmt_reset_vartable();
8505 compose_changed_cb(NULL, compose);
8508 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8509 gtkaspell_highlight_all(compose->gtkaspell);
8513 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8515 MsgInfo* dummyinfo = NULL;
8516 MsgInfo *msginfo = NULL;
8519 if (compose->replyinfo != NULL)
8520 msginfo = compose->replyinfo;
8521 else if (compose->fwdinfo != NULL)
8522 msginfo = compose->fwdinfo;
8524 dummyinfo = compose_msginfo_new_from_compose(compose);
8525 msginfo = dummyinfo;
8528 if (tmpl->from && *tmpl->from != '\0') {
8530 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8531 compose->gtkaspell);
8533 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8535 quote_fmt_scan_string(tmpl->from);
8538 buf = quote_fmt_get_buffer();
8540 alertpanel_error(_("Template From format error."));
8542 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8546 if (tmpl->to && *tmpl->to != '\0') {
8548 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8549 compose->gtkaspell);
8551 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8553 quote_fmt_scan_string(tmpl->to);
8556 buf = quote_fmt_get_buffer();
8558 alertpanel_error(_("Template To format error."));
8560 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8564 if (tmpl->cc && *tmpl->cc != '\0') {
8566 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8567 compose->gtkaspell);
8569 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8571 quote_fmt_scan_string(tmpl->cc);
8574 buf = quote_fmt_get_buffer();
8576 alertpanel_error(_("Template Cc format error."));
8578 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8582 if (tmpl->bcc && *tmpl->bcc != '\0') {
8584 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8585 compose->gtkaspell);
8587 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8589 quote_fmt_scan_string(tmpl->bcc);
8592 buf = quote_fmt_get_buffer();
8594 alertpanel_error(_("Template Bcc format error."));
8596 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8600 /* process the subject */
8601 if (tmpl->subject && *tmpl->subject != '\0') {
8603 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8604 compose->gtkaspell);
8606 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8608 quote_fmt_scan_string(tmpl->subject);
8611 buf = quote_fmt_get_buffer();
8613 alertpanel_error(_("Template subject format error."));
8615 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8619 procmsg_msginfo_free( dummyinfo );
8622 static void compose_destroy(Compose *compose)
8624 GtkAllocation allocation;
8625 GtkTextBuffer *buffer;
8626 GtkClipboard *clipboard;
8628 compose_list = g_list_remove(compose_list, compose);
8630 if (compose->updating) {
8631 debug_print("danger, not destroying anything now\n");
8632 compose->deferred_destroy = TRUE;
8635 /* NOTE: address_completion_end() does nothing with the window
8636 * however this may change. */
8637 address_completion_end(compose->window);
8639 slist_free_strings_full(compose->to_list);
8640 slist_free_strings_full(compose->newsgroup_list);
8641 slist_free_strings_full(compose->header_list);
8643 slist_free_strings_full(extra_headers);
8644 extra_headers = NULL;
8646 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8648 g_hash_table_destroy(compose->email_hashtable);
8650 procmsg_msginfo_free(compose->targetinfo);
8651 procmsg_msginfo_free(compose->replyinfo);
8652 procmsg_msginfo_free(compose->fwdinfo);
8654 g_free(compose->replyto);
8655 g_free(compose->cc);
8656 g_free(compose->bcc);
8657 g_free(compose->newsgroups);
8658 g_free(compose->followup_to);
8660 g_free(compose->ml_post);
8662 g_free(compose->inreplyto);
8663 g_free(compose->references);
8664 g_free(compose->msgid);
8665 g_free(compose->boundary);
8667 g_free(compose->redirect_filename);
8668 if (compose->undostruct)
8669 undo_destroy(compose->undostruct);
8671 g_free(compose->sig_str);
8673 g_free(compose->exteditor_file);
8675 g_free(compose->orig_charset);
8677 g_free(compose->privacy_system);
8679 #ifndef USE_NEW_ADDRBOOK
8680 if (addressbook_get_target_compose() == compose)
8681 addressbook_set_target_compose(NULL);
8684 if (compose->gtkaspell) {
8685 gtkaspell_delete(compose->gtkaspell);
8686 compose->gtkaspell = NULL;
8690 if (!compose->batch) {
8691 gtk_widget_get_allocation(compose->window, &allocation);
8692 prefs_common.compose_width = allocation.width;
8693 prefs_common.compose_height = allocation.height;
8696 if (!gtk_widget_get_parent(compose->paned))
8697 gtk_widget_destroy(compose->paned);
8698 gtk_widget_destroy(compose->popupmenu);
8700 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8701 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8702 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8704 gtk_widget_destroy(compose->window);
8705 toolbar_destroy(compose->toolbar);
8706 g_free(compose->toolbar);
8707 cm_mutex_free(compose->mutex);
8711 static void compose_attach_info_free(AttachInfo *ainfo)
8713 g_free(ainfo->file);
8714 g_free(ainfo->content_type);
8715 g_free(ainfo->name);
8716 g_free(ainfo->charset);
8720 static void compose_attach_update_label(Compose *compose)
8725 GtkTreeModel *model;
8730 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8731 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8732 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8736 while(gtk_tree_model_iter_next(model, &iter))
8739 text = g_strdup_printf("(%d)", i);
8740 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8744 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8746 Compose *compose = (Compose *)data;
8747 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8748 GtkTreeSelection *selection;
8750 GtkTreeModel *model;
8752 selection = gtk_tree_view_get_selection(tree_view);
8753 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8758 for (cur = sel; cur != NULL; cur = cur->next) {
8759 GtkTreePath *path = cur->data;
8760 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8763 gtk_tree_path_free(path);
8766 for (cur = sel; cur != NULL; cur = cur->next) {
8767 GtkTreeRowReference *ref = cur->data;
8768 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8771 if (gtk_tree_model_get_iter(model, &iter, path))
8772 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8774 gtk_tree_path_free(path);
8775 gtk_tree_row_reference_free(ref);
8779 compose_attach_update_label(compose);
8782 static struct _AttachProperty
8785 GtkWidget *mimetype_entry;
8786 GtkWidget *encoding_optmenu;
8787 GtkWidget *path_entry;
8788 GtkWidget *filename_entry;
8790 GtkWidget *cancel_btn;
8793 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8795 gtk_tree_path_free((GtkTreePath *)ptr);
8798 static void compose_attach_property(GtkAction *action, gpointer data)
8800 Compose *compose = (Compose *)data;
8801 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8803 GtkComboBox *optmenu;
8804 GtkTreeSelection *selection;
8806 GtkTreeModel *model;
8809 static gboolean cancelled;
8811 /* only if one selected */
8812 selection = gtk_tree_view_get_selection(tree_view);
8813 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8816 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8820 path = (GtkTreePath *) sel->data;
8821 gtk_tree_model_get_iter(model, &iter, path);
8822 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8825 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8831 if (!attach_prop.window)
8832 compose_attach_property_create(&cancelled);
8833 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8834 gtk_widget_grab_focus(attach_prop.ok_btn);
8835 gtk_widget_show(attach_prop.window);
8836 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8837 GTK_WINDOW(compose->window));
8839 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8840 if (ainfo->encoding == ENC_UNKNOWN)
8841 combobox_select_by_data(optmenu, ENC_BASE64);
8843 combobox_select_by_data(optmenu, ainfo->encoding);
8845 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8846 ainfo->content_type ? ainfo->content_type : "");
8847 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8848 ainfo->file ? ainfo->file : "");
8849 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8850 ainfo->name ? ainfo->name : "");
8853 const gchar *entry_text;
8855 gchar *cnttype = NULL;
8862 gtk_widget_hide(attach_prop.window);
8863 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8868 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8869 if (*entry_text != '\0') {
8872 text = g_strstrip(g_strdup(entry_text));
8873 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8874 cnttype = g_strdup(text);
8877 alertpanel_error(_("Invalid MIME type."));
8883 ainfo->encoding = combobox_get_active_data(optmenu);
8885 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8886 if (*entry_text != '\0') {
8887 if (is_file_exist(entry_text) &&
8888 (size = get_file_size(entry_text)) > 0)
8889 file = g_strdup(entry_text);
8892 (_("File doesn't exist or is empty."));
8898 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8899 if (*entry_text != '\0') {
8900 g_free(ainfo->name);
8901 ainfo->name = g_strdup(entry_text);
8905 g_free(ainfo->content_type);
8906 ainfo->content_type = cnttype;
8909 g_free(ainfo->file);
8913 ainfo->size = (goffset)size;
8915 /* update tree store */
8916 text = to_human_readable(ainfo->size);
8917 gtk_tree_model_get_iter(model, &iter, path);
8918 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8919 COL_MIMETYPE, ainfo->content_type,
8921 COL_NAME, ainfo->name,
8922 COL_CHARSET, ainfo->charset,
8928 gtk_tree_path_free(path);
8931 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8933 label = gtk_label_new(str); \
8934 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8935 GTK_FILL, 0, 0, 0); \
8936 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8938 entry = gtk_entry_new(); \
8939 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8940 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8943 static void compose_attach_property_create(gboolean *cancelled)
8949 GtkWidget *mimetype_entry;
8952 GtkListStore *optmenu_menu;
8953 GtkWidget *path_entry;
8954 GtkWidget *filename_entry;
8957 GtkWidget *cancel_btn;
8958 GList *mime_type_list, *strlist;
8961 debug_print("Creating attach_property window...\n");
8963 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8964 gtk_widget_set_size_request(window, 480, -1);
8965 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8966 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8967 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8968 g_signal_connect(G_OBJECT(window), "delete_event",
8969 G_CALLBACK(attach_property_delete_event),
8971 g_signal_connect(G_OBJECT(window), "key_press_event",
8972 G_CALLBACK(attach_property_key_pressed),
8975 vbox = gtk_vbox_new(FALSE, 8);
8976 gtk_container_add(GTK_CONTAINER(window), vbox);
8978 table = gtk_table_new(4, 2, FALSE);
8979 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8980 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8981 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8983 label = gtk_label_new(_("MIME type"));
8984 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8986 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8987 #if !GTK_CHECK_VERSION(2, 24, 0)
8988 mimetype_entry = gtk_combo_box_entry_new_text();
8990 mimetype_entry = gtk_combo_box_text_new_with_entry();
8992 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8993 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8995 /* stuff with list */
8996 mime_type_list = procmime_get_mime_type_list();
8998 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8999 MimeType *type = (MimeType *) mime_type_list->data;
9002 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9004 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9007 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9008 (GCompareFunc)strcmp2);
9011 for (mime_type_list = strlist; mime_type_list != NULL;
9012 mime_type_list = mime_type_list->next) {
9013 #if !GTK_CHECK_VERSION(2, 24, 0)
9014 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9016 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9018 g_free(mime_type_list->data);
9020 g_list_free(strlist);
9021 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9022 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9024 label = gtk_label_new(_("Encoding"));
9025 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9027 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9029 hbox = gtk_hbox_new(FALSE, 0);
9030 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9031 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9033 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9034 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9036 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9037 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9038 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9039 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9040 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9042 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9044 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9045 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9047 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9048 &ok_btn, GTK_STOCK_OK,
9050 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9051 gtk_widget_grab_default(ok_btn);
9053 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9054 G_CALLBACK(attach_property_ok),
9056 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9057 G_CALLBACK(attach_property_cancel),
9060 gtk_widget_show_all(vbox);
9062 attach_prop.window = window;
9063 attach_prop.mimetype_entry = mimetype_entry;
9064 attach_prop.encoding_optmenu = optmenu;
9065 attach_prop.path_entry = path_entry;
9066 attach_prop.filename_entry = filename_entry;
9067 attach_prop.ok_btn = ok_btn;
9068 attach_prop.cancel_btn = cancel_btn;
9071 #undef SET_LABEL_AND_ENTRY
9073 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9079 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9085 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9086 gboolean *cancelled)
9094 static gboolean attach_property_key_pressed(GtkWidget *widget,
9096 gboolean *cancelled)
9098 if (event && event->keyval == GDK_KEY_Escape) {
9102 if (event && event->keyval == GDK_KEY_Return) {
9110 static void compose_exec_ext_editor(Compose *compose)
9117 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9118 G_DIR_SEPARATOR, compose);
9120 if (pipe(pipe_fds) < 0) {
9126 if ((pid = fork()) < 0) {
9133 /* close the write side of the pipe */
9136 compose->exteditor_file = g_strdup(tmp);
9137 compose->exteditor_pid = pid;
9139 compose_set_ext_editor_sensitive(compose, FALSE);
9142 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9144 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9146 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9150 } else { /* process-monitoring process */
9156 /* close the read side of the pipe */
9159 if (compose_write_body_to_file(compose, tmp) < 0) {
9160 fd_write_all(pipe_fds[1], "2\n", 2);
9164 pid_ed = compose_exec_ext_editor_real(tmp);
9166 fd_write_all(pipe_fds[1], "1\n", 2);
9170 /* wait until editor is terminated */
9171 waitpid(pid_ed, NULL, 0);
9173 fd_write_all(pipe_fds[1], "0\n", 2);
9180 #endif /* G_OS_UNIX */
9184 static gint compose_exec_ext_editor_real(const gchar *file)
9191 cm_return_val_if_fail(file != NULL, -1);
9193 if ((pid = fork()) < 0) {
9198 if (pid != 0) return pid;
9200 /* grandchild process */
9202 if (setpgid(0, getppid()))
9205 if (prefs_common_get_ext_editor_cmd() &&
9206 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9207 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9208 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9210 if (prefs_common_get_ext_editor_cmd())
9211 g_warning("External editor command-line is invalid: '%s'\n",
9212 prefs_common_get_ext_editor_cmd());
9213 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9216 cmdline = strsplit_with_quote(buf, " ", 1024);
9217 execvp(cmdline[0], cmdline);
9220 g_strfreev(cmdline);
9225 static gboolean compose_ext_editor_kill(Compose *compose)
9227 pid_t pgid = compose->exteditor_pid * -1;
9230 ret = kill(pgid, 0);
9232 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9236 msg = g_strdup_printf
9237 (_("The external editor is still working.\n"
9238 "Force terminating the process?\n"
9239 "process group id: %d"), -pgid);
9240 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9241 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9245 if (val == G_ALERTALTERNATE) {
9246 g_source_remove(compose->exteditor_tag);
9247 g_io_channel_shutdown(compose->exteditor_ch,
9249 g_io_channel_unref(compose->exteditor_ch);
9251 if (kill(pgid, SIGTERM) < 0) perror("kill");
9252 waitpid(compose->exteditor_pid, NULL, 0);
9254 g_warning("Terminated process group id: %d", -pgid);
9255 g_warning("Temporary file: %s",
9256 compose->exteditor_file);
9258 compose_set_ext_editor_sensitive(compose, TRUE);
9260 g_free(compose->exteditor_file);
9261 compose->exteditor_file = NULL;
9262 compose->exteditor_pid = -1;
9263 compose->exteditor_ch = NULL;
9264 compose->exteditor_tag = -1;
9272 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9276 Compose *compose = (Compose *)data;
9279 debug_print("Compose: input from monitoring process\n");
9281 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9283 g_io_channel_shutdown(source, FALSE, NULL);
9284 g_io_channel_unref(source);
9286 waitpid(compose->exteditor_pid, NULL, 0);
9288 if (buf[0] == '0') { /* success */
9289 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9290 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9292 gtk_text_buffer_set_text(buffer, "", -1);
9293 compose_insert_file(compose, compose->exteditor_file);
9294 compose_changed_cb(NULL, compose);
9295 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9297 if (claws_unlink(compose->exteditor_file) < 0)
9298 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9299 } else if (buf[0] == '1') { /* failed */
9300 g_warning("Couldn't exec external editor\n");
9301 if (claws_unlink(compose->exteditor_file) < 0)
9302 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9303 } else if (buf[0] == '2') {
9304 g_warning("Couldn't write to file\n");
9305 } else if (buf[0] == '3') {
9306 g_warning("Pipe read failed\n");
9309 compose_set_ext_editor_sensitive(compose, TRUE);
9311 g_free(compose->exteditor_file);
9312 compose->exteditor_file = NULL;
9313 compose->exteditor_pid = -1;
9314 compose->exteditor_ch = NULL;
9315 compose->exteditor_tag = -1;
9320 static void compose_set_ext_editor_sensitive(Compose *compose,
9323 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9325 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9326 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9327 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9328 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9329 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9330 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9332 gtk_widget_set_sensitive(compose->text, sensitive);
9333 if (compose->toolbar->send_btn)
9334 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9335 if (compose->toolbar->sendl_btn)
9336 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9337 if (compose->toolbar->draft_btn)
9338 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9339 if (compose->toolbar->insert_btn)
9340 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9341 if (compose->toolbar->sig_btn)
9342 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9343 if (compose->toolbar->exteditor_btn)
9344 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9345 if (compose->toolbar->linewrap_current_btn)
9346 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9347 if (compose->toolbar->linewrap_all_btn)
9348 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9350 #endif /* G_OS_UNIX */
9353 * compose_undo_state_changed:
9355 * Change the sensivity of the menuentries undo and redo
9357 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9358 gint redo_state, gpointer data)
9360 Compose *compose = (Compose *)data;
9362 switch (undo_state) {
9363 case UNDO_STATE_TRUE:
9364 if (!undostruct->undo_state) {
9365 undostruct->undo_state = TRUE;
9366 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9369 case UNDO_STATE_FALSE:
9370 if (undostruct->undo_state) {
9371 undostruct->undo_state = FALSE;
9372 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9375 case UNDO_STATE_UNCHANGED:
9377 case UNDO_STATE_REFRESH:
9378 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9381 g_warning("Undo state not recognized");
9385 switch (redo_state) {
9386 case UNDO_STATE_TRUE:
9387 if (!undostruct->redo_state) {
9388 undostruct->redo_state = TRUE;
9389 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9392 case UNDO_STATE_FALSE:
9393 if (undostruct->redo_state) {
9394 undostruct->redo_state = FALSE;
9395 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9398 case UNDO_STATE_UNCHANGED:
9400 case UNDO_STATE_REFRESH:
9401 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9404 g_warning("Redo state not recognized");
9409 /* callback functions */
9411 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9412 GtkAllocation *allocation,
9415 prefs_common.compose_notebook_height = allocation->height;
9418 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9419 * includes "non-client" (windows-izm) in calculation, so this calculation
9420 * may not be accurate.
9422 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9423 GtkAllocation *allocation,
9424 GtkSHRuler *shruler)
9426 if (prefs_common.show_ruler) {
9427 gint char_width = 0, char_height = 0;
9428 gint line_width_in_chars;
9430 gtkut_get_font_size(GTK_WIDGET(widget),
9431 &char_width, &char_height);
9432 line_width_in_chars =
9433 (allocation->width - allocation->x) / char_width;
9435 /* got the maximum */
9436 gtk_shruler_set_range(GTK_SHRULER(shruler),
9437 0.0, line_width_in_chars, 0);
9446 ComposePrefType type;
9447 gboolean entry_marked;
9450 static void account_activated(GtkComboBox *optmenu, gpointer data)
9452 Compose *compose = (Compose *)data;
9455 gchar *folderidentifier;
9456 gint account_id = 0;
9459 GSList *list, *saved_list = NULL;
9460 HeaderEntryState *state;
9461 GtkRcStyle *style = NULL;
9462 #if !GTK_CHECK_VERSION(3, 0, 0)
9463 static GdkColor yellow;
9464 static gboolean color_set = FALSE;
9466 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9469 /* Get ID of active account in the combo box */
9470 menu = gtk_combo_box_get_model(optmenu);
9471 gtk_combo_box_get_active_iter(optmenu, &iter);
9472 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9474 ac = account_find_from_id(account_id);
9475 cm_return_if_fail(ac != NULL);
9477 if (ac != compose->account) {
9478 compose_select_account(compose, ac, FALSE);
9480 for (list = compose->header_list; list; list = list->next) {
9481 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9483 if (hentry->type == PREF_ACCOUNT || !list->next) {
9484 compose_destroy_headerentry(compose, hentry);
9488 state = g_malloc0(sizeof(HeaderEntryState));
9489 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9490 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9491 state->entry = gtk_editable_get_chars(
9492 GTK_EDITABLE(hentry->entry), 0, -1);
9493 state->type = hentry->type;
9495 #if !GTK_CHECK_VERSION(3, 0, 0)
9497 gdk_color_parse("#f5f6be", &yellow);
9498 color_set = gdk_colormap_alloc_color(
9499 gdk_colormap_get_system(),
9500 &yellow, FALSE, TRUE);
9504 style = gtk_widget_get_modifier_style(hentry->entry);
9505 state->entry_marked = gdk_color_equal(&yellow,
9506 &style->base[GTK_STATE_NORMAL]);
9508 saved_list = g_slist_append(saved_list, state);
9509 compose_destroy_headerentry(compose, hentry);
9512 compose->header_last = NULL;
9513 g_slist_free(compose->header_list);
9514 compose->header_list = NULL;
9515 compose->header_nextrow = 1;
9516 compose_create_header_entry(compose);
9518 if (ac->set_autocc && ac->auto_cc)
9519 compose_entry_append(compose, ac->auto_cc,
9520 COMPOSE_CC, PREF_ACCOUNT);
9522 if (ac->set_autobcc && ac->auto_bcc)
9523 compose_entry_append(compose, ac->auto_bcc,
9524 COMPOSE_BCC, PREF_ACCOUNT);
9526 if (ac->set_autoreplyto && ac->auto_replyto)
9527 compose_entry_append(compose, ac->auto_replyto,
9528 COMPOSE_REPLYTO, PREF_ACCOUNT);
9530 for (list = saved_list; list; list = list->next) {
9531 state = (HeaderEntryState *) list->data;
9533 compose_add_header_entry(compose, state->header,
9534 state->entry, state->type);
9535 if (state->entry_marked)
9536 compose_entry_mark_default_to(compose, state->entry);
9538 g_free(state->header);
9539 g_free(state->entry);
9542 g_slist_free(saved_list);
9544 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9545 (ac->protocol == A_NNTP) ?
9546 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9549 /* Set message save folder */
9550 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9551 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9553 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9554 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9556 compose_set_save_to(compose, NULL);
9557 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9558 folderidentifier = folder_item_get_identifier(account_get_special_folder
9559 (compose->account, F_OUTBOX));
9560 compose_set_save_to(compose, folderidentifier);
9561 g_free(folderidentifier);
9565 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9566 GtkTreeViewColumn *column, Compose *compose)
9568 compose_attach_property(NULL, compose);
9571 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9574 Compose *compose = (Compose *)data;
9575 GtkTreeSelection *attach_selection;
9576 gint attach_nr_selected;
9578 if (!event) return FALSE;
9580 if (event->button == 3) {
9581 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9582 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9584 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9585 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9587 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9588 NULL, NULL, event->button, event->time);
9595 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9598 Compose *compose = (Compose *)data;
9600 if (!event) return FALSE;
9602 switch (event->keyval) {
9603 case GDK_KEY_Delete:
9604 compose_attach_remove_selected(NULL, compose);
9610 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9612 toolbar_comp_set_sensitive(compose, allow);
9613 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9614 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9616 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9618 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9619 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9620 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9622 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9626 static void compose_send_cb(GtkAction *action, gpointer data)
9628 Compose *compose = (Compose *)data;
9630 if (prefs_common.work_offline &&
9631 !inc_offline_should_override(TRUE,
9632 _("Claws Mail needs network access in order "
9633 "to send this email.")))
9636 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9637 g_source_remove(compose->draft_timeout_tag);
9638 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9641 compose_send(compose);
9644 static void compose_send_later_cb(GtkAction *action, gpointer data)
9646 Compose *compose = (Compose *)data;
9650 compose_allow_user_actions(compose, FALSE);
9651 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9652 compose_allow_user_actions(compose, TRUE);
9656 compose_close(compose);
9657 } else if (val == -1) {
9658 alertpanel_error(_("Could not queue message."));
9659 } else if (val == -2) {
9660 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9661 } else if (val == -3) {
9662 if (privacy_peek_error())
9663 alertpanel_error(_("Could not queue message for sending:\n\n"
9664 "Signature failed: %s"), privacy_get_error());
9665 } else if (val == -4) {
9666 alertpanel_error(_("Could not queue message for sending:\n\n"
9667 "Charset conversion failed."));
9668 } else if (val == -5) {
9669 alertpanel_error(_("Could not queue message for sending:\n\n"
9670 "Couldn't get recipient encryption key."));
9671 } else if (val == -6) {
9674 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9677 #define DRAFTED_AT_EXIT "drafted_at_exit"
9678 static void compose_register_draft(MsgInfo *info)
9680 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9681 DRAFTED_AT_EXIT, NULL);
9682 FILE *fp = g_fopen(filepath, "ab");
9685 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9693 gboolean compose_draft (gpointer data, guint action)
9695 Compose *compose = (Compose *)data;
9700 MsgFlags flag = {0, 0};
9701 static gboolean lock = FALSE;
9702 MsgInfo *newmsginfo;
9704 gboolean target_locked = FALSE;
9705 gboolean err = FALSE;
9707 if (lock) return FALSE;
9709 if (compose->sending)
9712 draft = account_get_special_folder(compose->account, F_DRAFT);
9713 cm_return_val_if_fail(draft != NULL, FALSE);
9715 if (!g_mutex_trylock(compose->mutex)) {
9716 /* we don't want to lock the mutex once it's available,
9717 * because as the only other part of compose.c locking
9718 * it is compose_close - which means once unlocked,
9719 * the compose struct will be freed */
9720 debug_print("couldn't lock mutex, probably sending\n");
9726 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9727 G_DIR_SEPARATOR, compose);
9728 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9729 FILE_OP_ERROR(tmp, "fopen");
9733 /* chmod for security */
9734 if (change_file_mode_rw(fp, tmp) < 0) {
9735 FILE_OP_ERROR(tmp, "chmod");
9736 g_warning("can't change file mode\n");
9739 /* Save draft infos */
9740 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9741 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9743 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9744 gchar *savefolderid;
9746 savefolderid = compose_get_save_to(compose);
9747 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9748 g_free(savefolderid);
9750 if (compose->return_receipt) {
9751 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9753 if (compose->privacy_system) {
9754 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9755 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9756 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9759 /* Message-ID of message replying to */
9760 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9763 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9764 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9767 /* Message-ID of message forwarding to */
9768 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9771 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9772 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9776 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9777 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9779 sheaders = compose_get_manual_headers_info(compose);
9780 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9783 /* end of headers */
9784 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9791 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9795 if (fclose(fp) == EOF) {
9799 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9800 if (compose->targetinfo) {
9801 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9803 flag.perm_flags |= MSG_LOCKED;
9805 flag.tmp_flags = MSG_DRAFT;
9807 folder_item_scan(draft);
9808 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9809 MsgInfo *tmpinfo = NULL;
9810 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9811 if (compose->msgid) {
9812 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9815 msgnum = tmpinfo->msgnum;
9816 procmsg_msginfo_free(tmpinfo);
9817 debug_print("got draft msgnum %d from scanning\n", msgnum);
9819 debug_print("didn't get draft msgnum after scanning\n");
9822 debug_print("got draft msgnum %d from adding\n", msgnum);
9828 if (action != COMPOSE_AUTO_SAVE) {
9829 if (action != COMPOSE_DRAFT_FOR_EXIT)
9830 alertpanel_error(_("Could not save draft."));
9833 gtkut_window_popup(compose->window);
9834 val = alertpanel_full(_("Could not save draft"),
9835 _("Could not save draft.\n"
9836 "Do you want to cancel exit or discard this email?"),
9837 _("_Cancel exit"), _("_Discard email"), NULL,
9838 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9839 if (val == G_ALERTALTERNATE) {
9841 g_mutex_unlock(compose->mutex); /* must be done before closing */
9842 compose_close(compose);
9846 g_mutex_unlock(compose->mutex); /* must be done before closing */
9855 if (compose->mode == COMPOSE_REEDIT) {
9856 compose_remove_reedit_target(compose, TRUE);
9859 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9862 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9864 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9866 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9867 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9868 procmsg_msginfo_set_flags(newmsginfo, 0,
9869 MSG_HAS_ATTACHMENT);
9871 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9872 compose_register_draft(newmsginfo);
9874 procmsg_msginfo_free(newmsginfo);
9877 folder_item_scan(draft);
9879 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9881 g_mutex_unlock(compose->mutex); /* must be done before closing */
9882 compose_close(compose);
9888 path = folder_item_fetch_msg(draft, msgnum);
9890 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9893 if (g_stat(path, &s) < 0) {
9894 FILE_OP_ERROR(path, "stat");
9900 procmsg_msginfo_free(compose->targetinfo);
9901 compose->targetinfo = procmsg_msginfo_new();
9902 compose->targetinfo->msgnum = msgnum;
9903 compose->targetinfo->size = (goffset)s.st_size;
9904 compose->targetinfo->mtime = s.st_mtime;
9905 compose->targetinfo->folder = draft;
9907 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9908 compose->mode = COMPOSE_REEDIT;
9910 if (action == COMPOSE_AUTO_SAVE) {
9911 compose->autosaved_draft = compose->targetinfo;
9913 compose->modified = FALSE;
9914 compose_set_title(compose);
9918 g_mutex_unlock(compose->mutex);
9922 void compose_clear_exit_drafts(void)
9924 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9925 DRAFTED_AT_EXIT, NULL);
9926 if (is_file_exist(filepath))
9927 claws_unlink(filepath);
9932 void compose_reopen_exit_drafts(void)
9934 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9935 DRAFTED_AT_EXIT, NULL);
9936 FILE *fp = g_fopen(filepath, "rb");
9940 while (fgets(buf, sizeof(buf), fp)) {
9941 gchar **parts = g_strsplit(buf, "\t", 2);
9942 const gchar *folder = parts[0];
9943 int msgnum = parts[1] ? atoi(parts[1]):-1;
9945 if (folder && *folder && msgnum > -1) {
9946 FolderItem *item = folder_find_item_from_identifier(folder);
9947 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9949 compose_reedit(info, FALSE);
9956 compose_clear_exit_drafts();
9959 static void compose_save_cb(GtkAction *action, gpointer data)
9961 Compose *compose = (Compose *)data;
9962 compose_draft(compose, COMPOSE_KEEP_EDITING);
9963 compose->rmode = COMPOSE_REEDIT;
9966 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9968 if (compose && file_list) {
9971 for ( tmp = file_list; tmp; tmp = tmp->next) {
9972 gchar *file = (gchar *) tmp->data;
9973 gchar *utf8_filename = conv_filename_to_utf8(file);
9974 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9975 compose_changed_cb(NULL, compose);
9980 g_free(utf8_filename);
9985 static void compose_attach_cb(GtkAction *action, gpointer data)
9987 Compose *compose = (Compose *)data;
9990 if (compose->redirect_filename != NULL)
9993 /* Set focus_window properly, in case we were called via popup menu,
9994 * which unsets it (via focus_out_event callback on compose window). */
9995 manage_window_focus_in(compose->window, NULL, NULL);
9997 file_list = filesel_select_multiple_files_open(_("Select file"));
10000 compose_attach_from_list(compose, file_list, TRUE);
10001 g_list_free(file_list);
10005 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10007 Compose *compose = (Compose *)data;
10009 gint files_inserted = 0;
10011 file_list = filesel_select_multiple_files_open(_("Select file"));
10016 for ( tmp = file_list; tmp; tmp = tmp->next) {
10017 gchar *file = (gchar *) tmp->data;
10018 gchar *filedup = g_strdup(file);
10019 gchar *shortfile = g_path_get_basename(filedup);
10020 ComposeInsertResult res;
10021 /* insert the file if the file is short or if the user confirmed that
10022 he/she wants to insert the large file */
10023 res = compose_insert_file(compose, file);
10024 if (res == COMPOSE_INSERT_READ_ERROR) {
10025 alertpanel_error(_("File '%s' could not be read."), shortfile);
10026 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10027 alertpanel_error(_("File '%s' contained invalid characters\n"
10028 "for the current encoding, insertion may be incorrect."),
10030 } else if (res == COMPOSE_INSERT_SUCCESS)
10037 g_list_free(file_list);
10041 if (files_inserted > 0 && compose->gtkaspell &&
10042 compose->gtkaspell->check_while_typing)
10043 gtkaspell_highlight_all(compose->gtkaspell);
10047 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10049 Compose *compose = (Compose *)data;
10051 compose_insert_sig(compose, FALSE);
10054 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10056 Compose *compose = (Compose *)data;
10058 compose_insert_sig(compose, TRUE);
10061 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10065 Compose *compose = (Compose *)data;
10067 gtkut_widget_get_uposition(widget, &x, &y);
10068 if (!compose->batch) {
10069 prefs_common.compose_x = x;
10070 prefs_common.compose_y = y;
10072 if (compose->sending || compose->updating)
10074 compose_close_cb(NULL, compose);
10078 void compose_close_toolbar(Compose *compose)
10080 compose_close_cb(NULL, compose);
10083 static gboolean compose_can_autosave(Compose *compose)
10085 if (compose->privacy_system && compose->use_encryption)
10086 return prefs_common.autosave && prefs_common.autosave_encrypted;
10088 return prefs_common.autosave;
10091 static void compose_close_cb(GtkAction *action, gpointer data)
10093 Compose *compose = (Compose *)data;
10097 if (compose->exteditor_tag != -1) {
10098 if (!compose_ext_editor_kill(compose))
10103 if (compose->modified) {
10104 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10105 if (!g_mutex_trylock(compose->mutex)) {
10106 /* we don't want to lock the mutex once it's available,
10107 * because as the only other part of compose.c locking
10108 * it is compose_close - which means once unlocked,
10109 * the compose struct will be freed */
10110 debug_print("couldn't lock mutex, probably sending\n");
10114 val = alertpanel(_("Discard message"),
10115 _("This message has been modified. Discard it?"),
10116 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10118 val = alertpanel(_("Save changes"),
10119 _("This message has been modified. Save the latest changes?"),
10120 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10122 g_mutex_unlock(compose->mutex);
10124 case G_ALERTDEFAULT:
10125 if (compose_can_autosave(compose) && !reedit)
10126 compose_remove_draft(compose);
10128 case G_ALERTALTERNATE:
10129 compose_draft(data, COMPOSE_QUIT_EDITING);
10136 compose_close(compose);
10139 static void compose_print_cb(GtkAction *action, gpointer data)
10141 Compose *compose = (Compose *) data;
10143 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10144 if (compose->targetinfo)
10145 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10148 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10150 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10151 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10152 Compose *compose = (Compose *) data;
10155 compose->out_encoding = (CharSet)value;
10158 static void compose_address_cb(GtkAction *action, gpointer data)
10160 Compose *compose = (Compose *)data;
10162 #ifndef USE_NEW_ADDRBOOK
10163 addressbook_open(compose);
10165 GError* error = NULL;
10166 addressbook_connect_signals(compose);
10167 addressbook_dbus_open(TRUE, &error);
10169 g_warning("%s", error->message);
10170 g_error_free(error);
10175 static void about_show_cb(GtkAction *action, gpointer data)
10180 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10182 Compose *compose = (Compose *)data;
10187 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10188 cm_return_if_fail(tmpl != NULL);
10190 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10192 val = alertpanel(_("Apply template"), msg,
10193 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10196 if (val == G_ALERTDEFAULT)
10197 compose_template_apply(compose, tmpl, TRUE);
10198 else if (val == G_ALERTALTERNATE)
10199 compose_template_apply(compose, tmpl, FALSE);
10202 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10204 Compose *compose = (Compose *)data;
10206 compose_exec_ext_editor(compose);
10209 static void compose_undo_cb(GtkAction *action, gpointer data)
10211 Compose *compose = (Compose *)data;
10212 gboolean prev_autowrap = compose->autowrap;
10214 compose->autowrap = FALSE;
10215 undo_undo(compose->undostruct);
10216 compose->autowrap = prev_autowrap;
10219 static void compose_redo_cb(GtkAction *action, gpointer data)
10221 Compose *compose = (Compose *)data;
10222 gboolean prev_autowrap = compose->autowrap;
10224 compose->autowrap = FALSE;
10225 undo_redo(compose->undostruct);
10226 compose->autowrap = prev_autowrap;
10229 static void entry_cut_clipboard(GtkWidget *entry)
10231 if (GTK_IS_EDITABLE(entry))
10232 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10233 else if (GTK_IS_TEXT_VIEW(entry))
10234 gtk_text_buffer_cut_clipboard(
10235 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10236 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10240 static void entry_copy_clipboard(GtkWidget *entry)
10242 if (GTK_IS_EDITABLE(entry))
10243 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10244 else if (GTK_IS_TEXT_VIEW(entry))
10245 gtk_text_buffer_copy_clipboard(
10246 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10247 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10250 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10251 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10253 if (GTK_IS_TEXT_VIEW(entry)) {
10254 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10255 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10256 GtkTextIter start_iter, end_iter;
10258 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10260 if (contents == NULL)
10263 /* we shouldn't delete the selection when middle-click-pasting, or we
10264 * can't mid-click-paste our own selection */
10265 if (clip != GDK_SELECTION_PRIMARY) {
10266 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10267 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10270 if (insert_place == NULL) {
10271 /* if insert_place isn't specified, insert at the cursor.
10272 * used for Ctrl-V pasting */
10273 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10274 start = gtk_text_iter_get_offset(&start_iter);
10275 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10277 /* if insert_place is specified, paste here.
10278 * used for mid-click-pasting */
10279 start = gtk_text_iter_get_offset(insert_place);
10280 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10281 if (prefs_common.primary_paste_unselects)
10282 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10286 /* paste unwrapped: mark the paste so it's not wrapped later */
10287 end = start + strlen(contents);
10288 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10289 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10290 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10291 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10292 /* rewrap paragraph now (after a mid-click-paste) */
10293 mark_start = gtk_text_buffer_get_insert(buffer);
10294 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10295 gtk_text_iter_backward_char(&start_iter);
10296 compose_beautify_paragraph(compose, &start_iter, TRUE);
10298 } else if (GTK_IS_EDITABLE(entry))
10299 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10301 compose->modified = TRUE;
10304 static void entry_allsel(GtkWidget *entry)
10306 if (GTK_IS_EDITABLE(entry))
10307 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10308 else if (GTK_IS_TEXT_VIEW(entry)) {
10309 GtkTextIter startiter, enditer;
10310 GtkTextBuffer *textbuf;
10312 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10313 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10314 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10316 gtk_text_buffer_move_mark_by_name(textbuf,
10317 "selection_bound", &startiter);
10318 gtk_text_buffer_move_mark_by_name(textbuf,
10319 "insert", &enditer);
10323 static void compose_cut_cb(GtkAction *action, gpointer data)
10325 Compose *compose = (Compose *)data;
10326 if (compose->focused_editable
10327 #ifndef GENERIC_UMPC
10328 && gtk_widget_has_focus(compose->focused_editable)
10331 entry_cut_clipboard(compose->focused_editable);
10334 static void compose_copy_cb(GtkAction *action, gpointer data)
10336 Compose *compose = (Compose *)data;
10337 if (compose->focused_editable
10338 #ifndef GENERIC_UMPC
10339 && gtk_widget_has_focus(compose->focused_editable)
10342 entry_copy_clipboard(compose->focused_editable);
10345 static void compose_paste_cb(GtkAction *action, gpointer data)
10347 Compose *compose = (Compose *)data;
10348 gint prev_autowrap;
10349 GtkTextBuffer *buffer;
10351 if (compose->focused_editable &&
10352 #ifndef GENERIC_UMPC
10353 gtk_widget_has_focus(compose->focused_editable)
10356 entry_paste_clipboard(compose, compose->focused_editable,
10357 prefs_common.linewrap_pastes,
10358 GDK_SELECTION_CLIPBOARD, NULL);
10363 #ifndef GENERIC_UMPC
10364 gtk_widget_has_focus(compose->text) &&
10366 compose->gtkaspell &&
10367 compose->gtkaspell->check_while_typing)
10368 gtkaspell_highlight_all(compose->gtkaspell);
10372 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10374 Compose *compose = (Compose *)data;
10375 gint wrap_quote = prefs_common.linewrap_quote;
10376 if (compose->focused_editable
10377 #ifndef GENERIC_UMPC
10378 && gtk_widget_has_focus(compose->focused_editable)
10381 /* let text_insert() (called directly or at a later time
10382 * after the gtk_editable_paste_clipboard) know that
10383 * text is to be inserted as a quotation. implemented
10384 * by using a simple refcount... */
10385 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10386 G_OBJECT(compose->focused_editable),
10387 "paste_as_quotation"));
10388 g_object_set_data(G_OBJECT(compose->focused_editable),
10389 "paste_as_quotation",
10390 GINT_TO_POINTER(paste_as_quotation + 1));
10391 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10392 entry_paste_clipboard(compose, compose->focused_editable,
10393 prefs_common.linewrap_pastes,
10394 GDK_SELECTION_CLIPBOARD, NULL);
10395 prefs_common.linewrap_quote = wrap_quote;
10399 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10401 Compose *compose = (Compose *)data;
10402 gint prev_autowrap;
10403 GtkTextBuffer *buffer;
10405 if (compose->focused_editable
10406 #ifndef GENERIC_UMPC
10407 && gtk_widget_has_focus(compose->focused_editable)
10410 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10411 GDK_SELECTION_CLIPBOARD, NULL);
10416 #ifndef GENERIC_UMPC
10417 gtk_widget_has_focus(compose->text) &&
10419 compose->gtkaspell &&
10420 compose->gtkaspell->check_while_typing)
10421 gtkaspell_highlight_all(compose->gtkaspell);
10425 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10427 Compose *compose = (Compose *)data;
10428 gint prev_autowrap;
10429 GtkTextBuffer *buffer;
10431 if (compose->focused_editable
10432 #ifndef GENERIC_UMPC
10433 && gtk_widget_has_focus(compose->focused_editable)
10436 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10437 GDK_SELECTION_CLIPBOARD, NULL);
10442 #ifndef GENERIC_UMPC
10443 gtk_widget_has_focus(compose->text) &&
10445 compose->gtkaspell &&
10446 compose->gtkaspell->check_while_typing)
10447 gtkaspell_highlight_all(compose->gtkaspell);
10451 static void compose_allsel_cb(GtkAction *action, gpointer data)
10453 Compose *compose = (Compose *)data;
10454 if (compose->focused_editable
10455 #ifndef GENERIC_UMPC
10456 && gtk_widget_has_focus(compose->focused_editable)
10459 entry_allsel(compose->focused_editable);
10462 static void textview_move_beginning_of_line (GtkTextView *text)
10464 GtkTextBuffer *buffer;
10468 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10470 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10471 mark = gtk_text_buffer_get_insert(buffer);
10472 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10473 gtk_text_iter_set_line_offset(&ins, 0);
10474 gtk_text_buffer_place_cursor(buffer, &ins);
10477 static void textview_move_forward_character (GtkTextView *text)
10479 GtkTextBuffer *buffer;
10483 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10485 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10486 mark = gtk_text_buffer_get_insert(buffer);
10487 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10488 if (gtk_text_iter_forward_cursor_position(&ins))
10489 gtk_text_buffer_place_cursor(buffer, &ins);
10492 static void textview_move_backward_character (GtkTextView *text)
10494 GtkTextBuffer *buffer;
10498 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10500 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10501 mark = gtk_text_buffer_get_insert(buffer);
10502 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10503 if (gtk_text_iter_backward_cursor_position(&ins))
10504 gtk_text_buffer_place_cursor(buffer, &ins);
10507 static void textview_move_forward_word (GtkTextView *text)
10509 GtkTextBuffer *buffer;
10514 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10516 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10517 mark = gtk_text_buffer_get_insert(buffer);
10518 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10519 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10520 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10521 gtk_text_iter_backward_word_start(&ins);
10522 gtk_text_buffer_place_cursor(buffer, &ins);
10526 static void textview_move_backward_word (GtkTextView *text)
10528 GtkTextBuffer *buffer;
10532 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10534 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10535 mark = gtk_text_buffer_get_insert(buffer);
10536 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10537 if (gtk_text_iter_backward_word_starts(&ins, 1))
10538 gtk_text_buffer_place_cursor(buffer, &ins);
10541 static void textview_move_end_of_line (GtkTextView *text)
10543 GtkTextBuffer *buffer;
10547 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10549 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10550 mark = gtk_text_buffer_get_insert(buffer);
10551 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10552 if (gtk_text_iter_forward_to_line_end(&ins))
10553 gtk_text_buffer_place_cursor(buffer, &ins);
10556 static void textview_move_next_line (GtkTextView *text)
10558 GtkTextBuffer *buffer;
10563 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10565 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10566 mark = gtk_text_buffer_get_insert(buffer);
10567 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10568 offset = gtk_text_iter_get_line_offset(&ins);
10569 if (gtk_text_iter_forward_line(&ins)) {
10570 gtk_text_iter_set_line_offset(&ins, offset);
10571 gtk_text_buffer_place_cursor(buffer, &ins);
10575 static void textview_move_previous_line (GtkTextView *text)
10577 GtkTextBuffer *buffer;
10582 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10584 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10585 mark = gtk_text_buffer_get_insert(buffer);
10586 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10587 offset = gtk_text_iter_get_line_offset(&ins);
10588 if (gtk_text_iter_backward_line(&ins)) {
10589 gtk_text_iter_set_line_offset(&ins, offset);
10590 gtk_text_buffer_place_cursor(buffer, &ins);
10594 static void textview_delete_forward_character (GtkTextView *text)
10596 GtkTextBuffer *buffer;
10598 GtkTextIter ins, end_iter;
10600 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10602 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10603 mark = gtk_text_buffer_get_insert(buffer);
10604 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10606 if (gtk_text_iter_forward_char(&end_iter)) {
10607 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10611 static void textview_delete_backward_character (GtkTextView *text)
10613 GtkTextBuffer *buffer;
10615 GtkTextIter ins, end_iter;
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);
10623 if (gtk_text_iter_backward_char(&end_iter)) {
10624 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10628 static void textview_delete_forward_word (GtkTextView *text)
10630 GtkTextBuffer *buffer;
10632 GtkTextIter ins, end_iter;
10634 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10636 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10637 mark = gtk_text_buffer_get_insert(buffer);
10638 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10640 if (gtk_text_iter_forward_word_end(&end_iter)) {
10641 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10645 static void textview_delete_backward_word (GtkTextView *text)
10647 GtkTextBuffer *buffer;
10649 GtkTextIter ins, end_iter;
10651 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10653 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10654 mark = gtk_text_buffer_get_insert(buffer);
10655 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10657 if (gtk_text_iter_backward_word_start(&end_iter)) {
10658 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10662 static void textview_delete_line (GtkTextView *text)
10664 GtkTextBuffer *buffer;
10666 GtkTextIter ins, start_iter, end_iter;
10668 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10670 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10671 mark = gtk_text_buffer_get_insert(buffer);
10672 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10675 gtk_text_iter_set_line_offset(&start_iter, 0);
10678 if (gtk_text_iter_ends_line(&end_iter)){
10679 if (!gtk_text_iter_forward_char(&end_iter))
10680 gtk_text_iter_backward_char(&start_iter);
10683 gtk_text_iter_forward_to_line_end(&end_iter);
10684 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10687 static void textview_delete_to_line_end (GtkTextView *text)
10689 GtkTextBuffer *buffer;
10691 GtkTextIter ins, end_iter;
10693 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10695 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10696 mark = gtk_text_buffer_get_insert(buffer);
10697 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10699 if (gtk_text_iter_ends_line(&end_iter))
10700 gtk_text_iter_forward_char(&end_iter);
10702 gtk_text_iter_forward_to_line_end(&end_iter);
10703 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10706 #define DO_ACTION(name, act) { \
10707 if(!strcmp(name, a_name)) { \
10711 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10713 const gchar *a_name = gtk_action_get_name(action);
10714 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10715 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10716 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10717 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10718 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10719 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10720 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10721 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10722 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10723 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10724 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10725 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10726 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10727 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10731 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10733 Compose *compose = (Compose *)data;
10734 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10735 ComposeCallAdvancedAction action = -1;
10737 action = compose_call_advanced_action_from_path(gaction);
10740 void (*do_action) (GtkTextView *text);
10741 } action_table[] = {
10742 {textview_move_beginning_of_line},
10743 {textview_move_forward_character},
10744 {textview_move_backward_character},
10745 {textview_move_forward_word},
10746 {textview_move_backward_word},
10747 {textview_move_end_of_line},
10748 {textview_move_next_line},
10749 {textview_move_previous_line},
10750 {textview_delete_forward_character},
10751 {textview_delete_backward_character},
10752 {textview_delete_forward_word},
10753 {textview_delete_backward_word},
10754 {textview_delete_line},
10755 {textview_delete_to_line_end}
10758 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10760 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10761 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10762 if (action_table[action].do_action)
10763 action_table[action].do_action(text);
10765 g_warning("Not implemented yet.");
10769 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10771 GtkAllocation allocation;
10775 if (GTK_IS_EDITABLE(widget)) {
10776 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10777 gtk_editable_set_position(GTK_EDITABLE(widget),
10780 if ((parent = gtk_widget_get_parent(widget))
10781 && (parent = gtk_widget_get_parent(parent))
10782 && (parent = gtk_widget_get_parent(parent))) {
10783 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10784 gtk_widget_get_allocation(widget, &allocation);
10785 gint y = allocation.y;
10786 gint height = allocation.height;
10787 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10788 (GTK_SCROLLED_WINDOW(parent));
10790 gfloat value = gtk_adjustment_get_value(shown);
10791 gfloat upper = gtk_adjustment_get_upper(shown);
10792 gfloat page_size = gtk_adjustment_get_page_size(shown);
10793 if (y < (int)value) {
10794 gtk_adjustment_set_value(shown, y - 1);
10796 if ((y + height) > ((int)value + (int)page_size)) {
10797 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10798 gtk_adjustment_set_value(shown,
10799 y + height - (int)page_size - 1);
10801 gtk_adjustment_set_value(shown,
10802 (int)upper - (int)page_size - 1);
10809 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10810 compose->focused_editable = widget;
10812 #ifdef GENERIC_UMPC
10813 if (GTK_IS_TEXT_VIEW(widget)
10814 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10815 g_object_ref(compose->notebook);
10816 g_object_ref(compose->edit_vbox);
10817 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10818 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10819 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10820 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10821 g_object_unref(compose->notebook);
10822 g_object_unref(compose->edit_vbox);
10823 g_signal_handlers_block_by_func(G_OBJECT(widget),
10824 G_CALLBACK(compose_grab_focus_cb),
10826 gtk_widget_grab_focus(widget);
10827 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10828 G_CALLBACK(compose_grab_focus_cb),
10830 } else if (!GTK_IS_TEXT_VIEW(widget)
10831 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10832 g_object_ref(compose->notebook);
10833 g_object_ref(compose->edit_vbox);
10834 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10835 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10836 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10837 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10838 g_object_unref(compose->notebook);
10839 g_object_unref(compose->edit_vbox);
10840 g_signal_handlers_block_by_func(G_OBJECT(widget),
10841 G_CALLBACK(compose_grab_focus_cb),
10843 gtk_widget_grab_focus(widget);
10844 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10845 G_CALLBACK(compose_grab_focus_cb),
10851 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10853 compose->modified = TRUE;
10854 // compose_beautify_paragraph(compose, NULL, TRUE);
10855 #ifndef GENERIC_UMPC
10856 compose_set_title(compose);
10860 static void compose_wrap_cb(GtkAction *action, gpointer data)
10862 Compose *compose = (Compose *)data;
10863 compose_beautify_paragraph(compose, NULL, TRUE);
10866 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10868 Compose *compose = (Compose *)data;
10869 compose_wrap_all_full(compose, TRUE);
10872 static void compose_find_cb(GtkAction *action, gpointer data)
10874 Compose *compose = (Compose *)data;
10876 message_search_compose(compose);
10879 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10882 Compose *compose = (Compose *)data;
10883 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10884 if (compose->autowrap)
10885 compose_wrap_all_full(compose, TRUE);
10886 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10889 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10892 Compose *compose = (Compose *)data;
10893 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10896 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10898 Compose *compose = (Compose *)data;
10900 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10903 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10905 Compose *compose = (Compose *)data;
10907 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10910 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10912 g_free(compose->privacy_system);
10914 compose->privacy_system = g_strdup(account->default_privacy_system);
10915 compose_update_privacy_system_menu_item(compose, warn);
10918 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10920 Compose *compose = (Compose *)data;
10922 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10923 gtk_widget_show(compose->ruler_hbox);
10924 prefs_common.show_ruler = TRUE;
10926 gtk_widget_hide(compose->ruler_hbox);
10927 gtk_widget_queue_resize(compose->edit_vbox);
10928 prefs_common.show_ruler = FALSE;
10932 static void compose_attach_drag_received_cb (GtkWidget *widget,
10933 GdkDragContext *context,
10936 GtkSelectionData *data,
10939 gpointer user_data)
10941 Compose *compose = (Compose *)user_data;
10945 type = gtk_selection_data_get_data_type(data);
10946 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10948 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10950 ) && gtk_drag_get_source_widget(context) !=
10951 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10952 list = uri_list_extract_filenames(
10953 (const gchar *)gtk_selection_data_get_data(data));
10954 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10955 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10956 compose_attach_append
10957 (compose, (const gchar *)tmp->data,
10958 utf8_filename, NULL, NULL);
10959 g_free(utf8_filename);
10961 if (list) compose_changed_cb(NULL, compose);
10962 list_free_strings(list);
10964 } else if (gtk_drag_get_source_widget(context)
10965 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10966 /* comes from our summaryview */
10967 SummaryView * summaryview = NULL;
10968 GSList * list = NULL, *cur = NULL;
10970 if (mainwindow_get_mainwindow())
10971 summaryview = mainwindow_get_mainwindow()->summaryview;
10974 list = summary_get_selected_msg_list(summaryview);
10976 for (cur = list; cur; cur = cur->next) {
10977 MsgInfo *msginfo = (MsgInfo *)cur->data;
10978 gchar *file = NULL;
10980 file = procmsg_get_message_file_full(msginfo,
10983 compose_attach_append(compose, (const gchar *)file,
10984 (const gchar *)file, "message/rfc822", NULL);
10988 g_slist_free(list);
10992 static gboolean compose_drag_drop(GtkWidget *widget,
10993 GdkDragContext *drag_context,
10995 guint time, gpointer user_data)
10997 /* not handling this signal makes compose_insert_drag_received_cb
11002 static gboolean completion_set_focus_to_subject
11003 (GtkWidget *widget,
11004 GdkEventKey *event,
11007 cm_return_val_if_fail(compose != NULL, FALSE);
11009 /* make backtab move to subject field */
11010 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11011 gtk_widget_grab_focus(compose->subject_entry);
11017 static void compose_insert_drag_received_cb (GtkWidget *widget,
11018 GdkDragContext *drag_context,
11021 GtkSelectionData *data,
11024 gpointer user_data)
11026 Compose *compose = (Compose *)user_data;
11030 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11032 type = gtk_selection_data_get_data_type(data);
11034 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11036 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11038 AlertValue val = G_ALERTDEFAULT;
11039 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11041 list = uri_list_extract_filenames(ddata);
11042 if (list == NULL && strstr(ddata, "://")) {
11043 /* Assume a list of no files, and data has ://, is a remote link */
11044 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11045 gchar *tmpfile = get_tmp_file();
11046 str_write_to_file(tmpdata, tmpfile);
11048 compose_insert_file(compose, tmpfile);
11049 claws_unlink(tmpfile);
11051 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11052 compose_beautify_paragraph(compose, NULL, TRUE);
11055 switch (prefs_common.compose_dnd_mode) {
11056 case COMPOSE_DND_ASK:
11057 val = alertpanel_full(_("Insert or attach?"),
11058 _("Do you want to insert the contents of the file(s) "
11059 "into the message body, or attach it to the email?"),
11060 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11061 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11063 case COMPOSE_DND_INSERT:
11064 val = G_ALERTALTERNATE;
11066 case COMPOSE_DND_ATTACH:
11067 val = G_ALERTOTHER;
11070 /* unexpected case */
11071 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11074 if (val & G_ALERTDISABLE) {
11075 val &= ~G_ALERTDISABLE;
11076 /* remember what action to perform by default, only if we don't click Cancel */
11077 if (val == G_ALERTALTERNATE)
11078 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11079 else if (val == G_ALERTOTHER)
11080 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11083 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11084 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11085 list_free_strings(list);
11088 } else if (val == G_ALERTOTHER) {
11089 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11090 list_free_strings(list);
11095 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11096 compose_insert_file(compose, (const gchar *)tmp->data);
11098 list_free_strings(list);
11100 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11105 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11108 static void compose_header_drag_received_cb (GtkWidget *widget,
11109 GdkDragContext *drag_context,
11112 GtkSelectionData *data,
11115 gpointer user_data)
11117 GtkEditable *entry = (GtkEditable *)user_data;
11118 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11120 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11123 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11124 gchar *decoded=g_new(gchar, strlen(email));
11127 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11128 gtk_editable_delete_text(entry, 0, -1);
11129 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11130 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11134 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11137 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11139 Compose *compose = (Compose *)data;
11141 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11142 compose->return_receipt = TRUE;
11144 compose->return_receipt = FALSE;
11147 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11149 Compose *compose = (Compose *)data;
11151 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11152 compose->remove_references = TRUE;
11154 compose->remove_references = FALSE;
11157 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11158 ComposeHeaderEntry *headerentry)
11160 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11164 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11165 GdkEventKey *event,
11166 ComposeHeaderEntry *headerentry)
11168 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11169 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11170 !(event->state & GDK_MODIFIER_MASK) &&
11171 (event->keyval == GDK_KEY_BackSpace) &&
11172 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11173 gtk_container_remove
11174 (GTK_CONTAINER(headerentry->compose->header_table),
11175 headerentry->combo);
11176 gtk_container_remove
11177 (GTK_CONTAINER(headerentry->compose->header_table),
11178 headerentry->entry);
11179 headerentry->compose->header_list =
11180 g_slist_remove(headerentry->compose->header_list,
11182 g_free(headerentry);
11183 } else if (event->keyval == GDK_KEY_Tab) {
11184 if (headerentry->compose->header_last == headerentry) {
11185 /* Override default next focus, and give it to subject_entry
11186 * instead of notebook tabs
11188 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11189 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11196 static gboolean scroll_postpone(gpointer data)
11198 Compose *compose = (Compose *)data;
11200 if (compose->batch)
11203 GTK_EVENTS_FLUSH();
11204 compose_show_first_last_header(compose, FALSE);
11208 static void compose_headerentry_changed_cb(GtkWidget *entry,
11209 ComposeHeaderEntry *headerentry)
11211 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11212 compose_create_header_entry(headerentry->compose);
11213 g_signal_handlers_disconnect_matched
11214 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11215 0, 0, NULL, NULL, headerentry);
11217 if (!headerentry->compose->batch)
11218 g_timeout_add(0, scroll_postpone, headerentry->compose);
11222 static gboolean compose_defer_auto_save_draft(Compose *compose)
11224 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11225 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11229 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11231 GtkAdjustment *vadj;
11233 cm_return_if_fail(compose);
11238 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11239 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11240 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11241 gtk_widget_get_parent(compose->header_table)));
11242 gtk_adjustment_set_value(vadj, (show_first ?
11243 gtk_adjustment_get_lower(vadj) :
11244 (gtk_adjustment_get_upper(vadj) -
11245 gtk_adjustment_get_page_size(vadj))));
11246 gtk_adjustment_changed(vadj);
11249 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11250 const gchar *text, gint len, Compose *compose)
11252 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11253 (G_OBJECT(compose->text), "paste_as_quotation"));
11256 cm_return_if_fail(text != NULL);
11258 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11259 G_CALLBACK(text_inserted),
11261 if (paste_as_quotation) {
11263 const gchar *qmark;
11265 GtkTextIter start_iter;
11268 len = strlen(text);
11270 new_text = g_strndup(text, len);
11272 qmark = compose_quote_char_from_context(compose);
11274 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11275 gtk_text_buffer_place_cursor(buffer, iter);
11277 pos = gtk_text_iter_get_offset(iter);
11279 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11280 _("Quote format error at line %d."));
11281 quote_fmt_reset_vartable();
11283 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11284 GINT_TO_POINTER(paste_as_quotation - 1));
11286 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11287 gtk_text_buffer_place_cursor(buffer, iter);
11288 gtk_text_buffer_delete_mark(buffer, mark);
11290 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11291 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11292 compose_beautify_paragraph(compose, &start_iter, FALSE);
11293 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11294 gtk_text_buffer_delete_mark(buffer, mark);
11296 if (strcmp(text, "\n") || compose->automatic_break
11297 || gtk_text_iter_starts_line(iter)) {
11298 GtkTextIter before_ins;
11299 gtk_text_buffer_insert(buffer, iter, text, len);
11300 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11301 before_ins = *iter;
11302 gtk_text_iter_backward_chars(&before_ins, len);
11303 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11306 /* check if the preceding is just whitespace or quote */
11307 GtkTextIter start_line;
11308 gchar *tmp = NULL, *quote = NULL;
11309 gint quote_len = 0, is_normal = 0;
11310 start_line = *iter;
11311 gtk_text_iter_set_line_offset(&start_line, 0);
11312 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11315 if (*tmp == '\0') {
11318 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11326 gtk_text_buffer_insert(buffer, iter, text, len);
11328 gtk_text_buffer_insert_with_tags_by_name(buffer,
11329 iter, text, len, "no_join", NULL);
11334 if (!paste_as_quotation) {
11335 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11336 compose_beautify_paragraph(compose, iter, FALSE);
11337 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11338 gtk_text_buffer_delete_mark(buffer, mark);
11341 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11342 G_CALLBACK(text_inserted),
11344 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11346 if (compose_can_autosave(compose) &&
11347 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11348 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11349 compose->draft_timeout_tag = g_timeout_add
11350 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11354 static void compose_check_all(GtkAction *action, gpointer data)
11356 Compose *compose = (Compose *)data;
11357 if (!compose->gtkaspell)
11360 if (gtk_widget_has_focus(compose->subject_entry))
11361 claws_spell_entry_check_all(
11362 CLAWS_SPELL_ENTRY(compose->subject_entry));
11364 gtkaspell_check_all(compose->gtkaspell);
11367 static void compose_highlight_all(GtkAction *action, gpointer data)
11369 Compose *compose = (Compose *)data;
11370 if (compose->gtkaspell) {
11371 claws_spell_entry_recheck_all(
11372 CLAWS_SPELL_ENTRY(compose->subject_entry));
11373 gtkaspell_highlight_all(compose->gtkaspell);
11377 static void compose_check_backwards(GtkAction *action, gpointer data)
11379 Compose *compose = (Compose *)data;
11380 if (!compose->gtkaspell) {
11381 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11385 if (gtk_widget_has_focus(compose->subject_entry))
11386 claws_spell_entry_check_backwards(
11387 CLAWS_SPELL_ENTRY(compose->subject_entry));
11389 gtkaspell_check_backwards(compose->gtkaspell);
11392 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11394 Compose *compose = (Compose *)data;
11395 if (!compose->gtkaspell) {
11396 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11400 if (gtk_widget_has_focus(compose->subject_entry))
11401 claws_spell_entry_check_forwards_go(
11402 CLAWS_SPELL_ENTRY(compose->subject_entry));
11404 gtkaspell_check_forwards_go(compose->gtkaspell);
11409 *\brief Guess originating forward account from MsgInfo and several
11410 * "common preference" settings. Return NULL if no guess.
11412 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11414 PrefsAccount *account = NULL;
11416 cm_return_val_if_fail(msginfo, NULL);
11417 cm_return_val_if_fail(msginfo->folder, NULL);
11418 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11420 if (msginfo->folder->prefs->enable_default_account)
11421 account = account_find_from_id(msginfo->folder->prefs->default_account);
11424 account = msginfo->folder->folder->account;
11426 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11428 Xstrdup_a(to, msginfo->to, return NULL);
11429 extract_address(to);
11430 account = account_find_from_address(to, FALSE);
11433 if (!account && prefs_common.forward_account_autosel) {
11434 gchar cc[BUFFSIZE];
11435 if (!procheader_get_header_from_msginfo
11436 (msginfo, cc,sizeof cc , "Cc:")) {
11437 gchar *buf = cc + strlen("Cc:");
11438 extract_address(buf);
11439 account = account_find_from_address(buf, FALSE);
11443 if (!account && prefs_common.forward_account_autosel) {
11444 gchar deliveredto[BUFFSIZE];
11445 if (!procheader_get_header_from_msginfo
11446 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11447 gchar *buf = deliveredto + strlen("Delivered-To:");
11448 extract_address(buf);
11449 account = account_find_from_address(buf, FALSE);
11456 gboolean compose_close(Compose *compose)
11460 cm_return_val_if_fail(compose, FALSE);
11462 if (!g_mutex_trylock(compose->mutex)) {
11463 /* we have to wait for the (possibly deferred by auto-save)
11464 * drafting to be done, before destroying the compose under
11466 debug_print("waiting for drafting to finish...\n");
11467 compose_allow_user_actions(compose, FALSE);
11468 if (compose->close_timeout_tag == 0) {
11469 compose->close_timeout_tag =
11470 g_timeout_add (500, (GSourceFunc) compose_close,
11476 if (compose->close_timeout_tag) {
11477 /* let the close be done by the deferred callback */
11478 g_mutex_unlock(compose->mutex);
11482 gtkut_widget_get_uposition(compose->window, &x, &y);
11483 if (!compose->batch) {
11484 prefs_common.compose_x = x;
11485 prefs_common.compose_y = y;
11487 g_mutex_unlock(compose->mutex);
11488 compose_destroy(compose);
11493 * Add entry field for each address in list.
11494 * \param compose E-Mail composition object.
11495 * \param listAddress List of (formatted) E-Mail addresses.
11497 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11500 node = listAddress;
11502 addr = ( gchar * ) node->data;
11503 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11504 node = g_list_next( node );
11508 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11509 guint action, gboolean opening_multiple)
11511 gchar *body = NULL;
11512 GSList *new_msglist = NULL;
11513 MsgInfo *tmp_msginfo = NULL;
11514 gboolean originally_enc = FALSE;
11515 gboolean originally_sig = FALSE;
11516 Compose *compose = NULL;
11517 gchar *s_system = NULL;
11519 cm_return_if_fail(msgview != NULL);
11521 cm_return_if_fail(msginfo_list != NULL);
11523 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11524 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11525 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11527 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11528 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11529 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11530 orig_msginfo, mimeinfo);
11531 if (tmp_msginfo != NULL) {
11532 new_msglist = g_slist_append(NULL, tmp_msginfo);
11534 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11535 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11536 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11538 tmp_msginfo->folder = orig_msginfo->folder;
11539 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11540 if (orig_msginfo->tags) {
11541 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11542 tmp_msginfo->folder->tags_dirty = TRUE;
11548 if (!opening_multiple)
11549 body = messageview_get_selection(msgview);
11552 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11553 procmsg_msginfo_free(tmp_msginfo);
11554 g_slist_free(new_msglist);
11556 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11558 if (compose && originally_enc) {
11559 compose_force_encryption(compose, compose->account, FALSE, s_system);
11562 if (compose && originally_sig && compose->account->default_sign_reply) {
11563 compose_force_signing(compose, compose->account, s_system);
11567 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11570 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11573 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11574 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11575 GSList *cur = msginfo_list;
11576 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11577 "messages. Opening the windows "
11578 "could take some time. Do you "
11579 "want to continue?"),
11580 g_slist_length(msginfo_list));
11581 if (g_slist_length(msginfo_list) > 9
11582 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11583 != G_ALERTALTERNATE) {
11588 /* We'll open multiple compose windows */
11589 /* let the WM place the next windows */
11590 compose_force_window_origin = FALSE;
11591 for (; cur; cur = cur->next) {
11593 tmplist.data = cur->data;
11594 tmplist.next = NULL;
11595 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11597 compose_force_window_origin = TRUE;
11599 /* forwarding multiple mails as attachments is done via a
11600 * single compose window */
11601 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11605 void compose_check_for_email_account(Compose *compose)
11607 PrefsAccount *ac = NULL, *curr = NULL;
11613 if (compose->account && compose->account->protocol == A_NNTP) {
11614 ac = account_get_cur_account();
11615 if (ac->protocol == A_NNTP) {
11616 list = account_get_list();
11618 for( ; list != NULL ; list = g_list_next(list)) {
11619 curr = (PrefsAccount *) list->data;
11620 if (curr->protocol != A_NNTP) {
11626 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11631 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11632 const gchar *address)
11634 GSList *msginfo_list = NULL;
11635 gchar *body = messageview_get_selection(msgview);
11638 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11640 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11641 compose_check_for_email_account(compose);
11642 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11643 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11644 compose_reply_set_subject(compose, msginfo);
11647 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11650 void compose_set_position(Compose *compose, gint pos)
11652 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11654 gtkut_text_view_set_position(text, pos);
11657 gboolean compose_search_string(Compose *compose,
11658 const gchar *str, gboolean case_sens)
11660 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11662 return gtkut_text_view_search_string(text, str, case_sens);
11665 gboolean compose_search_string_backward(Compose *compose,
11666 const gchar *str, gboolean case_sens)
11668 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11670 return gtkut_text_view_search_string_backward(text, str, case_sens);
11673 /* allocate a msginfo structure and populate its data from a compose data structure */
11674 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11676 MsgInfo *newmsginfo;
11678 gchar buf[BUFFSIZE];
11680 cm_return_val_if_fail( compose != NULL, NULL );
11682 newmsginfo = procmsg_msginfo_new();
11685 get_rfc822_date(buf, sizeof(buf));
11686 newmsginfo->date = g_strdup(buf);
11689 if (compose->from_name) {
11690 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11691 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11695 if (compose->subject_entry)
11696 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11698 /* to, cc, reply-to, newsgroups */
11699 for (list = compose->header_list; list; list = list->next) {
11700 gchar *header = gtk_editable_get_chars(
11702 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11703 gchar *entry = gtk_editable_get_chars(
11704 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11706 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11707 if ( newmsginfo->to == NULL ) {
11708 newmsginfo->to = g_strdup(entry);
11709 } else if (entry && *entry) {
11710 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11711 g_free(newmsginfo->to);
11712 newmsginfo->to = tmp;
11715 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11716 if ( newmsginfo->cc == NULL ) {
11717 newmsginfo->cc = g_strdup(entry);
11718 } else if (entry && *entry) {
11719 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11720 g_free(newmsginfo->cc);
11721 newmsginfo->cc = tmp;
11724 if ( strcasecmp(header,
11725 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11726 if ( newmsginfo->newsgroups == NULL ) {
11727 newmsginfo->newsgroups = g_strdup(entry);
11728 } else if (entry && *entry) {
11729 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11730 g_free(newmsginfo->newsgroups);
11731 newmsginfo->newsgroups = tmp;
11739 /* other data is unset */
11745 /* update compose's dictionaries from folder dict settings */
11746 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11747 FolderItem *folder_item)
11749 cm_return_if_fail(compose != NULL);
11751 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11752 FolderItemPrefs *prefs = folder_item->prefs;
11754 if (prefs->enable_default_dictionary)
11755 gtkaspell_change_dict(compose->gtkaspell,
11756 prefs->default_dictionary, FALSE);
11757 if (folder_item->prefs->enable_default_alt_dictionary)
11758 gtkaspell_change_alt_dict(compose->gtkaspell,
11759 prefs->default_alt_dictionary);
11760 if (prefs->enable_default_dictionary
11761 || prefs->enable_default_alt_dictionary)
11762 compose_spell_menu_changed(compose);
11767 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11769 Compose *compose = (Compose *)data;
11771 cm_return_if_fail(compose != NULL);
11773 gtk_widget_grab_focus(compose->text);